"use client"; import { useEffect, useState } from "react"; import { Button } from "@/components/ui/button"; import { showCustomToast, dismissToast } from "@/lib/toast"; import { RefreshCw } from "lucide-react"; export default function UpdateNotification() { const [updateAvailable, setUpdateAvailable] = useState(false); const [registration, setRegistration] = useState(null); const [toastId, setToastId] = useState(null); useEffect(() => { if ("serviceWorker" in navigator) { registerServiceWorker(); } if (process.env.NODE_ENV === "development") { const handleTestUpdate = () => setUpdateAvailable(true); window.addEventListener("manual-update-test", handleTestUpdate); return () => window.removeEventListener("manual-update-test", handleTestUpdate); } }, []); useEffect(() => { if (updateAvailable && !toastId) { showUpdateNotification(); } }, [updateAvailable, toastId]); useEffect(() => { // Cleanup function return () => { if (toastId) { dismissToast(toastId); } }; }, [toastId]); const registerServiceWorker = async () => { try { const reg = await navigator.serviceWorker.register("/sw.js"); setRegistration(reg); // Automatically activate any waiting service worker on page load if (reg.waiting) { reg.waiting.postMessage({ type: "SKIP_WAITING" }); // Don't show notification since we're auto-activating return; } reg.addEventListener("updatefound", () => { const newWorker = reg.installing; if (newWorker) { newWorker.addEventListener("statechange", () => { if (newWorker.state === "installed") { if (navigator.serviceWorker.controller) { // There's an existing service worker, so this new one should wait setUpdateAvailable(true); } else { // This is the first service worker, no need to show notification } } }); } }); // Periodically check for updates (every 60 seconds) setInterval(() => { reg.update(); }, 60000); } catch (error) { console.error("❌ Service Worker registration failed:", error); } }; const showUpdateNotification = () => { if (toastId) { return; // Don't show multiple notifications } const id = showCustomToast(
New version available! Save your work by downloading your card preset.
, { duration: Infinity, // Keep toast visible until user acts closeButton: true, onDismiss: () => { setUpdateAvailable(false); setToastId(null); }, }, ); setToastId(id); }; const handleRefresh = () => { // Dismiss the toast if (toastId) { dismissToast(toastId); setToastId(null); } // Tell service worker to skip waiting and activate if (registration?.waiting) { registration.waiting.postMessage({ type: "SKIP_WAITING" }); // Listen for the service worker to take control, then reload navigator.serviceWorker.addEventListener( "controllerchange", () => { window.location.reload(); }, { once: true }, ); // Only listen once } else { // If no waiting service worker, just reload window.location.reload(); } }; // This component doesn't render anything visible return null; }