73 lines
4.3 KiB
TypeScript
73 lines
4.3 KiB
TypeScript
"use client";
|
|
|
|
import { useCallback, useEffect, useState } from "react";
|
|
import { BellIcon } from "lucide-react";
|
|
import { Button } from "@/components/ui/button";
|
|
import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from "@/components/ui/dialog";
|
|
|
|
export function CommentPushPrompt({ userId }: { userId: string }) {
|
|
const [publicKey, setPublicKey] = useState<string | null>(null);
|
|
const [open, setOpen] = useState(false);
|
|
const [enabled, setEnabled] = useState(false);
|
|
const [busy, setBusy] = useState(false);
|
|
const [error, setError] = useState("");
|
|
const askedKey = `comment-push-asked-${userId}`;
|
|
const syncedKey = `comment-push-synced-${userId}`;
|
|
const syncPush = useCallback(async (key: string) => {
|
|
// Reuse the site's existing root service worker so ticket notifications keep working.
|
|
const registration = await navigator.serviceWorker.register("/commission-push-sw.js", { scope: "/" });
|
|
const subscription = await registration.pushManager.getSubscription() ?? await registration.pushManager.subscribe({ userVisibleOnly: true, applicationServerKey: key });
|
|
const response = await fetch("/api/comments/push-subscription", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(subscription.toJSON()) });
|
|
if (!response.ok) throw new Error("subscription-failed");
|
|
sessionStorage.setItem(syncedKey, "1");
|
|
}, [syncedKey]);
|
|
useEffect(() => {
|
|
if (!window.isSecureContext || !("Notification" in window) || !("PushManager" in window) || !("serviceWorker" in navigator)) return;
|
|
let active = true;
|
|
void fetch("/api/comments/push-subscription", { cache: "no-store" }).then(async (response) => {
|
|
if (!response.ok) return;
|
|
const config = await response.json() as { publicKey: string };
|
|
if (!active) return;
|
|
setPublicKey(config.publicKey);
|
|
if (Notification.permission === "granted") {
|
|
if (!sessionStorage.getItem(syncedKey)) await syncPush(config.publicKey);
|
|
if (active) setEnabled(true);
|
|
}
|
|
}).catch(() => undefined);
|
|
return () => { active = false; };
|
|
}, [userId, syncedKey, syncPush]);
|
|
|
|
useEffect(() => {
|
|
function ask() {
|
|
if (!publicKey || Notification.permission !== "default" || localStorage.getItem(askedKey)) return;
|
|
localStorage.setItem(askedKey, "1");
|
|
setOpen(true);
|
|
}
|
|
window.addEventListener("comment:posted", ask);
|
|
return () => window.removeEventListener("comment:posted", ask);
|
|
}, [publicKey, askedKey]);
|
|
|
|
async function enable() {
|
|
if (!publicKey) return;
|
|
setBusy(true); setError("");
|
|
try {
|
|
if (await Notification.requestPermission() !== "granted") {
|
|
setError("ยังไม่ได้อนุญาตการแจ้งเตือน เปิดสิทธิ์การแจ้งเตือนในการตั้งค่าเว็บไซต์ของเบราว์เซอร์");
|
|
return;
|
|
}
|
|
await syncPush(publicKey);
|
|
setEnabled(true); setOpen(false);
|
|
} catch { setError("เปิดการแจ้งเตือนไม่สำเร็จ กรุณาลองอีกครั้ง"); }
|
|
finally { setBusy(false); }
|
|
}
|
|
if (!publicKey || enabled) return null;
|
|
return <>
|
|
<Button variant="outline" size="sm" className="self-start" onClick={() => setOpen(true)}><BellIcon data-icon="inline-start" />แจ้งเตือนเมื่อมีคนตอบกลับ</Button>
|
|
<Dialog open={open} onOpenChange={setOpen}><DialogContent>
|
|
<DialogHeader><DialogTitle>รับการแจ้งเตือนเมื่อมีคนตอบกลับ?</DialogTitle><DialogDescription>เราจะแจ้งเตือนเมื่อมีคนตอบกลับความคิดเห็นของคุณ แม้ปิดแท็บแล้ว กดที่การแจ้งเตือนเพื่อไปยังคำตอบนั้น</DialogDescription></DialogHeader>
|
|
{error && <p role="alert" className="text-sm text-destructive">{error}</p>}
|
|
<DialogFooter><Button variant="outline" disabled={busy} onClick={() => setOpen(false)}>ไว้ก่อน</Button><Button disabled={busy} onClick={() => void enable()}>เปิดการแจ้งเตือน</Button></DialogFooter>
|
|
</DialogContent></Dialog>
|
|
</>;
|
|
}
|