feat : ask for noti
CI / Verify (push) Successful in 1m50s
CI / Build immutable images and deploy (push) Successful in 2m59s

This commit is contained in:
2026-10-08 02:23:59 +07:00 Unverified
parent 5812d99715
commit 9bc710c94b
34 changed files with 10913 additions and 201 deletions
+1 -18
View File
@@ -1,26 +1,9 @@
"use client";
export async function subscribeCommissionPush(scope: "customer" | "admin", publicKey: string) {
if (await Notification.requestPermission() !== "granted") return false;
await syncCommissionPush(scope, publicKey);
return true;
}
export async function syncCommissionPush(scope: "customer" | "admin", publicKey: string) {
const registration = await navigator.serviceWorker.register("/commission-push-sw.js", { scope: "/" });
const subscription = await registration.pushManager.getSubscription() ??
await registration.pushManager.subscribe({ userVisibleOnly: true, applicationServerKey: publicKey });
const response = await fetch("/api/commission/push-subscription", { method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ ...subscription.toJSON(), scope }) });
if (!response.ok) throw new Error("Could not save push subscription");
sessionStorage.setItem(`commission-push-synced-${scope}`, "1");
}
export async function unsubscribeCommissionPush() {
sessionStorage.removeItem("commission-push-synced-customer");
sessionStorage.removeItem("commission-push-synced-admin");
for (const key of Object.keys(sessionStorage)) if (key.startsWith("comment-push-synced-")) sessionStorage.removeItem(key);
for (const key of Object.keys(sessionStorage)) if (key.startsWith("comment-push-synced-") || key.startsWith("site-push-synced-")) sessionStorage.removeItem(key);
if (!("serviceWorker" in navigator)) return;
const registration = await navigator.serviceWorker.getRegistration("/");
const subscription = await registration?.pushManager.getSubscription();
@@ -1,66 +0,0 @@
"use client";
import { useEffect, useState } from "react";
import { Button } from "@/components/ui/button";
import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from "@/components/ui/dialog";
import { subscribeCommissionPush, syncCommissionPush } from "./push-client";
type PushRequest = { scope: "customer" | "admin"; publicKey: string };
const askedKey = "commission-push-asked";
export function CommissionPushEntryPrompt({ scope }: { scope: "customer" | "admin" }) {
const [request, setRequest] = useState<PushRequest | null>(null);
const [busy, setBusy] = useState(false);
const [error, setError] = useState("");
useEffect(() => {
if (!window.isSecureContext ||
!("Notification" in window) || !("PushManager" in window) || !("serviceWorker" in navigator) ||
Notification.permission === "denied" ||
(Notification.permission === "default" && sessionStorage.getItem(askedKey)) ||
(Notification.permission === "granted" && (sessionStorage.getItem(`commission-push-synced-${scope}`) ||
(scope === "customer" && sessionStorage.getItem("commission-push-synced-admin"))))) return;
let active = true;
void fetch("/api/commission/push-subscription", { cache: "no-store" }).then(async (response) => {
if (!response.ok) return;
const config = await response.json() as { publicKey: string; canAdmin: boolean };
if (!active || (scope === "admin" && !config.canAdmin)) return;
const subscriptionScope = config.canAdmin ? "admin" : scope;
if (Notification.permission === "granted") {
await syncCommissionPush(subscriptionScope, config.publicKey);
return;
}
if (Notification.permission !== "default") return;
sessionStorage.setItem(askedKey, "1");
setRequest({ scope: subscriptionScope, publicKey: config.publicKey });
}).catch(() => undefined);
return () => { active = false; };
}, [scope]);
async function enable() {
if (!request) return;
setBusy(true);
setError("");
try {
await subscribeCommissionPush(request.scope, request.publicKey);
setRequest(null);
} catch {
setError("เปิดการแจ้งเตือนไม่สำเร็จ กรุณาลองอีกครั้ง");
} finally { setBusy(false); }
}
return <Dialog open={request !== null} onOpenChange={(open) => { if (!open) setRequest(null); }}>
<DialogContent>
<DialogHeader>
<DialogTitle>รับการแจ้งเตือนข้อความใหม่?</DialogTitle>
<DialogDescription>เมื่อมีข้อความใหม่ใน Ticket เราจะแจ้งเตือนพร้อมชื่อผู้ส่งและข้อความ แม้ปิดแท็บแล้ว</DialogDescription>
</DialogHeader>
{error && <p role="alert" className="text-sm text-destructive">{error}</p>}
<DialogFooter>
<Button type="button" variant="outline" disabled={busy} onClick={() => setRequest(null)}>ไว้ก่อน</Button>
<Button type="button" disabled={busy} onClick={() => void enable()}>เปิดการแจ้งเตือน</Button>
</DialogFooter>
</DialogContent>
</Dialog>;
}
-2
View File
@@ -80,7 +80,6 @@ import {
import { Skeleton } from "@/components/ui/skeleton";
import { cn } from "@/lib/utils";
import { parseCommissionMessageEvent } from "@/lib/commission/message-event";
import { CommissionPushEntryPrompt } from "./push-entry-prompt";
import { keepCommissionTitle } from "./keep-title";
type ChatMessage = {
@@ -626,7 +625,6 @@ export function CommissionTicketChat({
return (
<>
{!admin && <CommissionPushEntryPrompt scope="customer" />}
<AlertDialog open={closedAlertOpen} onOpenChange={setClosedAlertOpen}>
<AlertDialogContent>
<AlertDialogHeader>