import { useEffect, useMemo, useState } from "react"; import { Mail, Save } from "lucide-react"; import { FormGrid, ContentGrid, ActionBlockerHint, Button, Card, DismissibleAlert, DocumentationHelpLink, FormField, ReferenceMultiSelect, ToggleSwitch, hasScope, i18nMessage, isViewSurfaceVisible, moduleViewSurfaceId, platformModuleReferenceProvider, useEffectiveView, usePlatformLanguage, usePlatformModules, useUnsavedDraftGuard, useViewSurfaces, type ApiSettings, type AuthInfo, type ReferenceOption } from "@govoplan/core-webui"; import { getNotificationPreferences, updateNotificationPreferences, type NotificationPreferences } from "../../api/notifications"; import { NOTIFICATIONS_BLOCKER_LABELS, NOTIFICATIONS_DELIVERY_DOCUMENTATION, NOTIFICATIONS_DOCUMENTATION, NOTIFICATIONS_I18N } from "./interfacePatterns"; type Draft = Pick & { muted_source_modules: string[]; }; const DEFAULT_DRAFT: Draft = { show_unread_badge: true, email_enabled: false, email_digest_enabled: false, muted_source_modules: [] }; export default function NotificationSettingsPanel({ settings, auth }: { settings: ApiSettings; auth: AuthInfo }) { const modules = usePlatformModules(); const effectiveView = useEffectiveView(); const viewSurfaces = useViewSurfaces(); const { translateText } = usePlatformLanguage(); const [loaded, setLoaded] = useState(null); const [draft, setDraft] = useState(DEFAULT_DRAFT); const [loading, setLoading] = useState(true); const [saving, setSaving] = useState(false); const [message, setMessage] = useState(""); const [messageTone, setMessageTone] = useState<"success" | "warning">("success"); const canWrite = hasScope(auth, "notifications:notification:write"); const mailAvailable = modules.some((module) => module.id === "mail"); const dirty = useMemo(() => { if (!loaded) return false; return ( draft.show_unread_badge !== loaded.show_unread_badge || draft.email_enabled !== loaded.email_enabled || draft.email_digest_enabled !== loaded.email_digest_enabled || draft.muted_source_modules.join(",") !== loaded.muted_source_modules.join(",") ); }, [draft, loaded]); const moduleOptions = useMemo( () => modules.map((module) => { const visible = isViewSurfaceVisible( effectiveView, moduleViewSurfaceId(module.id), viewSurfaces ); return { value: module.id, label: translateText(module.label), description: [ module.id, i18nMessage("i18n:govoplan-notifications.version_value", { value0: module.version }), visible ? null : translateText("i18n:govoplan-notifications.hidden_by_view") ].filter(Boolean).join(" ยท "), kind: "module", availability: visible ? "available" : "unavailable", disabled: !visible, sourceModule: "core", provenance: { version: module.version, visibleInActiveView: visible } }; }), [effectiveView, modules, translateText, viewSurfaces] ); const moduleProvider = useMemo( () => platformModuleReferenceProvider(settings, moduleOptions), [moduleOptions, settings] ); function resetDraft() { if (!loaded) return; setDraft({ show_unread_badge: loaded.show_unread_badge, email_enabled: loaded.email_enabled, email_digest_enabled: loaded.email_digest_enabled, muted_source_modules: loaded.muted_source_modules }); } useUnsavedDraftGuard({ dirty, onSave: savePreferences, onDiscard: resetDraft }); useEffect(() => { void loadPreferences(); }, [auth.user.id, settings.accessToken, settings.apiBaseUrl, settings.apiKey]); async function loadPreferences() { setLoading(true); setMessage(""); try { const preferences = await getNotificationPreferences(settings); setLoaded(preferences); setDraft({ show_unread_badge: preferences.show_unread_badge, email_enabled: preferences.email_enabled, email_digest_enabled: preferences.email_digest_enabled, muted_source_modules: preferences.muted_source_modules }); } catch (error) { setMessageTone("warning"); setMessage(error instanceof Error ? error.message : translateText("i18n:govoplan-notifications.preferences_load_failed")); } finally { setLoading(false); } } async function savePreferences(): Promise { if (!canWrite) return false; setSaving(true); setMessage(""); try { const next = await updateNotificationPreferences(settings, { show_unread_badge: draft.show_unread_badge, email_enabled: draft.email_enabled, email_digest_enabled: draft.email_digest_enabled, muted_source_modules: draft.muted_source_modules }); setLoaded(next); setDraft({ show_unread_badge: next.show_unread_badge, email_enabled: next.email_enabled, email_digest_enabled: next.email_digest_enabled, muted_source_modules: next.muted_source_modules }); window.dispatchEvent(new CustomEvent("govoplan:notifications-changed")); setMessageTone("success"); setMessage("i18n:govoplan-notifications.preferences_saved"); return true; } catch (error) { setMessageTone("warning"); setMessage(error instanceof Error ? error.message : translateText("i18n:govoplan-notifications.preferences_save_failed")); return false; } finally { setSaving(false); } } const saveDisabledReason = loading ? NOTIFICATIONS_I18N.loading : saving ? NOTIFICATIONS_I18N.saving : !canWrite ? NOTIFICATIONS_I18N.writePermissionRequired : !dirty ? NOTIFICATIONS_I18N.noChanges : undefined; const preferenceControlsDisabled = loading || saving || !canWrite; const emailToggleDisabled = preferenceControlsDisabled || (!mailAvailable && !draft.email_enabled); const digestToggleDisabled = preferenceControlsDisabled || !mailAvailable || !draft.email_enabled; return ( {!canWrite ? ( ) : null} }> setDraft((current) => ({ ...current, show_unread_badge: value }))} /> setDraft((current) => ({ ...current, muted_source_modules })) } provider={moduleProvider} aria-label="i18n:govoplan-notifications.muted_sources" placeholder="i18n:govoplan-notifications.add_source_module" emptyText="i18n:govoplan-notifications.no_visible_modules" disabled={preferenceControlsDisabled} />
{message ? {message} : null}
}>
i18n:govoplan-notifications.email_notifications
{!mailAvailable ? ( ) : null} setDraft((current) => ({ ...current, email_enabled: value, email_digest_enabled: value ? current.email_digest_enabled : false }))} /> setDraft((current) => ({ ...current, email_digest_enabled: value }))} />
); }