This commit is contained in:
Danny White committed 2026-06-24 15:41:29 -06:00
1 parent 0876c52637
commit 3f4e42abb4
4 files changed
+79 -18

No files matched your search

@@ -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', '<p>Custom body</p>')).toBeNull()
})
})
@@ -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
}
@@ -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<editor.IStandaloneCodeEditor | null>(null)
@@ -157,7 +159,7 @@ export const TemplateEditor = ({
const onSubmit = (values: z.infer<typeof template.validationSchema>) => {
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 = ({
<form onSubmit={form.handleSubmit(onSubmit)}>
{supportsFlowPicker && (
<CardContent className="border-b">
<TemplateFlowPicker value={flowMode} onValueChange={setFlowMode} />
<TemplateFlowPicker value={flowMode} onValueChange={handleFlowModeChange} />
</CardContent>
)}
<CardContent>
@@ -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 = ({
<ButtonTooltip
variant="primary"
type="submit"
disabled={!canEdit || isSavingTemplate || !hasChanges}
disabled={!canSaveTemplate || isSavingTemplate || !hasChanges}
loading={isSavingTemplate}
tooltip={{ content: { side: 'bottom', text: saveChangesTooltip } }}
>
@@ -15,7 +15,7 @@ export const TemplateFlowPicker = ({ value, onValueChange }: TemplateFlowPickerP
<FormItemLayout
isReactForm={false}
layout="vertical"
label="Authentication method"
label="Confirmation"
description={selectedOption?.helperText}
>
<Select