diff --git a/AppImage/components/pwa-install-prompt.tsx b/AppImage/components/pwa-install-prompt.tsx index 2b696747..91a0d78a 100644 --- a/AppImage/components/pwa-install-prompt.tsx +++ b/AppImage/components/pwa-install-prompt.tsx @@ -1,21 +1,24 @@ "use client" import { useCallback, useEffect, useState } from "react" -import { Download, Plus, Share, X } from "lucide-react" +import { Plus, Share, X } from "lucide-react" // ========================================================== // PwaInstallPrompt // ========================================================== // Bottom-sheet shown on mobile when the Monitor is opened in // a browser (not launched as an installed PWA). Two variants: -// iOS Safari → manual 3-step instructions (no browser API) -// Android → native install prompt when the browser -// fires `beforeinstallprompt` (Chromium 89+ -// delivers it based on manifest validity alone, -// no Service Worker required), with the manual -// "browser menu → Add to Home Screen" steps -// always shown below as a fallback. +// iOS Safari → manual 3-step instructions +// Android → generic "browser menu → Add to Home Screen" // +// No `beforeinstallprompt` handling: capturing the event to +// drive a custom Install button interacts badly with Chrome's +// own "Add to Home Screen" menu — Chromium degrades the manual +// path to a plain shortcut when a page has intercepted the +// event but hasn't yet called `prompt()`. Installation goes +// through the browser's own menu entry and produces a real PWA. +// +// Never shown on desktop, or when already running standalone. // Dismissal options: // "Not now" → temporary, hidden for 30 days // "Don't show again" → permanent (no expiry) @@ -27,13 +30,6 @@ const DISMISSED_FOREVER_KEY = "proxmenux-install-dismissed" const DISMISSED_UNTIL_KEY = "proxmenux-install-dismissed-until" const NOT_NOW_DAYS = 30 -// `BeforeInstallPromptEvent` isn't in the TS DOM lib yet. -interface BeforeInstallPromptEvent extends Event { - readonly platforms: string[] - readonly userChoice: Promise<{ outcome: "accepted" | "dismissed"; platform: string }> - prompt(): Promise -} - function isMobileDevice(): boolean { if (typeof window === "undefined") return false // Prefer feature detection (coarse pointer + touch) over UA sniffing, @@ -64,7 +60,6 @@ function isIOS(): boolean { export function PwaInstallPrompt() { const [open, setOpen] = useState(false) const [platform, setPlatform] = useState<"ios" | "android" | null>(null) - const [deferredPrompt, setDeferredPrompt] = useState(null) useEffect(() => { if (typeof window === "undefined") return @@ -87,28 +82,6 @@ export function PwaInstallPrompt() { setOpen(true) }, []) - useEffect(() => { - if (typeof window === "undefined") return - const onBeforeInstall = (e: Event) => { - // Suppress the browser's own mini-infobar so the Install button - // in the bottom sheet is the primary path. - e.preventDefault() - setDeferredPrompt(e as BeforeInstallPromptEvent) - } - const onInstalled = () => { - // The browser confirms the install landed on the home screen — - // close the sheet and drop the deferred event. - setDeferredPrompt(null) - setOpen(false) - } - window.addEventListener("beforeinstallprompt", onBeforeInstall) - window.addEventListener("appinstalled", onInstalled) - return () => { - window.removeEventListener("beforeinstallprompt", onBeforeInstall) - window.removeEventListener("appinstalled", onInstalled) - } - }, []) - const handleNotNow = useCallback(() => { try { const until = Date.now() + NOT_NOW_DAYS * 24 * 60 * 60 * 1000 @@ -137,25 +110,6 @@ export function PwaInstallPrompt() { setOpen(false) }, []) - const handleInstall = useCallback(async () => { - if (!deferredPrompt) return - try { - await deferredPrompt.prompt() - const { outcome } = await deferredPrompt.userChoice - // A `beforeinstallprompt` event can only be used once; drop the - // reference either way so the button hides. - setDeferredPrompt(null) - if (outcome === "accepted") { - setOpen(false) - } - } catch { - // Browser refused to run the prompt (already handled, race with - // appinstalled, etc.) — leave the sheet open so the operator can - // fall back to the manual steps rendered below. - setDeferredPrompt(null) - } - }, [deferredPrompt]) - if (!open || !platform) return null return ( @@ -236,33 +190,11 @@ export function PwaInstallPrompt() { ) : ( - <> - {deferredPrompt && ( - - )} -
- {deferredPrompt ? ( - <> - Or install manually: browser menu →{" "} - Add to Home Screen → confirm by tapping{" "} - Install. - - ) : ( - <> - Open the browser menu →{" "} - Add to Home Screen → confirm by tapping{" "} - Install. - - )} -
- +
+ Open the browser menu →{" "} + Add to Home Screen → confirm by tapping{" "} + Install. +
)}
diff --git a/CHANGELOG.md b/CHANGELOG.md index 70a54b69..34f6c7e0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,10 +20,8 @@ This release adds two in-dashboard improvements — a one-click Proxmox update t ## 📱 In-app Install prompt for mobile -- First-time visitors on **Android** (Chrome / Brave / Edge / Samsung Internet) and **iOS Safari** now see a bottom-sheet with clear instructions for adding the Monitor as a PWA to their home screen. -- On Android, when the browser deems the Monitor installable and fires `beforeinstallprompt` (Chromium 89+ delivers it on manifest validity alone, no Service Worker required), an **Install** button in the sheet drives the native install flow with a single tap; the browser's own in-page install banner is suppressed so the sheet is the primary path. The manual "browser menu → Add to Home Screen" steps are still shown as a fallback for browsers that don't fire the event (Firefox Android) or cases where the operator prefers the manual path. -- iOS Safari keeps the manual 3-step instructions — no equivalent JS API is available on iOS. -- The sheet closes automatically when the browser fires `appinstalled` — no lingering prompt after the install lands on the home screen. +- First-time visitors on **Android** (Chrome / Brave) and **iOS Safari** now see a bottom-sheet with clear instructions for adding the Monitor as a PWA to their home screen. +- Installation goes through the browser's own menu entry ("Add to Home Screen"), which produces a real installed PWA that launches in standalone mode. The sheet doesn't intercept the browser's `beforeinstallprompt` event — intercepting it and not calling `prompt()` degrades the manual menu path to a plain shortcut, which is what showed up in field testing. - Two dismissal levels: **Not now** (temporary, reappears in 30 days) and **Don't show again** (permanent, stored in `localStorage`). - Never shown on desktop, or once the Monitor is already running standalone. diff --git a/web/data/changelog/es.md b/web/data/changelog/es.md index 5d03f7c4..37fa8199 100644 --- a/web/data/changelog/es.md +++ b/web/data/changelog/es.md @@ -19,10 +19,8 @@ Esta versión suma dos mejoras visibles en el propio dashboard — un botón par ## 📱 Prompt de instalación en la app para móvil -- Los visitantes por primera vez en **Android** (Chrome / Brave / Edge / Samsung Internet) e **iOS Safari** ven ahora una bottom-sheet con instrucciones claras para añadir el Monitor a su pantalla de inicio como PWA. -- En Android, cuando el navegador considera el Monitor instalable y dispara `beforeinstallprompt` (Chromium 89+ lo entrega solo con manifest válido, sin necesidad de Service Worker), un botón **Install** en la bottom-sheet dispara el flujo nativo de instalación con un solo tap; el propio banner de instalación in-page del navegador se suprime para que la bottom-sheet sea el camino principal. Las instrucciones manuales de "menú del navegador → Añadir a pantalla de inicio" siguen mostrándose como fallback para navegadores que no disparan el evento (Firefox Android) o casos en que el operador prefiere la ruta manual. -- iOS Safari mantiene las instrucciones manuales de 3 pasos — no existe una API JS equivalente en iOS. -- La bottom-sheet se cierra automáticamente cuando el navegador dispara `appinstalled` — no queda ningún prompt colgado tras la instalación en la pantalla de inicio. +- Los visitantes por primera vez en **Android** (Chrome / Brave) e **iOS Safari** ven ahora una bottom-sheet con instrucciones claras para añadir el Monitor a su pantalla de inicio como PWA. +- La instalación se hace a través de la propia entrada del menú del navegador ("Añadir a pantalla de inicio"), que produce una PWA real instalada que se lanza en modo standalone. La bottom-sheet no intercepta el evento `beforeinstallprompt` del navegador — interceptarlo sin llegar a llamar a `prompt()` degrada la ruta manual del menú a un acceso directo simple, que es lo que apareció en las pruebas de campo. - Dos niveles de descarte: **Not now** (temporal, reaparece a los 30 días) y **Don't show again** (permanente, almacenado en `localStorage`). - No se muestra en escritorio, ni cuando el Monitor ya se está ejecutando como aplicación instalada.