"use client"; import { useEffect, useState } from 'react'; import { useTranslations } from 'next-intl'; import { useSettingsStore } from '@/stores/settings-store'; import { SettingsSection, SettingItem, ToggleSwitch, Select } from './settings-section'; import { playNotificationSound, NOTIFICATION_SOUNDS } from '@/lib/notification-sound'; import type { NotificationSoundChoice } from '@/lib/notification-sound'; import { Button } from '@/components/ui/button'; import { CheckCircle2, Lock, Volume2, XCircle } from 'lucide-react'; import { usePolicyStore } from '@/stores/policy-store'; import { useAuthStore } from '@/stores/auth-store'; import { ConfirmDialog } from '@/components/ui/confirm-dialog'; import { useConfirmDialog } from '@/hooks/use-confirm-dialog'; import { DEFAULT_RELAY_BASE_URL, WebPushUnsupportedError, disableWebPush, enableWebPush, isWebPushEnabled, isWebPushSupported, } from '@/lib/web-push'; type PushStatus = | { kind: 'idle' } | { kind: 'busy' } | { kind: 'enabled' } | { kind: 'unsupported' } | { kind: 'error'; message: string }; export function NotificationSettings() { const t = useTranslations('settings.notifications'); const { emailNotificationsEnabled, emailNotificationSound, notificationSoundChoice, calendarNotificationsEnabled, calendarNotificationSound, calendarInvitationParsingEnabled, updateSetting, } = useSettingsStore(); const { isSettingLocked, isSettingHidden } = usePolicyStore(); const adminPushRelayUrl = usePolicyStore((s) => s.policy.pushRelayUrl); const pushRelayLocked = usePolicyStore((s) => s.policy.pushRelayUrlLocked) === true; const client = useAuthStore((s) => s.client); const username = useAuthStore((s) => s.username); const { dialogProps: confirmDialogProps, confirm: confirmDialog } = useConfirmDialog(); const supported = typeof window !== 'undefined' && isWebPushSupported(); const adminUrl = (adminPushRelayUrl ?? '').trim(); const [relayUrl, setRelayUrl] = useState(adminUrl || DEFAULT_RELAY_BASE_URL); const [pushStatus, setPushStatus] = useState( supported ? { kind: 'idle' } : { kind: 'unsupported' }, ); // Pull the admin-configured URL into local state when policy loads/changes. // When locked, the admin value always wins; when only set (not locked), use // it as the initial default but let the user override. useEffect(() => { if (pushRelayLocked && adminUrl) { setRelayUrl(adminUrl); } else if (adminUrl) { setRelayUrl((current) => (current === DEFAULT_RELAY_BASE_URL ? adminUrl : current)); } }, [adminUrl, pushRelayLocked]); useEffect(() => { if (!supported) return; if (!client) return; const accountId = client.getAccountId(); if (!accountId) return; void (async () => { const enabled = await isWebPushEnabled(accountId); setPushStatus(enabled ? { kind: 'enabled' } : { kind: 'idle' }); })(); }, [supported, client]); const trimmedRelay = relayUrl.trim().replace(/\/+$/, ''); const isValidRelay = /^https?:\/\/.+/i.test(trimmedRelay); const busy = pushStatus.kind === 'busy'; const handleEnablePush = async () => { if (!client) { setPushStatus({ kind: 'error', message: 'Sign in first' }); return; } if (!isValidRelay) { setPushStatus({ kind: 'error', message: 'Enter a valid https:// URL' }); return; } setPushStatus({ kind: 'busy' }); try { await enableWebPush({ client, relayBaseUrl: trimmedRelay, accountLabel: username ?? undefined, }); setPushStatus({ kind: 'enabled' }); } catch (err) { if (err instanceof WebPushUnsupportedError) { setPushStatus({ kind: 'unsupported' }); return; } setPushStatus({ kind: 'error', message: err instanceof Error ? err.message : 'Failed to enable push', }); } }; const handleDisablePush = async () => { if (!client) return; const confirmed = await confirmDialog({ title: t('push.confirm_disable_title'), message: t('push.confirm_disable_message'), confirmText: t('push.disable'), variant: 'destructive', }); if (!confirmed) return; setPushStatus({ kind: 'busy' }); try { await disableWebPush({ client, relayBaseUrl: trimmedRelay }); setPushStatus({ kind: 'idle' }); } catch (err) { setPushStatus({ kind: 'error', message: err instanceof Error ? err.message : 'Failed to disable push', }); } }; const soundOptions = NOTIFICATION_SOUNDS.map((s) => ({ value: s.id, label: t(`sounds.${s.id}`), })); return (

{pushRelayLocked ? t('push.relay_locked_desc') : t('push.relay_desc')}

setRelayUrl(e.target.value)} placeholder={t('push.relay_placeholder')} disabled={busy || pushStatus.kind === 'unsupported' || pushRelayLocked} readOnly={pushRelayLocked} className="w-full rounded border bg-background px-3 py-2 text-sm disabled:opacity-50" />
{pushStatus.kind === 'enabled' && ( )}
{pushStatus.kind === 'unsupported' && (

{t('push.ios_hint')}

)}