feat(admin): give the customer emails a tab of their own (#135)
The six email templates lived at the bottom of the Settings tab, under the cart-expiry card and inside a 720px wrapper. Finding them took knowing they were there — "Settings" reads as app configuration and the only thing visible on that tab was a 480px card about cart expiry. Reaching them, the editor was then crushed: EmailTemplateEditor splits a markdown pane and a rendered preview side by side, and 720px left each half under 350px, so the preview showed the email at a width nothing like how it will be read and the markdown toolbar wrapped. Emails is now its own tab, between Customers and Settings, with no width cap. Within it the email types are a left vertical rail rather than a strip across the top: six labels wrapped on narrower displays, and stacking them is what leaves the editor the width the split needs. Settings keeps the cart-expiry card and nothing else. The Default/Customised tag comes off the labels. Six antd tags stacked down a rail stop it being scannable, so a customised template gets a dot and the state in full moves into the editor beside the Restore default button that acts on it. The dot carries aria-label="Customised" so the word stays in the tab's accessible name and the state is not conveyed by a mark alone. Emails owns the fetch it inherited from Settings, and adds a Spin over it. templates starts empty, so the gap before the request lands would otherwise render an empty rail that reads as "there are no emails to edit". Closes #135
This commit is contained in:
@@ -5,19 +5,13 @@ import Button from 'antd/es/button';
|
||||
import Typography from 'antd/es/typography';
|
||||
import message from 'antd/es/message';
|
||||
import Card from 'antd/es/card';
|
||||
import Tabs from 'antd/es/tabs';
|
||||
import Tag from 'antd/es/tag';
|
||||
import Space from 'antd/es/space';
|
||||
import { fetchAdminSettings, updateAdminSettings } from './adminSettingsApi';
|
||||
import { EmailTemplate, fetchEmailTemplates } from './emailTemplatesApi';
|
||||
import EmailTemplateEditor from './EmailTemplateEditor';
|
||||
|
||||
const { Title, Text } = Typography;
|
||||
|
||||
export default function Settings() {
|
||||
const [form] = Form.useForm();
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [templates, setTemplates] = useState<EmailTemplate[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
fetchAdminSettings()
|
||||
@@ -29,22 +23,6 @@ export default function Settings() {
|
||||
.finally(() => setLoading(false));
|
||||
}, [form]);
|
||||
|
||||
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'));
|
||||
}, []);
|
||||
|
||||
// Replaces the one that changed so the Customised badge 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))
|
||||
);
|
||||
}
|
||||
|
||||
async function handleSave() {
|
||||
const values = await form.validateFields();
|
||||
await updateAdminSettings(values);
|
||||
@@ -52,7 +30,6 @@ export default function Settings() {
|
||||
}
|
||||
|
||||
return (
|
||||
<div style={{ maxWidth: 720 }}>
|
||||
<Card style={{ maxWidth: 480 }}>
|
||||
<Title level={4}>Cart Settings</Title>
|
||||
<Text type="secondary">
|
||||
@@ -65,33 +42,5 @@ export default function Settings() {
|
||||
<Button type="primary" onClick={handleSave} loading={loading}>Save</Button>
|
||||
</Form>
|
||||
</Card>
|
||||
|
||||
<Title level={4} style={{ marginTop: 32 }}>Customer emails</Title>
|
||||
<Text type="secondary">
|
||||
The wording customers receive. Leave one alone and it sends the built-in copy.
|
||||
</Text>
|
||||
{/* Tabs rather than a stacked column. With six templates the cart reminder
|
||||
sat below five editors, so which one you were editing was knowable only
|
||||
from a card title you had already scrolled past. The Default/Customised
|
||||
tag moves onto the tab label, so which templates have been changed is
|
||||
visible without opening each one. */}
|
||||
<Tabs
|
||||
style={{ marginTop: 16 }}
|
||||
items={templates.map(template => ({
|
||||
key: template.key,
|
||||
label: (
|
||||
<Space size={4}>
|
||||
{template.label}
|
||||
{template.subject !== null || template.body !== null
|
||||
? <Tag color="blue">Customised</Tag>
|
||||
: <Tag>Default</Tag>}
|
||||
</Space>
|
||||
),
|
||||
children: (
|
||||
<EmailTemplateEditor template={template} onChanged={handleTemplateChanged} />
|
||||
)
|
||||
}))}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user