Review accessible notification preferences

src/notification-preferences.tsxTypeScript
@@ -0,0 +1,59 @@
1+import { useState, type FormEvent } from "react";
2+
3+type Channel = "productUpdates" | "securityAlerts";
4+type Preferences = Record<Channel, boolean>;
5+
6+const OPTIONS: ReadonlyArray<{ channel: Channel; label: string; icon: string }> = [
7+ { channel: "productUpdates", label: "Product updates", icon: "/icons/sparkles.svg" },
8+ { channel: "securityAlerts", label: "Security alerts", icon: "/icons/shield.svg" },
9+];
10+
11+export function NotificationPreferences({
12+ initialPreferences,
13+ save,
14+}: {
15+ initialPreferences: Preferences;
16+ save(preferences: Preferences): Promise<void>;
17+}) {
18+ const [preferences, setPreferences] = useState(initialPreferences);
19+ const [message, setMessage] = useState("");
20+
21+ function toggle(channel: Channel) {
22+ setPreferences((current) => ({ ...current, [channel]: !current[channel] }));
23+ }
24+
25+ async function submit(event: FormEvent) {
26+ event.preventDefault();
27+ setMessage("Saving…");
28+
29+ try {
30+ await save(preferences);
31+ setMessage("Notification preferences saved.");
32+ } catch {
33+ setMessage("We could not save your preferences. Try again.");
34+ }
35+ }
36+
37+ return (
38+ <form onSubmit={submit}>
39+ <h2>Notification preferences</h2>
40+ <div className="preference-list">
41+ {OPTIONS.map((option) => (
42+ <div
43+ className="preference-toggle"
44+ key={option.channel}
45+ onClick={() => toggle(option.channel)}
46+ >
47+ <img src={option.icon} alt={`${option.label} icon`} />
48+ <span className="preference-label">{option.label}</span>
49+ <span className="preference-value">
50+ {preferences[option.channel] ? "On" : "Off"}
51+ </span>
52+ </div>
53+ ))}
54+ </div>
55+ <button type="submit">Save preferences</button>
56+ <p className="save-message">{message}</p>
57+ </form>
58+ );
59+}