"use client"; import { useEffect, useMemo, useState } from "react"; import { NotificationSettings, NotificationProvider, SchedulerProvider } from "@/types/notifications"; import { scheduleTestNotification, scheduleTestBackupNotification } from "@/utils/scheduler"; export interface SettingsModalProps { open: boolean; initial?: NotificationSettings; onClose: () => void; onSave: (settings: NotificationSettings) => void; } const ENV_NTFY = (process.env.NEXT_PUBLIC_NTFY_URL || process.env.NTFY_URL || 'https://ntfy.sh').replace(/\/$/, ''); const ENV_SCHEDY = (process.env.NEXT_PUBLIC_SCHEDY_URL || process.env.SCHEDY_URL || 'http://localhost:8080').replace(/\/$/, ''); const ENV_SCHEDY_API_KEY = (process.env.NEXT_PUBLIC_SCHEDY_API_KEY || process.env.SCHEDY_API_KEY || '').trim(); const defaultSettings: NotificationSettings = { provider: "", scheduler: 'schedy', ntfyServer: ENV_NTFY, ntfyTopic: "", gotifyServer: "", gotifyToken: "", snsRegion: "", snsTopicArn: "", snsAccessKeyId: "", snsSecretAccessKey: "", schedyBaseUrl: ENV_SCHEDY, schedyApiKey: ENV_SCHEDY_API_KEY, email: "", backupEmail: "", backupDelayDays: 1, daysBefore: 10, }; export default function SettingsModal({ open, initial, onClose, onSave }: SettingsModalProps) { const [form, setForm] = useState(initial || defaultSettings); const [testStatus, setTestStatus] = useState(""); useEffect(() => { if (open) { // Merge defaults to ensure newly added fields (e.g., backupDelayDays) are populated const merged = { ...defaultSettings, ...(initial || {}) } as NotificationSettings; // Re-apply env defaults if user hasn't set values if (!merged.ntfyServer) merged.ntfyServer = ENV_NTFY; if (!merged.schedyBaseUrl) merged.schedyBaseUrl = ENV_SCHEDY; setForm(merged); } // eslint-disable-next-line react-hooks/exhaustive-deps }, [open, initial]); const provider: NotificationProvider | '' = form.provider || ''; const scheduler: SchedulerProvider | '' = form.scheduler || ''; const saveDisabled = useMemo(() => { if (!form.email) return true; const db = form.daysBefore ?? 10; if (Number(db) < 0) return true; // if backupEmail provided, enforce min backupDelayDays >= 1 (treat empty/NaN as invalid) const hasBackup = Boolean((form.backupEmail || '').trim()); if (hasBackup) { const bdd = Number(form.backupDelayDays); if (!Number.isFinite(bdd) || bdd < 1) return true; } if (!provider) return true; if (!scheduler) return true; if (provider === 'ntfy') return !(form.ntfyServer && form.ntfyTopic); if (provider === 'gotify') return !(form.gotifyServer && form.gotifyToken); if (provider === 'sns') return !(form.snsRegion && form.snsTopicArn && form.snsAccessKeyId && form.snsSecretAccessKey); if (scheduler === 'schedy') return !(form.schedyBaseUrl && form.schedyApiKey); return true; }, [form, provider, scheduler]); const canTest = useMemo(() => { // Basic same checks as save + provider must be ntfy if (saveDisabled) return false; return provider === 'ntfy' && scheduler === 'schedy'; }, [saveDisabled, provider, scheduler]); const onTest = async () => { setTestStatus('Scheduling test…'); try { const { jobId } = await scheduleTestNotification(form); setTestStatus(`Test scheduled (job ${jobId}). You should receive a notification shortly.`); } catch (e: any) { setTestStatus(`Test failed: ${e?.message || String(e)}`); } }; const onTestBackup = async () => { setTestStatus('Scheduling backup test…'); try { const { jobId } = await scheduleTestBackupNotification(form); setTestStatus(`Backup test scheduled (job ${jobId}). You should receive a backup email shortly.`); } catch (e: any) { setTestStatus(`Backup test failed: ${e?.message || String(e)}`); } }; if (!open) return null; return (
Notification Settings

Configure your notification provider and scheduler credentials.

{scheduler === 'schedy' && ( <> )} {provider === 'ntfy' && ( <> )} {provider === 'gotify' && ( <> )} {provider === 'sns' && ( <>
Storing AWS credentials in the browser is insecure. Prefer a server-side relay.
)}
{testStatus && (
{testStatus}
)}
); }