xtablo-source/apps/main/src/hooks/useInstallPrompt.ts
Arthur Belleville 8e6e8b6d71
style(pwa): fix biome formatting in hook and banner
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
2026-04-02 19:54:46 +02:00

48 lines
1.6 KiB
TypeScript

import { useCallback, useEffect, useRef, useState } from "react";
const DISMISSED_KEY = "pwa-install-dismissed";
interface BeforeInstallPromptEvent extends Event {
prompt(): Promise<{ outcome: "accepted" | "dismissed" }>;
}
export function useInstallPrompt() {
const deferredPrompt = useRef<BeforeInstallPromptEvent | null>(null);
const [canInstall, setCanInstall] = useState(false);
const [isDismissed, setIsDismissed] = useState(
() => localStorage.getItem(DISMISSED_KEY) === "true"
);
const isStandalone =
typeof window !== "undefined" &&
typeof window.matchMedia === "function" &&
(window.matchMedia("(display-mode: standalone)")?.matches ?? false);
const isIOS = typeof navigator !== "undefined" && /iPad|iPhone|iPod/.test(navigator.userAgent);
useEffect(() => {
const handler = (e: Event) => {
e.preventDefault();
deferredPrompt.current = e as BeforeInstallPromptEvent;
setCanInstall(true);
};
window.addEventListener("beforeinstallprompt", handler);
return () => window.removeEventListener("beforeinstallprompt", handler);
}, []);
const promptInstall = useCallback(async () => {
if (!deferredPrompt.current) return;
const result = await deferredPrompt.current.prompt();
if (result.outcome === "accepted") {
deferredPrompt.current = null;
setCanInstall(false);
}
}, []);
const dismiss = useCallback(() => {
setIsDismissed(true);
localStorage.setItem(DISMISSED_KEY, "true");
}, []);
return { canInstall, isStandalone, isIOS, isDismissed, promptInstall, dismiss };
}