diff --git a/apps/studio/components/interfaces/Auth/EmailTemplates/EmailTemplates.flowVariants.test.ts b/apps/studio/components/interfaces/Auth/EmailTemplates/EmailTemplates.flowVariants.test.ts index 58a477dac22..7878b6a4975 100644 --- a/apps/studio/components/interfaces/Auth/EmailTemplates/EmailTemplates.flowVariants.test.ts +++ b/apps/studio/components/interfaces/Auth/EmailTemplates/EmailTemplates.flowVariants.test.ts @@ -1,6 +1,10 @@ import { describe, expect, it } from 'vitest' -import { getTemplateFlowVariant, supportsTemplateFlowPicker } from './EmailTemplates.flowVariants' +import { + getTemplateFlowVariant, + inferTemplateFlowMode, + supportsTemplateFlowPicker, +} from './EmailTemplates.flowVariants' describe('EmailTemplates.flowVariants', () => { it('supports flow picker on the four auth templates only', () => { @@ -28,4 +32,11 @@ describe('EmailTemplates.flowVariants', () => { expect(both.body).toContain('{{ .ConfirmationURL }}') expect(both.body).toContain('Alternatively, enter this code: {{ .Token }}') }) + + it('infers the saved flow mode from subject and body', () => { + const otp = getTemplateFlowVariant('MAGIC_LINK', 'otp') + + expect(inferTemplateFlowMode('MAGIC_LINK', otp.subject, otp.body)).toBe('otp') + expect(inferTemplateFlowMode('MAGIC_LINK', 'Custom subject', '

Custom body

')).toBeNull() + }) }) diff --git a/apps/studio/components/interfaces/Auth/EmailTemplates/EmailTemplates.flowVariants.ts b/apps/studio/components/interfaces/Auth/EmailTemplates/EmailTemplates.flowVariants.ts index 93d4620c96c..597cbb3fc11 100644 --- a/apps/studio/components/interfaces/Auth/EmailTemplates/EmailTemplates.flowVariants.ts +++ b/apps/studio/components/interfaces/Auth/EmailTemplates/EmailTemplates.flowVariants.ts @@ -21,8 +21,8 @@ export const TEMPLATE_FLOW_MODE_OPTIONS: { }[] = [ { value: 'link', - label: 'Confirmation link', - helperText: 'Users click a link in the email.', + label: 'Link', + helperText: 'Users click a confirmation link in the email.', }, { value: 'otp', @@ -122,3 +122,19 @@ export const getTemplateFlowVariant = ( ): TemplateFlowVariant => { return TEMPLATE_FLOW_VARIANTS[templateId][mode] } + +export const inferTemplateFlowMode = ( + templateId: FlowPickerTemplateId, + subject: string, + body: string +): TemplateFlowMode | null => { + const variants = TEMPLATE_FLOW_VARIANTS[templateId] + + for (const mode of ['link', 'otp', 'both'] as const) { + if (variants[mode].subject === subject && variants[mode].body === body) { + return mode + } + } + + return null +} diff --git a/apps/studio/components/interfaces/Auth/EmailTemplates/TemplateEditor.tsx b/apps/studio/components/interfaces/Auth/EmailTemplates/TemplateEditor.tsx index a496c6e7209..d6f0af440fc 100644 --- a/apps/studio/components/interfaces/Auth/EmailTemplates/TemplateEditor.tsx +++ b/apps/studio/components/interfaces/Auth/EmailTemplates/TemplateEditor.tsx @@ -25,6 +25,7 @@ import z from 'zod' import { getTemplateFlowVariant, + inferTemplateFlowMode, supportsTemplateFlowPicker, type TemplateFlowMode, } from './EmailTemplates.flowVariants' @@ -102,6 +103,7 @@ export const TemplateEditor = ({ ) const canEdit = canUpdateConfig && !isReadOnly const supportsFlowPicker = showFlowPicker && supportsTemplateFlowPicker(template.id) + const canSaveTemplate = canUpdateConfig && (canEdit || supportsFlowPicker) const { id, properties } = template const editorRef = useRef(null) @@ -157,7 +159,7 @@ export const TemplateEditor = ({ const onSubmit = (values: z.infer) => { if (!projectRef) return console.error('Project ref is required') - if (!canEdit) return + if (!canSaveTemplate) return setIsSavingTemplate(true) @@ -216,10 +218,10 @@ export const TemplateEditor = ({ (subjectSlug !== undefined && authConfig?.MAILER_SUBJECTS_CUSTOM_CONTENTS?.[subjectSlug] === true) const hasFormChanges = JSON.stringify(formValues) !== JSON.stringify(baselineValues) - const hasChanges = supportsFlowPicker ? false : hasFormChanges || baselineBodyValue !== bodyValue + const hasChanges = hasFormChanges || baselineBodyValue !== bodyValue const saveChangesTooltip = !canUpdateConfig ? 'You need additional permissions to edit templates' - : isReadOnly + : !canSaveTemplate ? 'Set up custom SMTP to edit and save templates' : !hasChanges ? 'Make a change before saving' @@ -267,18 +269,38 @@ export const TemplateEditor = ({ [form, subjectSlug, template.id] ) - useEffect(() => { - if (!supportsFlowPicker) return - applyFlowVariant(flowMode) - }, [applyFlowVariant, flowMode, supportsFlowPicker]) + const handleFlowModeChange = (mode: TemplateFlowMode) => { + setFlowMode(mode) + applyFlowVariant(mode) + } + + const resetFlowModeFromSavedTemplate = useCallback(() => { + if (!supportsFlowPicker || !supportsTemplateFlowPicker(template.id) || !subjectSlug) return + + const savedSubject = (authConfig?.[subjectSlug] as string | undefined) ?? '' + const savedBody = (authConfig?.[messageSlug] as string | undefined) ?? '' + + setFlowMode(inferTemplateFlowMode(template.id, savedSubject, savedBody) ?? 'link') + }, [authConfig, messageSlug, subjectSlug, supportsFlowPicker, template.id]) // Update form values when authConfig changes useEffect(() => { - if (authConfig && !supportsFlowPicker) { - form.reset(getFormValuesFromConfig(authConfig)) - setBodyValue((authConfig && authConfig[messageSlug]) ?? '') + if (!authConfig) return + + form.reset(getFormValuesFromConfig(authConfig)) + setBodyValue((authConfig && authConfig[messageSlug]) ?? '') + + if (supportsFlowPicker) { + resetFlowModeFromSavedTemplate() } - }, [authConfig, getFormValuesFromConfig, messageSlug, form, supportsFlowPicker]) + }, [ + authConfig, + getFormValuesFromConfig, + messageSlug, + form, + supportsFlowPicker, + resetFlowModeFromSavedTemplate, + ]) useEffect(() => { if (projectRef && id && !!authConfig) { @@ -313,7 +335,7 @@ export const TemplateEditor = ({
{supportsFlowPicker && ( - + )} @@ -484,6 +506,17 @@ export const TemplateEditor = ({ setBodyValue((config && config[messageSlug]) ?? '') setValidationResult(undefined) setHasUnsavedChanges(false) + if ( + supportsFlowPicker && + supportsTemplateFlowPicker(template.id) && + subjectSlug + ) { + const savedSubject = (config[subjectSlug] as string | undefined) ?? '' + const savedBody = (config[messageSlug] as string | undefined) ?? '' + setFlowMode( + inferTemplateFlowMode(template.id, savedSubject, savedBody) ?? 'link' + ) + } }} /> )} @@ -494,7 +527,8 @@ export const TemplateEditor = ({ type="button" onClick={() => { form.reset(INITIAL_VALUES) - setBodyValue((authConfig && authConfig[messageSlug]) ?? '') + setBodyValue(baselineBodyValue) + resetFlowModeFromSavedTemplate() setHasUnsavedChanges(false) }} > @@ -504,7 +538,7 @@ export const TemplateEditor = ({ diff --git a/apps/studio/components/interfaces/Auth/EmailTemplates/TemplateFlowPicker.tsx b/apps/studio/components/interfaces/Auth/EmailTemplates/TemplateFlowPicker.tsx index ac2dc147c6e..b80e2633533 100644 --- a/apps/studio/components/interfaces/Auth/EmailTemplates/TemplateFlowPicker.tsx +++ b/apps/studio/components/interfaces/Auth/EmailTemplates/TemplateFlowPicker.tsx @@ -15,7 +15,7 @@ export const TemplateFlowPicker = ({ value, onValueChange }: TemplateFlowPickerP