import { useEffect, useState } from 'react'; import Tabs from 'antd/es/tabs'; import Spin from 'antd/es/spin'; import Typography from 'antd/es/typography'; import message from 'antd/es/message'; import { EmailTemplate, fetchEmailTemplates } from './emailTemplatesApi'; import EmailTemplateEditor from './EmailTemplateEditor'; const { Text } = Typography; export default function Emails() { const [templates, setTemplates] = useState([]); const [loading, setLoading] = useState(true); useEffect(() => { fetchEmailTemplates() .then(setTemplates) // Reported rather than swallowed: an empty list would otherwise read as // "there are no templates" instead of "they could not be loaded". .catch(() => message.error('Could not load the email templates')) .finally(() => setLoading(false)); }, []); // Replaces the one that changed so the customised marker and the Restore // button reflect what the server now holds, without refetching the rest. function handleTemplateChanged(updated: EmailTemplate) { setTemplates(current => current.map(t => (t.key === updated.key ? { ...t, subject: updated.subject, body: updated.body } : t)) ); } return (
The wording customers receive. Leave one alone and it sends the built-in copy. {/* Spinner rather than an empty rail: with templates starting empty, the gap before the fetch lands reads as "there are no emails to edit". */} {loading ? (
) : ( /* A left rail rather than a strip across the top. Six labels wrapped on narrower displays, and stacking them leaves the editor the full width it needs for a markdown pane and a rendered preview side by side. */ ({ key: template.key, label: ( <> {template.label} {/* A dot, not a Tag — six tags down a rail stop it being scannable. aria-label keeps the word in the tab's accessible name, so the state is not colour-only. */} {(template.subject !== null || template.body !== null) && ( )} ), children: ( ) }))} /> )}
); }