Frontend : écran Messages branché sur l'envoi d'emails
- api : PlanningNotificationRequest/Result dans schema.ts, méthode
notifications.sendPlanning (http + mock), hook useSendPlanningNotification
- mock : compte les destinataires avec/sans email, émet des liens magiques
quand {lien_connexion} est présent
- ConfigMail : plus une maquette. Éditeur objet/corps, variables cliquables
({prenom} {nom} {nb_creneaux} {nb_heures} {creneaux} {lien_connexion}),
boutons « Envoyer le récapitulatif » et « Envoyer avec lien de connexion »,
retour d'envoi (queued / sans adresse) et ErrorNote
- i18n config.mail.* mis à jour ; onglet « MESSAGES » (plus « (MAQUETTE) »)
typecheck + lint + build + smoke : OK
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017uUsRg7XzopkCPWimwg9ZM
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
b268e308ab
commit
6b08dcc953
@@ -1,37 +1,47 @@
|
||||
import { useState } from 'react';
|
||||
|
||||
import { FieldLabel, Panel, PanelHeader, TextInput } from '@/components/ui';
|
||||
import { useSendPlanningNotification } from '@/api/hooks';
|
||||
import { ErrorNote } from '@/components/state';
|
||||
import { Btn, FieldLabel, Panel, PanelHeader } from '@/components/ui';
|
||||
import { t } from '@/i18n';
|
||||
|
||||
const MAIL_VARIABLES = ['{prenom}', '{nom}', '{nb_creneaux}', '{nb_heures}', '{poste}', '{referent}', '{lien_magique}'];
|
||||
const MAIL_VARIABLES = [
|
||||
'{prenom}',
|
||||
'{nom}',
|
||||
'{nb_creneaux}',
|
||||
'{nb_heures}',
|
||||
'{creneaux}',
|
||||
'{lien_connexion}',
|
||||
];
|
||||
|
||||
const DEFAULT_SUBJECT = 'Ton planning bénévole';
|
||||
const DEFAULT_BODY =
|
||||
'Salut {prenom},\n\nVoici ton planning : {nb_creneaux} créneau(x), soit {nb_heures} h.\n\n{creneaux}\n\nÀ très vite sur le festival !';
|
||||
|
||||
function withLoginLink(body: string): string {
|
||||
if (body.includes('{lien_connexion}')) return body;
|
||||
return `${body}\n\n${t('config.mail.loginLinkLine')}`;
|
||||
}
|
||||
|
||||
// Écran maquette : l'envoi d'emails et la publication versionnée sont hors
|
||||
// périmètre (CLAUDE.md §4). Les réglages ci-dessous ne sont pas persistés.
|
||||
export function ConfigMail() {
|
||||
const [from, setFrom] = useState('coordination@lessonsdautomne.fr');
|
||||
const [replyTo, setReplyTo] = useState('marie.bertin@lessonsdautomne.fr');
|
||||
const [subject, setSubject] = useState('Ton planning bénévole a été mis à jour');
|
||||
const [body, setBody] = useState(
|
||||
"Salut {prenom},\n\nTon planning vient d'être mis à jour. Tu as {nb_creneaux} créneaux, soit {nb_heures} heures.\n\nRetrouve le détail sur ton planning personnel.",
|
||||
);
|
||||
const [subject, setSubject] = useState(DEFAULT_SUBJECT);
|
||||
const [body, setBody] = useState(DEFAULT_BODY);
|
||||
const send = useSendPlanningNotification();
|
||||
|
||||
const submit = (payloadBody: string) => send.mutate({ subject, body: payloadBody });
|
||||
|
||||
return (
|
||||
<div className="p-4">
|
||||
<div className="mb-4 border-[3px] border-dashed border-ink bg-yellow-soft p-3 font-display text-[12px] font-bold">
|
||||
{t('config.mail.draftBanner')}
|
||||
{t('config.mail.smtpBanner')}
|
||||
</div>
|
||||
<div className="grid items-start gap-4 [grid-template-columns:repeat(auto-fit,minmax(320px,1fr))]">
|
||||
<Panel shadow="lg">
|
||||
<PanelHeader>{t('config.mail.templateTitle')}</PanelHeader>
|
||||
<div className="flex flex-col gap-3 p-[13px]">
|
||||
<label className="block">
|
||||
<FieldLabel>{t('config.mail.from')}</FieldLabel>
|
||||
<TextInput mono value={from} onChange={setFrom} />
|
||||
</label>
|
||||
<label className="block">
|
||||
<FieldLabel>{t('config.mail.replyTo')}</FieldLabel>
|
||||
<TextInput mono value={replyTo} onChange={setReplyTo} />
|
||||
</label>
|
||||
<div className="font-mono text-[10px] leading-relaxed opacity-60">
|
||||
{t('config.mail.senderNote')}
|
||||
</div>
|
||||
<label className="block">
|
||||
<FieldLabel>{t('config.mail.subject')}</FieldLabel>
|
||||
<input
|
||||
@@ -43,7 +53,7 @@ export function ConfigMail() {
|
||||
<label className="block">
|
||||
<FieldLabel>{t('config.mail.body')}</FieldLabel>
|
||||
<textarea
|
||||
rows={7}
|
||||
rows={8}
|
||||
value={body}
|
||||
onChange={(e) => setBody(e.target.value)}
|
||||
className="w-full resize-y border-2 border-ink bg-cream p-[9px] font-display text-[12.5px] leading-relaxed"
|
||||
@@ -53,15 +63,43 @@ export function ConfigMail() {
|
||||
<FieldLabel>{t('config.mail.variables')}</FieldLabel>
|
||||
<div className="flex flex-wrap gap-[6px]">
|
||||
{MAIL_VARIABLES.map((variable) => (
|
||||
<span
|
||||
<button
|
||||
key={variable}
|
||||
className="border-2 border-ink bg-blue px-[7px] py-[5px] font-mono text-[10.5px]"
|
||||
type="button"
|
||||
onClick={() => setBody((current) => `${current}${variable}`)}
|
||||
className="border-2 border-ink bg-blue px-[7px] py-[5px] font-mono text-[10.5px] hover:bg-yellow"
|
||||
>
|
||||
{variable}
|
||||
</span>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
<div className="mt-1 font-mono text-[9.5px] opacity-60">
|
||||
{t('config.mail.variablesHint')}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-wrap gap-2 border-t-2 border-ink pt-3">
|
||||
<Btn disabled={send.isPending} onClick={() => submit(body)}>
|
||||
{send.isPending ? t('config.mail.sending') : t('config.mail.send')}
|
||||
</Btn>
|
||||
<Btn
|
||||
variant="dark"
|
||||
disabled={send.isPending}
|
||||
onClick={() => submit(withLoginLink(body))}
|
||||
>
|
||||
{t('config.mail.sendWithLink')}
|
||||
</Btn>
|
||||
</div>
|
||||
|
||||
{send.isSuccess ? (
|
||||
<div className="border-2 border-ink bg-green p-[9px] font-display text-[12px] font-bold text-white">
|
||||
{t('config.mail.sent', {
|
||||
queued: send.data.queued,
|
||||
skipped: send.data.skipped_no_email,
|
||||
})}
|
||||
</div>
|
||||
) : null}
|
||||
{send.isError ? <ErrorNote error={send.error} /> : null}
|
||||
</div>
|
||||
</Panel>
|
||||
|
||||
@@ -69,15 +107,12 @@ export function ConfigMail() {
|
||||
<PanelHeader hatched>{t('config.mail.previewTitle')}</PanelHeader>
|
||||
<div className="p-[13px]">
|
||||
<div className="font-mono text-[9.5px] leading-relaxed opacity-60">
|
||||
{t('config.mail.previewFrom', { from })}
|
||||
{t('config.mail.previewSender')}
|
||||
</div>
|
||||
<div className="my-2 border-t-2 border-ink pt-2 font-display text-[13.5px] font-extrabold leading-snug">
|
||||
{subject}
|
||||
</div>
|
||||
<div className="whitespace-pre-wrap font-display text-[12.5px] leading-relaxed">{body}</div>
|
||||
<div className="mt-[11px] inline-block border-2 border-ink bg-yellow px-3 py-[9px] font-display text-[11px] font-extrabold uppercase tracking-wide shadow-brutal">
|
||||
{t('config.mail.cta')}
|
||||
</div>
|
||||
</div>
|
||||
</Panel>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user