Files
gitlab-instance-0a899031_cn…/app/[locale]/settings/notifications/page.tsx
2026-06-06 07:49:47 -05:00

197 lines
8.2 KiB
TypeScript

"use client";
import { useEffect, useState } from "react";
import { Link } from "@/i18n/navigation";
import Footer from "@/app/components/Footer";
import { apiFetch } from "@/app/lib/api-client";
interface PreferenceRecord {
channels: Record<string, boolean>;
categories: Record<string, boolean>;
quietHoursStart: string;
quietHoursEnd: string;
digest: string;
allowMarketing: boolean;
}
const DEFAULT_PREFERENCES: PreferenceRecord = {
channels: { in_app: true, email: false, wechat: false },
categories: {
system: true,
meetup: true,
gig: true,
match: true,
service: true,
community: true,
city: true,
},
quietHoursStart: "22:00",
quietHoursEnd: "08:00",
digest: "daily",
allowMarketing: false,
};
const CHANNELS = [
["in_app", "站内消息", "始终建议开启,所有业务通知都会进入消息中心。"],
["email", "邮件摘要", "适合离线时收到每日/每周汇总。"],
["wechat", "微信提醒", "后续可接企业微信、公众号或机器人。"],
];
const CATEGORIES = [
["system", "系统公告"],
["meetup", "活动提醒"],
["gig", "赏金任务"],
["match", "社交匹配"],
["service", "服务咨询"],
["community", "社区讨论"],
["city", "城市更新"],
];
export default function NotificationSettingsPage() {
const [prefs, setPrefs] = useState<PreferenceRecord>(DEFAULT_PREFERENCES);
const [saved, setSaved] = useState("");
useEffect(() => {
apiFetch<{ record: PreferenceRecord }>("/api/notifications/preferences")
.then((data) => setPrefs({ ...DEFAULT_PREFERENCES, ...(data.record || {}) }))
.catch(() => setPrefs(DEFAULT_PREFERENCES));
}, []);
const save = async () => {
await apiFetch("/api/notifications/preferences", {
method: "PUT",
body: JSON.stringify(prefs),
});
setSaved("通知偏好已保存");
};
return (
<div className="min-h-screen bg-[#f6f7f9] dark:bg-gray-950">
<main className="mx-auto max-w-5xl px-4 py-10 sm:px-6">
<Link href="/messages" className="text-sm font-medium text-gray-500 hover:text-gray-900 dark:text-gray-400 dark:hover:text-gray-100">
</Link>
<header className="mt-5 mb-8">
<p className="text-sm font-semibold text-[#ff4d4f]">Notification Settings</p>
<h1 className="mt-2 text-3xl font-bold text-gray-950 dark:text-gray-50"></h1>
<p className="mt-2 text-sm text-gray-500 dark:text-gray-400">
PocketBase
</p>
</header>
{saved && (
<div className="mb-6 rounded-2xl border border-emerald-200 bg-emerald-50 px-4 py-3 text-sm text-emerald-800 dark:border-emerald-900/60 dark:bg-emerald-950/30 dark:text-emerald-200">
{saved}
</div>
)}
<div className="grid gap-6 lg:grid-cols-[1fr_320px]">
<section className="space-y-6">
<div className="rounded-2xl bg-white p-5 shadow-sm ring-1 ring-gray-100 dark:bg-gray-900 dark:ring-gray-800">
<h2 className="font-semibold text-gray-950 dark:text-gray-50"></h2>
<div className="mt-4 space-y-3">
{CHANNELS.map(([key, title, desc]) => (
<label key={key} className="flex items-start justify-between gap-4 rounded-2xl border border-gray-100 p-4 dark:border-gray-800">
<span>
<span className="block font-medium text-gray-900 dark:text-gray-100">{title}</span>
<span className="mt-1 block text-sm text-gray-500 dark:text-gray-400">{desc}</span>
</span>
<input
type="checkbox"
checked={!!prefs.channels[key]}
onChange={(event) =>
setPrefs((prev) => ({
...prev,
channels: { ...prev.channels, [key]: event.target.checked },
}))
}
className="mt-1 h-5 w-5 accent-[#ff4d4f]"
/>
</label>
))}
</div>
</div>
<div className="rounded-2xl bg-white p-5 shadow-sm ring-1 ring-gray-100 dark:bg-gray-900 dark:ring-gray-800">
<h2 className="font-semibold text-gray-950 dark:text-gray-50"></h2>
<div className="mt-4 grid gap-3 sm:grid-cols-2">
{CATEGORIES.map(([key, label]) => (
<label key={key} className="flex items-center justify-between rounded-xl bg-gray-50 px-4 py-3 dark:bg-gray-800">
<span className="text-sm font-medium text-gray-700 dark:text-gray-200">{label}</span>
<input
type="checkbox"
checked={!!prefs.categories[key]}
onChange={(event) =>
setPrefs((prev) => ({
...prev,
categories: { ...prev.categories, [key]: event.target.checked },
}))
}
className="h-4 w-4 accent-[#ff4d4f]"
/>
</label>
))}
</div>
</div>
</section>
<aside className="space-y-6">
<div className="rounded-2xl bg-white p-5 shadow-sm ring-1 ring-gray-100 dark:bg-gray-900 dark:ring-gray-800">
<h2 className="font-semibold text-gray-950 dark:text-gray-50"></h2>
<div className="mt-4 grid grid-cols-2 gap-3">
<label>
<span className="text-xs text-gray-400"></span>
<input
value={prefs.quietHoursStart}
onChange={(event) => setPrefs((prev) => ({ ...prev, quietHoursStart: event.target.value }))}
className="mt-1 w-full rounded-xl border border-gray-200 px-3 py-2 text-sm dark:border-gray-800 dark:bg-gray-950 dark:text-gray-100"
/>
</label>
<label>
<span className="text-xs text-gray-400"></span>
<input
value={prefs.quietHoursEnd}
onChange={(event) => setPrefs((prev) => ({ ...prev, quietHoursEnd: event.target.value }))}
className="mt-1 w-full rounded-xl border border-gray-200 px-3 py-2 text-sm dark:border-gray-800 dark:bg-gray-950 dark:text-gray-100"
/>
</label>
</div>
</div>
<div className="rounded-2xl bg-white p-5 shadow-sm ring-1 ring-gray-100 dark:bg-gray-900 dark:ring-gray-800">
<h2 className="font-semibold text-gray-950 dark:text-gray-50"></h2>
<select
value={prefs.digest}
onChange={(event) => setPrefs((prev) => ({ ...prev, digest: event.target.value }))}
className="mt-4 w-full rounded-xl border border-gray-200 px-3 py-2 text-sm dark:border-gray-800 dark:bg-gray-950 dark:text-gray-100"
>
<option value="instant"></option>
<option value="daily"></option>
<option value="weekly"></option>
</select>
<label className="mt-4 flex items-center justify-between rounded-xl bg-gray-50 px-4 py-3 dark:bg-gray-800">
<span className="text-sm text-gray-700 dark:text-gray-200"></span>
<input
type="checkbox"
checked={prefs.allowMarketing}
onChange={(event) => setPrefs((prev) => ({ ...prev, allowMarketing: event.target.checked }))}
className="h-4 w-4 accent-[#ff4d4f]"
/>
</label>
</div>
<button
type="button"
onClick={save}
className="w-full rounded-2xl bg-[#ff4d4f] px-5 py-3 font-semibold text-white shadow-sm hover:bg-[#ff3333]"
>
</button>
</aside>
</div>
</main>
<Footer />
</div>
);
}