From a338e3b67e9a09b4718922eebb0e607f0d64fe67 Mon Sep 17 00:00:00 2001 From: thomasbeaudry Date: Tue, 13 Oct 2026 23:59:10 -0400 Subject: [PATCH 1/2] fix(web): scope email template languages to active languages and add Spanish defaults Email template editors (new-user and group assignment) showed all three languages regardless of which were enabled in the instance settings. This scoped the language picker and validation to activeLanguages from the setup state, so disabled languages no longer appear. Also added the missing Spanish translations to both default email templates and alphabetized the language options on the settings page. Co-Authored-By: Claude Opus 4.6 --- .../EmailTemplateEditor.tsx | 4 +++ .../CreateTemplateForm.tsx | 15 ++++++-- .../EditTemplateDialog.tsx | 6 ++++ .../GroupEmailTemplates.tsx | 11 +++++- .../ViewDefaultTemplateDialog.tsx | 10 ++++-- .../__tests__/CreateTemplateForm.test.tsx | 7 ++-- .../__tests__/GroupEmailTemplates.test.tsx | 4 +++ .../components/MailSettings/MailSettings.tsx | 5 ++- .../MailSettings/NewUserTemplateSection.tsx | 14 ++++++-- .../__tests__/MailSettings.test.tsx | 2 +- .../__tests__/NewUserTemplateSection.test.tsx | 29 ++++++++++++--- .../routes/_app/admin/__tests__/mail.test.tsx | 4 +++ apps/web/src/routes/_app/admin/mail.tsx | 8 ++++- apps/web/src/routes/_app/admin/settings.tsx | 36 ++++++++++--------- .../schemas/src/mail/__tests__/mail.test.ts | 4 +-- packages/schemas/src/mail/mail.ts | 29 +++++++++++++++ 16 files changed, 148 insertions(+), 40 deletions(-) diff --git a/apps/web/src/components/EmailTemplateEditor/EmailTemplateEditor.tsx b/apps/web/src/components/EmailTemplateEditor/EmailTemplateEditor.tsx index 9b4731505..e342b4645 100644 --- a/apps/web/src/components/EmailTemplateEditor/EmailTemplateEditor.tsx +++ b/apps/web/src/components/EmailTemplateEditor/EmailTemplateEditor.tsx @@ -11,6 +11,8 @@ export type EmailTemplateEditorProps = { error?: string; /** Namespaces the control ids and test ids, so two editors can coexist on one page. */ idPrefix: string; + /** The languages to offer in the language picker; defaults to all supported languages. */ + languages?: readonly Language[]; /** Omit to show the template read-only. */ onChange?: (template: MailTemplate) => void; template: MailTemplate; @@ -22,6 +24,7 @@ export type EmailTemplateEditorProps = { export const EmailTemplateEditor = ({ error, idPrefix, + languages, onChange, template, variables = [] @@ -56,6 +59,7 @@ export const EmailTemplateEditor = ({ diff --git a/apps/web/src/components/GroupEmailTemplates/CreateTemplateForm.tsx b/apps/web/src/components/GroupEmailTemplates/CreateTemplateForm.tsx index f5d49cd72..e49ff33fa 100644 --- a/apps/web/src/components/GroupEmailTemplates/CreateTemplateForm.tsx +++ b/apps/web/src/components/GroupEmailTemplates/CreateTemplateForm.tsx @@ -2,15 +2,17 @@ import React from 'react'; import { Button, Dialog, Heading, Input, Label } from '@douglasneuroinformatics/libui/components'; import { useNotificationsStore, useTranslation } from '@douglasneuroinformatics/libui/hooks'; +import type { Language } from '@opendatacapture/schemas/core'; import type { MailTemplate } from '@opendatacapture/schemas/mail'; import { EmailTemplateEditor } from '@/components/EmailTemplateEditor'; import { ASSIGNMENT_TEMPLATE_VARS } from '@/utils/email-template'; -import { LANGUAGE_LABELS, LANGUAGES } from '@/utils/language'; +import { LANGUAGE_LABELS } from '@/utils/language'; const emptyTemplate = (): MailTemplate => ({ body: {}, subject: {} }); export type CreateTemplateFormProps = { + activeLanguages: readonly Language[]; isPending: boolean; /** Resolves true once the template is saved; the form clears only then. */ onCreate: (name: string, template: MailTemplate) => Promise; @@ -18,7 +20,13 @@ export type CreateTemplateFormProps = { validateName: (name: string) => string | undefined; }; -export const CreateTemplateForm = ({ isPending, onCreate, validateContent, validateName }: CreateTemplateFormProps) => { +export const CreateTemplateForm = ({ + activeLanguages, + isPending, + onCreate, + validateContent, + validateName +}: CreateTemplateFormProps) => { const { t } = useTranslation(); const addNotification = useNotificationsStore((store) => store.addNotification); const formRef = React.useRef(null); @@ -27,7 +35,7 @@ export const CreateTemplateForm = ({ isPending, onCreate, validateContent, valid const [isConfirmingMissingTranslations, setIsConfirmingMissingTranslations] = React.useState(false); const contentError = validateContent(draft); - const missingLanguages = LANGUAGES.filter((code) => !draft.body[code]?.trim()); + const missingLanguages = activeLanguages.filter((code) => !draft.body[code]?.trim()); const create = async () => { if (!(await onCreate(name.trim(), draft))) { @@ -76,6 +84,7 @@ export const CreateTemplateForm = ({ isPending, onCreate, validateContent, valid void; onSave: (template: GroupEmailTemplate) => void; @@ -18,6 +20,7 @@ type EditTemplateFormProps = { }; const EditTemplateForm = ({ + activeLanguages, isPending, onCancel, onSave, @@ -62,6 +65,7 @@ const EditTemplateForm = ({ void; onSave: (template: GroupEmailTemplate) => void; @@ -104,6 +109,7 @@ export const EditTemplateDialog = ({ template, ...props }: EditTemplateDialogPro {/* Mounted only while open, so the draft resets between templates. */} {template && ( ( export const GroupEmailTemplates = () => { const { t } = useTranslation(); const addNotification = useNotificationsStore((store) => store.addNotification); + const { data: setupState } = useSetupStateQuery(); + const activeLanguages = setupState.activeLanguages; const currentGroup = useAppStore((store) => store.currentGroup); const groupId = currentGroup?.id; const queryClient = useQueryClient(); @@ -280,15 +283,21 @@ export const GroupEmailTemplates = () => { validateName(name)} onCreate={handleCreate} /> - + void; open: boolean; }; /** Read-only view of the message sent when no custom template is active. */ -export const ViewDefaultTemplateDialog = ({ onOpenChange, open }: ViewDefaultTemplateDialogProps) => { +export const ViewDefaultTemplateDialog = ({ activeLanguages, onOpenChange, open }: ViewDefaultTemplateDialogProps) => { const { t } = useTranslation(); return ( @@ -31,7 +33,11 @@ export const ViewDefaultTemplateDialog = ({ onOpenChange, open }: ViewDefaultTem })} - +