Alerts: backup-contact phrasing; correct backup countdown; include per-position pay link; deep-link route rendering in-place; single-position view via deep-link; ARB alias; backup test message alignment

This commit is contained in:
Siavash Sameni
2025-08-28 11:29:37 +04:00
parent 6c4a8dfe83
commit 29c346e868
5 changed files with 199 additions and 30 deletions

View File

@@ -1,7 +1,7 @@
"use client";
import { useEffect, useMemo, useState } from "react";
import { NotificationSettings, NotificationProvider, SchedulerProvider } from "@/types/notifications";
import { scheduleTestNotification } from "@/utils/scheduler";
import { scheduleTestNotification, scheduleTestBackupNotification } from "@/utils/scheduler";
export interface SettingsModalProps {
open: boolean;
@@ -28,6 +28,8 @@ const defaultSettings: NotificationSettings = {
schedyBaseUrl: ENV_SCHEDY,
schedyApiKey: ENV_SCHEDY_API_KEY,
email: "",
backupEmail: "",
backupDelayDays: 1,
daysBefore: 10,
};
@@ -37,13 +39,12 @@ export default function SettingsModal({ open, initial, onClose, onSave }: Settin
useEffect(() => {
if (open) {
const base: any = initial || defaultSettings;
const next: any = { ...base };
if (next.scheduler !== 'schedy') next.scheduler = 'schedy';
for (const k of ['cronhostApiKey', 'kronosBaseUrl', 'schedifyBaseUrl', 'schedifyApiKey', 'schedifyWebhookId']) {
if (k in next) delete next[k];
}
setForm(next);
// 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]);
@@ -55,6 +56,12 @@ export default function SettingsModal({ open, initial, onClose, onSave }: Settin
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);
@@ -80,6 +87,16 @@ export default function SettingsModal({ open, initial, onClose, onSave }: Settin
}
};
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 (
@@ -146,6 +163,16 @@ export default function SettingsModal({ open, initial, onClose, onSave }: Settin
/>
</label>
<label>
<span className="text-gray-300">Backup Email (optional)</span>
<input
className="mt-1 w-full rounded border border-gray-700 bg-gray-800 px-2 py-1 text-gray-100"
placeholder="backup@example.com"
value={form.backupEmail || ''}
onChange={(e) => setForm((f) => ({ ...f, backupEmail: e.target.value }))}
/>
</label>
<label>
<span className="text-gray-300">Days before liquidation</span>
<input
@@ -156,7 +183,16 @@ export default function SettingsModal({ open, initial, onClose, onSave }: Settin
onChange={(e) => setForm((f) => ({ ...f, daysBefore: Number(e.target.value) }))}
/>
</label>
<div />
<label>
<span className="text-gray-300">Backup delay (days after first email)</span>
<input
type="number"
min={1}
className="mt-1 w-full rounded border border-gray-700 bg-gray-800 px-2 py-1 text-gray-100"
value={form.backupDelayDays ?? 1}
onChange={(e) => setForm((f) => ({ ...f, backupDelayDays: Number(e.target.value) }))}
/>
</label>
{provider === 'ntfy' && (
<>
@@ -260,6 +296,14 @@ export default function SettingsModal({ open, initial, onClose, onSave }: Settin
>
Send test alert
</button>
<button
className="rounded bg-teal-600 px-3 py-1.5 disabled:opacity-50"
disabled={!canTest || !(form.backupEmail || '').trim()}
onClick={onTestBackup}
title={!((form.backupEmail || '').trim()) ? 'Set a backup email first' : ''}
>
Send backup test
</button>
<button
className="rounded bg-indigo-600 px-3 py-1.5 disabled:opacity-50"
disabled={saveDisabled}