138 lines
6.3 KiB
TypeScript
138 lines
6.3 KiB
TypeScript
"use client";
|
|
|
|
import { useEffect, useState } from "react";
|
|
import { usePathname } from "next/navigation";
|
|
|
|
import { syncCommissionPush } from "@/components/commission/push-client";
|
|
import { Button } from "@/components/ui/button";
|
|
import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from "@/components/ui/dialog";
|
|
|
|
type InstallEvent = Event & { prompt: () => Promise<void>; userChoice: Promise<{ outcome: string }> };
|
|
|
|
const dismissedKey = "site-entry-prompt-dismissed";
|
|
|
|
function isInstalled() {
|
|
return window.matchMedia("(display-mode: standalone)").matches ||
|
|
(navigator as Navigator & { standalone?: boolean }).standalone === true;
|
|
}
|
|
|
|
export function SiteEntryPrompt() {
|
|
const pathname = usePathname();
|
|
const [open, setOpen] = useState(false);
|
|
const [installed, setInstalled] = useState(false);
|
|
const [installEvent, setInstallEvent] = useState<InstallEvent | null>(null);
|
|
const [permission, setPermission] = useState<NotificationPermission | null>(null);
|
|
const [busy, setBusy] = useState(false);
|
|
const [error, setError] = useState("");
|
|
|
|
useEffect(() => {
|
|
// Commission pages have their own prompt, tied to the signed-in user's messages.
|
|
if (pathname !== "/") {
|
|
const frame = requestAnimationFrame(() => setOpen(false));
|
|
return () => cancelAnimationFrame(frame);
|
|
}
|
|
|
|
const installedNow = isInstalled();
|
|
const ios = /iPad|iPhone|iPod/.test(navigator.userAgent) ||
|
|
(navigator.platform === "MacIntel" && navigator.maxTouchPoints > 1);
|
|
const canNotify = window.isSecureContext && "Notification" in window &&
|
|
"PushManager" in window && "serviceWorker" in navigator;
|
|
|
|
const frame = requestAnimationFrame(() => {
|
|
setInstalled(installedNow);
|
|
setPermission(canNotify ? Notification.permission : null);
|
|
if (!sessionStorage.getItem(dismissedKey) &&
|
|
((ios && !installedNow) || (canNotify && Notification.permission === "default"))) {
|
|
setOpen(true);
|
|
}
|
|
});
|
|
|
|
const beforeInstall = (event: Event) => {
|
|
event.preventDefault();
|
|
setInstallEvent(event as InstallEvent);
|
|
if (!sessionStorage.getItem(dismissedKey)) setOpen(true);
|
|
};
|
|
const onInstalled = () => {
|
|
setInstalled(true);
|
|
setInstallEvent(null);
|
|
if (!canNotify || Notification.permission !== "default") setOpen(false);
|
|
};
|
|
window.addEventListener("beforeinstallprompt", beforeInstall);
|
|
window.addEventListener("appinstalled", onInstalled);
|
|
return () => {
|
|
cancelAnimationFrame(frame);
|
|
window.removeEventListener("beforeinstallprompt", beforeInstall);
|
|
window.removeEventListener("appinstalled", onInstalled);
|
|
};
|
|
}, [pathname]);
|
|
|
|
function dismiss() {
|
|
sessionStorage.setItem(dismissedKey, "1");
|
|
setOpen(false);
|
|
}
|
|
|
|
async function install() {
|
|
if (!installEvent) return;
|
|
await installEvent.prompt();
|
|
await installEvent.userChoice;
|
|
setInstallEvent(null);
|
|
}
|
|
|
|
async function enableNotifications() {
|
|
setBusy(true);
|
|
setError("");
|
|
try {
|
|
const result = await Notification.requestPermission();
|
|
setPermission(result);
|
|
if (result !== "granted") {
|
|
if (!showInstall) setOpen(false);
|
|
return;
|
|
}
|
|
|
|
// A signed-in commission user can receive messages immediately. Guests can
|
|
// subscribe when they later sign in and visit their commission page.
|
|
const response = await fetch("/api/commission/push-subscription", { cache: "no-store" });
|
|
if (response.ok) {
|
|
const config = await response.json() as { publicKey: string; canAdmin: boolean };
|
|
await syncCommissionPush(config.canAdmin ? "admin" : "customer", config.publicKey);
|
|
}
|
|
if (!showInstall) setOpen(false);
|
|
} catch {
|
|
setError("เปิดการแจ้งเตือนไม่สำเร็จ กรุณาลองอีกครั้ง");
|
|
} finally {
|
|
setBusy(false);
|
|
}
|
|
}
|
|
|
|
const ios = typeof navigator !== "undefined" &&
|
|
(/iPad|iPhone|iPod/.test(navigator.userAgent) ||
|
|
(navigator.platform === "MacIntel" && navigator.maxTouchPoints > 1));
|
|
const showInstall = !installed && (ios || installEvent !== null);
|
|
const canAskNotification = permission === "default" && (!ios || installed);
|
|
|
|
return <Dialog open={open} onOpenChange={(next) => { if (!next) dismiss(); }}>
|
|
<DialogContent>
|
|
<DialogHeader>
|
|
<DialogTitle>{showInstall ? "ติดตั้ง Buzz Guide และรับการแจ้งเตือน" : "รับการแจ้งเตือนจาก Buzz Guide"}</DialogTitle>
|
|
<DialogDescription>
|
|
{showInstall ?
|
|
"เพิ่มเว็บไซต์ลงหน้าจอหลักเพื่อเปิดใช้งานได้สะดวก และรับการแจ้งเตือนเมื่อมีข้อความใหม่ใน Commission หลังเข้าสู่ระบบ" :
|
|
"รับการแจ้งเตือนเมื่อมีข้อความใหม่ใน Commission หลังเข้าสู่ระบบ"}
|
|
</DialogDescription>
|
|
</DialogHeader>
|
|
{!installed && ios && <p className="text-sm text-muted-foreground">
|
|
บน iPhone/iPad แตะปุ่มแชร์ใน Safari แล้วเลือก ‘เพิ่มไปยังหน้าจอโฮม’ จากนั้นเปิดแอปที่ติดตั้ง
|
|
</p>}
|
|
{ios && !installed && <p className="text-sm text-muted-foreground">
|
|
ติดตั้งและเปิดจากหน้าจอโฮมก่อน จึงจะเปิดการแจ้งเตือนได้บน iPhone/iPad
|
|
</p>}
|
|
{error && <p role="alert" className="text-sm text-destructive">{error}</p>}
|
|
<DialogFooter>
|
|
<Button type="button" variant="outline" disabled={busy} onClick={dismiss}>ไว้ก่อน</Button>
|
|
{installEvent && !installed && <Button type="button" variant="secondary" disabled={busy} onClick={() => void install()}>ติดตั้งแอป</Button>}
|
|
{canAskNotification && <Button type="button" disabled={busy} onClick={() => void enableNotifications()}>เปิดการแจ้งเตือน</Button>}
|
|
</DialogFooter>
|
|
</DialogContent>
|
|
</Dialog>;
|
|
}
|