import { PermissionAction } from '@supabase/shared-types/out/constants' import { useEffect, useMemo, useState } from 'react' import ReactMarkdown from 'react-markdown' import { toast } from 'sonner' import { useParams } from 'common' import CodeEditor from 'components/ui/CodeEditor/CodeEditor' import { FormActions } from 'components/ui/Forms/FormActions' import { FormSection, FormSectionContent, FormSectionLabel } from 'components/ui/Forms/FormSection' import InformationBox from 'components/ui/InformationBox' import { useAuthConfigUpdateMutation } from 'data/auth/auth-config-update-mutation' import { useCheckPermissions } from 'hooks/misc/useCheckPermissions' import type { FormSchema } from 'types' import { AlertTitle_Shadcn_, Alert_Shadcn_, Form, Input, Tabs } from 'ui' import { Code, Monitor, Info } from 'lucide-react' interface TemplateEditorProps { template: FormSchema authConfig: Record } const TemplateEditor = ({ template, authConfig }: TemplateEditorProps) => { const { ref: projectRef } = useParams() const { mutate: updateAuthConfig, isLoading: isUpdatingConfig } = useAuthConfigUpdateMutation({ onError: (error) => toast.error(`Failed to update email templates: ${error.message}`), }) const { id, properties } = template const formId = `auth-config-email-templates-${id}` const INITIAL_VALUES = useMemo(() => { const result: { [x: string]: string } = {} Object.keys(properties).forEach((key) => { result[key] = (authConfig && authConfig[key]) ?? '' }) return result }, [authConfig, properties]) const canUpdateConfig = useCheckPermissions(PermissionAction.UPDATE, 'custom_config_gotrue') const messageSlug = `MAILER_TEMPLATES_${id}_CONTENT` const messageProperty = properties[messageSlug] const [bodyValue, setBodyValue] = useState((authConfig && authConfig[messageSlug]) ?? '') const [hasUnsavedChanges, setHasUnsavedChanges] = useState(false) const onSubmit = (values: any, { resetForm }: any) => { const payload = { ...values } // Because the template content uses the code editor which is not a form component // its state is kept separately from the form state, hence why we manually inject it here delete payload[messageSlug] if (messageProperty) payload[messageSlug] = bodyValue updateAuthConfig( { projectRef: projectRef!, config: payload }, { onSuccess: () => { toast.success('Successfully updated settings') resetForm({ values: values, initialValues: values, }) setHasUnsavedChanges(false) // Reset the unsaved changes state }, } ) } useEffect(() => { const handleBeforeUnload = (e: BeforeUnloadEvent) => { if (hasUnsavedChanges) { e.preventDefault() e.returnValue = '' // deprecated, but older browsers still require this } } window.addEventListener('beforeunload', handleBeforeUnload) return () => { window.removeEventListener('beforeunload', handleBeforeUnload) } }, [hasUnsavedChanges]) return (
{({ resetForm, values, initialValues }: any) => { const message = (authConfig && authConfig[messageSlug]) ?? '' const hasChanges = JSON.stringify(values) !== JSON.stringify(initialValues) || message !== bodyValue return ( <> {Object.keys(properties).map((x: string) => { const property = properties[x] if (property.type === 'string') { return (
{property.description} ) : null } labelOptional={ property.descriptionOptional ? ( {property.descriptionOptional} ) : null } disabled={!canUpdateConfig} />
) } })}
{messageProperty && ( <>
{messageProperty.title} {messageProperty.description} ) } />
} label="Source">
{ setBodyValue(e ?? '') if (bodyValue !== e) { setHasUnsavedChanges(true) } }} options={{ wordWrap: 'off', contextmenu: false }} value={bodyValue} />
} label="Preview"> The preview may differ slightly from the actual rendering in the email client.