diff --git a/app/_components/ui/PWAInstallPrompt.tsx b/app/_components/ui/PWAInstallPrompt.tsx new file mode 100644 index 0000000..828e225 --- /dev/null +++ b/app/_components/ui/PWAInstallPrompt.tsx @@ -0,0 +1,58 @@ +"use client"; + +import { useCallback, useEffect, useState } from "react"; + +type BeforeInstallPromptEvent = Event & { + prompt: () => Promise; + userChoice: Promise<{ outcome: "accepted" | "dismissed" }>; +}; + +export const PWAInstallPrompt = (): JSX.Element | null => { + const [deferred, setDeferred] = useState( + null + ); + const [isInstalled, setIsInstalled] = useState(false); + + useEffect(() => { + if (typeof window === "undefined") return; + const onBeforeInstallPrompt = (e: Event) => { + e.preventDefault(); + setDeferred(e as BeforeInstallPromptEvent); + }; + const onAppInstalled = () => { + setDeferred(null); + setIsInstalled(true); + }; + window.addEventListener("beforeinstallprompt", onBeforeInstallPrompt); + window.addEventListener("appinstalled", onAppInstalled); + if (window.matchMedia("(display-mode: standalone)").matches) { + setIsInstalled(true); + } + return () => { + window.removeEventListener("beforeinstallprompt", onBeforeInstallPrompt); + window.removeEventListener("appinstalled", onAppInstalled); + }; + }, []); + + const onInstall = useCallback(async () => { + if (!deferred) return; + try { + await deferred.prompt(); + const choice = await deferred.userChoice; + if (choice.outcome === "accepted") { + setDeferred(null); + } + } catch (_err) {} + }, [deferred]); + + if (isInstalled || !deferred) return null; + + return ( + + ); +}; diff --git a/app/_components/ui/ServiceWorkerRegister.tsx b/app/_components/ui/ServiceWorkerRegister.tsx new file mode 100644 index 0000000..7ff4cf0 --- /dev/null +++ b/app/_components/ui/ServiceWorkerRegister.tsx @@ -0,0 +1,23 @@ +"use client"; + +import { useEffect } from "react"; + +export const ServiceWorkerRegister = (): null => { + useEffect(() => { + if (typeof window === "undefined") return; + if (!("serviceWorker" in navigator)) return; + const register = async () => { + try { + const registrations = await navigator.serviceWorker.getRegistrations(); + const alreadyRegistered = registrations.some((r) => + r.scope.endsWith("/") + ); + if (alreadyRegistered) return; + await navigator.serviceWorker.register("/sw.js", { scope: "/" }); + } catch (_err) {} + }; + register(); + }, []); + + return null; +}; diff --git a/app/_components/ui/Sidebar.tsx b/app/_components/ui/Sidebar.tsx index d949960..e4855d1 100644 --- a/app/_components/ui/Sidebar.tsx +++ b/app/_components/ui/Sidebar.tsx @@ -92,7 +92,7 @@ export const Sidebar = forwardRef( >