diff --git a/components/identity/__tests__/identity-form-org-signature.test.tsx b/components/identity/__tests__/identity-form-org-signature.test.tsx
index 2c7dae84f..c83a580a9 100644
--- a/components/identity/__tests__/identity-form-org-signature.test.tsx
+++ b/components/identity/__tests__/identity-form-org-signature.test.tsx
@@ -72,7 +72,7 @@ describe('identity signature editor and the organisation signature', () => {
describe('Your details', () => {
it('asks only for the fields the template uses, and stores them', () => {
- setPolicy();
+ setPolicy({ locked: true });
useIdentityStore.setState({ identities: [ALICE] });
render();
expect(screen.queryByTestId('signature-detail-title')).toBeNull();
@@ -81,6 +81,29 @@ describe('Your details', () => {
expect(screen.getByTestId('signature-details-preview').innerHTML).toContain('Alice
Phone: +49 30 2');
});
+ it('previews the signature that is sent: the own one while the organisation one is only the default', () => {
+ setPolicy();
+ useIdentityStore.setState({ identities: [ALICE] });
+ render();
+ expect(screen.getByTestId('signature-detail-phone')).toBeInTheDocument();
+ expect(screen.getByTestId('signature-details-preview').innerHTML).toBe('
preview
');
+ });
+
+ it('asks for what the signature of a group address uses too', () => {
+ const team = { id: 'id-team', name: 'Team', email: 'team@example.org', mayDelete: false } as Identity;
+ const policy: SettingsPolicy = {
+ ...DEFAULT_POLICY,
+ organisation: { ...DEFAULT_ORGANISATION_POLICY },
+ scopes: [{ id: 'team', label: 'Team', match: { groups: ['team@example.org'] }, policy: { organisation: { signature: { text: '{name}\nMobile: {mobile}' } } } }],
+ };
+ const subject = { email: 'alice@example.org', groups: ['team@example.org'] };
+ usePolicyStore.setState({ rawPolicy: policy, policy: { ...policy, scopes: undefined }, subject });
+ useIdentityStore.setState({ identities: [ALICE, team] });
+ render();
+ expect(screen.getByTestId('signature-detail-mobile')).toBeInTheDocument();
+ expect(screen.queryByTestId('signature-detail-phone')).toBeNull();
+ });
+
it('shows nothing without an organisation signature', () => {
setPolicy(undefined, { html: '', text: '', fields: [] });
const { container } = render();
diff --git a/components/settings/signature-details-section.tsx b/components/settings/signature-details-section.tsx
index f0e52b7bc..641bc8e3a 100644
--- a/components/settings/signature-details-section.tsx
+++ b/components/settings/signature-details-section.tsx
@@ -1,10 +1,12 @@
'use client';
+import { useMemo } from 'react';
import { useTranslations } from 'next-intl';
import { Input } from '@/components/ui/input';
import { SettingsSection, SettingItem } from './settings-section';
-import { DEFAULT_ORGANISATION_POLICY, type SignatureField } from '@/lib/admin/organisation-policy';
-import { signatureFieldsToAsk, signatureFieldsUsed } from '@/lib/org-signature';
+import { DEFAULT_ORGANISATION_POLICY, SIGNATURE_FIELDS, type SignatureField } from '@/lib/admin/organisation-policy';
+import { effectiveSignatureIdentity, signatureFieldsToAsk, signatureFieldsUsed } from '@/lib/org-signature';
+import { policyForSender } from '@/lib/org/signature-state';
import { sanitizeSignatureHtmlForDisplay } from '@/lib/email-sanitization';
import { getPlainTextSignature } from '@/lib/signature-utils';
import { useOrgSignatureSetup } from '@/hooks/use-org-signature';
@@ -19,33 +21,52 @@ const INPUT_PROPS: Record s.identities);
+ const policy = usePolicyStore((s) => s.policy);
+ const rawPolicy = usePolicyStore((s) => s.rawPolicy);
+ const subject = usePolicyStore((s) => s.subject);
+ const resolveFor = usePolicyStore((s) => s.resolveFor);
+ return useMemo(() => {
+ const view = { policy, rawPolicy, subject, resolveFor };
+ // Every identity's policy (a group address may have its group's), and
+ // the account's own for an account without identities yet.
+ const policies = [policy, ...identities.map((identity) => policyForSender(view, identity.email))];
+ const used = new Set();
+ for (const each of policies) {
+ const organisation = each.organisation ?? DEFAULT_ORGANISATION_POLICY;
+ for (const field of [...signatureFieldsToAsk(organisation.signature), ...signatureFieldsUsed(organisation.disclaimer)]) {
+ used.add(field);
+ }
+ }
+ return SIGNATURE_FIELDS.filter((field) => used.has(field));
+ }, [identities, policy, rawPolicy, subject, resolveFor]);
+}
+
/**
* "Your details": what the organisation signature asks each user for
* (title, department, phone, mobile), filled in once and used for every
- * address. Shows only the fields the admin's templates use, and nothing
- * without an organisation signature. Synced with the other settings; in
- * Lite it stays in this browser.
+ * address. Shows the fields the signatures of any of the user's addresses
+ * use, and nothing when none does. The preview is the signature the
+ * primary address sends: the user's own where it still applies. Synced
+ * with the other settings; in Lite it stays in this browser.
*/
export function SignatureDetailsSection() {
const t = useTranslations('org_signature.details');
- const organisation = usePolicyStore((s) => s.policy.organisation) ?? DEFAULT_ORGANISATION_POLICY;
const details = useSettingsStore((s) => s.signatureDetails);
const updateSetting = useSettingsStore((s) => s.updateSetting);
const primary = useIdentityStore((s) => s.identities[0]);
const setup = useOrgSignatureSetup(primary);
-
- const used = new Set([
- ...signatureFieldsToAsk(organisation.signature),
- ...signatureFieldsUsed(organisation.disclaimer),
- ]);
- const fields = (['title', 'department', 'phone', 'mobile'] as const).filter((field) => used.has(field));
+ const fields = useFieldsToAsk();
if (fields.length === 0) return null;
- const preview = setup.signature
- ? setup.signature.html
- ? { html: sanitizeSignatureHtmlForDisplay(setup.signature.html) }
- : { text: getPlainTextSignature({ textSignature: setup.signature.text }) }
- : null;
+ const sent = effectiveSignatureIdentity(primary, primary, setup.signature, setup.restriction);
+ const preview = sent?.htmlSignature?.trim()
+ ? { html: sanitizeSignatureHtmlForDisplay(sent.htmlSignature) }
+ : sent?.textSignature?.trim()
+ ? { text: getPlainTextSignature(sent) }
+ : null;
return (