From 7383a13c130e08829cda703ba7c01d5669a84a2a Mon Sep 17 00:00:00 2001 From: fccview Date: Mon, 1 Sep 2025 15:23:59 +0100 Subject: [PATCH] fix pwa fully --- app/_components/ui/PWAInstallPrompt.tsx | 58 ++++++++++++++++++++ app/_components/ui/ServiceWorkerRegister.tsx | 23 ++++++++ app/_components/ui/Sidebar.tsx | 2 +- app/layout.tsx | 21 +------ app/page.tsx | 2 + middleware.ts | 2 +- next.config.js | 20 ++++++- public/manifest.json | 4 +- 8 files changed, 109 insertions(+), 23 deletions(-) create mode 100644 app/_components/ui/PWAInstallPrompt.tsx create mode 100644 app/_components/ui/ServiceWorkerRegister.tsx 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( >