Files
buzz-sheet/components/comments/comment-push-prompt.tsx
T
gunshiz 5812d99715
CI / Verify (push) Successful in 2m30s
CI / Build immutable images and deploy (push) Successful in 3m9s
feat : comment feature
2026-10-08 01:40:08 +07:00

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>
</>;
}