168 lines
6.2 KiB
TypeScript
168 lines
6.2 KiB
TypeScript
"use client";
|
|
|
|
import { useEffect, useState } from "react";
|
|
|
|
export function NotificationPreferences() {
|
|
const [loading, setLoading] = useState(true);
|
|
const [saving, setSaving] = useState(false);
|
|
const [message, setMessage] = useState<string | null>(null);
|
|
|
|
const [emailStatusChange, setEmailStatusChange] = useState(true);
|
|
const [emailJobAlerts, setEmailJobAlerts] = useState(true);
|
|
const [inAppStatusChange, setInAppStatusChange] = useState(true);
|
|
const [inAppJobAlerts, setInAppJobAlerts] = useState(true);
|
|
const [digestFrequency, setDigestFrequency] = useState("IMMEDIATE");
|
|
|
|
useEffect(() => {
|
|
async function loadPreferences() {
|
|
try {
|
|
const res = await fetch("/api/notifications/preferences");
|
|
const data = await res.json();
|
|
if (res.ok && data.preferences) {
|
|
setEmailStatusChange(data.preferences.emailStatusChange ?? true);
|
|
setEmailJobAlerts(data.preferences.emailJobAlerts ?? true);
|
|
setInAppStatusChange(data.preferences.inAppStatusChange ?? true);
|
|
setInAppJobAlerts(data.preferences.inAppJobAlerts ?? true);
|
|
setDigestFrequency(data.preferences.digestFrequency || "IMMEDIATE");
|
|
}
|
|
} catch (err) {
|
|
console.error("Failed to load notification preferences:", err);
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
}
|
|
loadPreferences();
|
|
}, []);
|
|
|
|
const handleSave = async () => {
|
|
setSaving(true);
|
|
setMessage(null);
|
|
try {
|
|
const res = await fetch("/api/notifications/preferences", {
|
|
method: "PATCH",
|
|
headers: { "Content-Type": "application/json" },
|
|
body: JSON.stringify({
|
|
emailStatusChange,
|
|
emailJobAlerts,
|
|
inAppStatusChange,
|
|
inAppJobAlerts,
|
|
digestFrequency,
|
|
}),
|
|
});
|
|
if (res.ok) {
|
|
setMessage("Notification preferences saved successfully.");
|
|
} else {
|
|
const data = await res.json();
|
|
setMessage("Failed to save: " + (data.error || "Unknown error"));
|
|
}
|
|
} catch (err: any) {
|
|
setMessage("Error: " + err.message);
|
|
} finally {
|
|
setSaving(false);
|
|
}
|
|
};
|
|
|
|
if (loading) {
|
|
return <div className="text-xs text-zinc-500">Loading notification preferences...</div>;
|
|
}
|
|
|
|
return (
|
|
<div className="bg-white p-5 rounded-md border border-zinc-200 shadow-sm space-y-4">
|
|
<div className="flex justify-between items-center">
|
|
<div>
|
|
<h2 className="text-sm font-bold text-zinc-900">🔔 Notification Preferences</h2>
|
|
<p className="text-xs text-zinc-500 mt-0.5">
|
|
Configure how and when you receive status updates and job alert notifications.
|
|
</p>
|
|
</div>
|
|
<button
|
|
type="button"
|
|
onClick={handleSave}
|
|
disabled={saving}
|
|
className="px-3 py-1.5 bg-zinc-900 text-white rounded text-xs font-semibold hover:bg-zinc-800 transition-colors disabled:opacity-50"
|
|
>
|
|
{saving ? "Saving..." : "Save Preferences"}
|
|
</button>
|
|
</div>
|
|
|
|
{message && (
|
|
<div
|
|
className={`p-2.5 rounded text-xs border ${
|
|
message.startsWith("Failed") || message.startsWith("Error")
|
|
? "bg-red-50 text-red-700 border-red-200"
|
|
: "bg-emerald-50 text-emerald-800 border-emerald-200"
|
|
}`}
|
|
>
|
|
{message}
|
|
</div>
|
|
)}
|
|
|
|
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4 text-xs pt-1">
|
|
{/* Application Status Changes */}
|
|
<div className="p-3.5 bg-zinc-50 border border-zinc-200 rounded space-y-2">
|
|
<h3 className="font-semibold text-zinc-900">Application Status Changes</h3>
|
|
<label className="flex items-center space-x-2 text-zinc-700 cursor-pointer">
|
|
<input
|
|
type="checkbox"
|
|
checked={inAppStatusChange}
|
|
onChange={(e) => setInAppStatusChange(e.target.checked)}
|
|
className="rounded border-zinc-300"
|
|
/>
|
|
<span>In-App Notifications</span>
|
|
</label>
|
|
<label className="flex items-center space-x-2 text-zinc-700 cursor-pointer">
|
|
<input
|
|
type="checkbox"
|
|
checked={emailStatusChange}
|
|
onChange={(e) => setEmailStatusChange(e.target.checked)}
|
|
className="rounded border-zinc-300"
|
|
/>
|
|
<span>Email Notifications</span>
|
|
</label>
|
|
</div>
|
|
|
|
{/* Saved Search / Job Alerts */}
|
|
<div className="p-3.5 bg-zinc-50 border border-zinc-200 rounded space-y-2">
|
|
<h3 className="font-semibold text-zinc-900">Job Alerts & Matching Jobs</h3>
|
|
<label className="flex items-center space-x-2 text-zinc-700 cursor-pointer">
|
|
<input
|
|
type="checkbox"
|
|
checked={inAppJobAlerts}
|
|
onChange={(e) => setInAppJobAlerts(e.target.checked)}
|
|
className="rounded border-zinc-300"
|
|
/>
|
|
<span>In-App Notifications</span>
|
|
</label>
|
|
<label className="flex items-center space-x-2 text-zinc-700 cursor-pointer">
|
|
<input
|
|
type="checkbox"
|
|
checked={emailJobAlerts}
|
|
onChange={(e) => setEmailJobAlerts(e.target.checked)}
|
|
className="rounded border-zinc-300"
|
|
/>
|
|
<span>Email Notifications</span>
|
|
</label>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Email Frequency / Digest Mode */}
|
|
<div className="pt-2 border-t border-zinc-100 text-xs">
|
|
<label className="block font-semibold text-zinc-900 mb-1">
|
|
Email Delivery Frequency / Digest Mode
|
|
</label>
|
|
<select
|
|
value={digestFrequency}
|
|
onChange={(e) => setDigestFrequency(e.target.value)}
|
|
className="w-full sm:w-64 border border-zinc-300 rounded p-2 focus:ring-1 focus:ring-zinc-900 focus:outline-none"
|
|
>
|
|
<option value="IMMEDIATE">Immediate (Notify as events occur)</option>
|
|
<option value="DAILY">Daily Digest</option>
|
|
<option value="OFF">Turn off all email notifications</option>
|
|
</select>
|
|
<p className="text-[11px] text-zinc-500 mt-1">
|
|
If set to Daily Digest, non-critical email alerts are batched into a daily summary.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|