feat(260929-if2): Erinnerung zusaetzlich per E-Mail, Doku und Aenderungsliste
- E-Mail-Planer: Anspruch vor dem Senden (genau eine Mail je Faelligkeit, hoechstens 3 Versuche), Systemlesen nur fuer die Kandidatenabfrage - MailService.sendReminderEmail (Berliner Zeit, nur Text), GET /reminders/email-status, Haken im Formular mit Erklaerung - Zugriffsklassifikation und Erlaubnisliste fuer forSystem nachgezogen, Aenderungsliste und Anwenderanleitung Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -7,6 +7,7 @@ import { requestBrowserPermissionOnce } from '@/lib/reminder-notify';
|
||||
import { defaultNewReminderInputs, isoToLocalInputs, localInputsToIso } from '@/lib/reminder-time';
|
||||
import {
|
||||
type Reminder,
|
||||
type ReminderEmailStatus,
|
||||
ReminderRequestError,
|
||||
createReminder,
|
||||
updateReminder,
|
||||
@@ -15,6 +16,8 @@ import {
|
||||
interface ReminderFormModalProps {
|
||||
/** `null` = neue Erinnerung anlegen, sonst diese (noch nicht faellige) bearbeiten. */
|
||||
reminder: Reminder | null;
|
||||
/** Ergebnis von `GET /reminders/email-status`; `null` = unbekannt oder fehlgeschlagen (gilt als nicht moeglich). */
|
||||
emailStatus: ReminderEmailStatus | null;
|
||||
onClose: () => void;
|
||||
/** Nach erfolgreichem Speichern (die Kachel laedt neu und meldet die Aenderung). */
|
||||
onSaved: () => void;
|
||||
@@ -42,7 +45,13 @@ const DESCRIPTION_MAX = 2000;
|
||||
* eine Nutzerhandlung, die der Browser fuer die Frage akzeptiert. Nie beim
|
||||
* Laden der Seite.
|
||||
*/
|
||||
export function ReminderFormModal({ reminder, onClose, onSaved, onStale }: ReminderFormModalProps) {
|
||||
export function ReminderFormModal({
|
||||
reminder,
|
||||
emailStatus,
|
||||
onClose,
|
||||
onSaved,
|
||||
onStale,
|
||||
}: ReminderFormModalProps) {
|
||||
const t = useTranslations('widgets');
|
||||
const initial = useRef(
|
||||
reminder ? isoToLocalInputs(reminder.dueAt) : defaultNewReminderInputs(new Date()),
|
||||
@@ -51,6 +60,7 @@ export function ReminderFormModal({ reminder, onClose, onSaved, onStale }: Remin
|
||||
const [time, setTime] = useState(initial.time);
|
||||
const [title, setTitle] = useState(reminder?.title ?? '');
|
||||
const [description, setDescription] = useState(reminder?.description ?? '');
|
||||
const [emailEnabled, setEmailEnabled] = useState(reminder?.emailEnabled ?? false);
|
||||
const [saving, setSaving] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const titleRef = useRef<HTMLInputElement>(null);
|
||||
@@ -84,7 +94,16 @@ export function ReminderFormModal({ reminder, onClose, onSaved, onStale }: Remin
|
||||
|
||||
setSaving(true);
|
||||
try {
|
||||
const input = { title: trimmedTitle, description: description.trim(), dueAt };
|
||||
// `emailEnabled` nur senden, wenn es sich aendert oder beim Anlegen `true`
|
||||
// ist: ein Server, der E-Mail nicht mehr anbietet, lehnt `true` mit 400
|
||||
// ab — eine unveraenderte Alt-Einstellung darf das Speichern nicht kippen.
|
||||
const emailChanged = reminder ? emailEnabled !== reminder.emailEnabled : emailEnabled;
|
||||
const input = {
|
||||
title: trimmedTitle,
|
||||
description: description.trim(),
|
||||
dueAt,
|
||||
...(emailChanged ? { emailEnabled } : {}),
|
||||
};
|
||||
if (reminder) {
|
||||
await updateReminder(reminder.id, input);
|
||||
} else {
|
||||
@@ -105,6 +124,17 @@ export function ReminderFormModal({ reminder, onClose, onSaved, onStale }: Remin
|
||||
}
|
||||
};
|
||||
|
||||
// E-Mail moeglich: Versand eingerichtet UND Adresse im Konto. Unbekannt gilt
|
||||
// als nicht moeglich. Ein bereits angehaktes Feld bleibt bedienbar, damit man
|
||||
// es wieder abwaehlen kann.
|
||||
const emailAvailable = emailStatus?.smtpConfigured === true && emailStatus.hasEmail;
|
||||
const emailHint =
|
||||
emailStatus === null || emailAvailable
|
||||
? null
|
||||
: !emailStatus.smtpConfigured
|
||||
? t('reminder.emailNoSmtp')
|
||||
: t('reminder.emailNoAddress');
|
||||
|
||||
if (typeof document === 'undefined') return null;
|
||||
|
||||
return createPortal(
|
||||
@@ -175,6 +205,24 @@ export function ReminderFormModal({ reminder, onClose, onSaved, onStale }: Remin
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-1">
|
||||
<label className="flex items-center gap-2 text-sm text-foreground">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={emailEnabled}
|
||||
disabled={!emailAvailable && !emailEnabled}
|
||||
onChange={(e) => setEmailEnabled(e.target.checked)}
|
||||
aria-describedby={emailHint ? 'reminder-email-hint' : undefined}
|
||||
/>
|
||||
{t('reminder.emailLabel')}
|
||||
</label>
|
||||
{emailHint && (
|
||||
<p id="reminder-email-hint" className="text-xs text-muted-foreground">
|
||||
{emailHint}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{error && (
|
||||
<div
|
||||
role="alert"
|
||||
|
||||
Reference in New Issue
Block a user