fix: masquer l'invite d'installation en mode PWA
This commit is contained in:
@@ -1,38 +1,74 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { Download } from "lucide-react";
|
import { Download, X } from "lucide-react";
|
||||||
|
|
||||||
interface BeforeInstallPromptEvent extends Event {
|
interface BeforeInstallPromptEvent extends Event {
|
||||||
prompt: () => Promise<void>;
|
prompt: () => Promise<void>;
|
||||||
userChoice: Promise<{ outcome: "accepted" | "dismissed" }>;
|
userChoice: Promise<{ outcome: "accepted" | "dismissed" }>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const DISMISS_KEY = "pwa-install-dismissed";
|
||||||
|
const DISMISS_DURATION = 7 * 24 * 60 * 60 * 1000; // 7 jours en millisecondes
|
||||||
|
|
||||||
export function InstallPWA() {
|
export function InstallPWA() {
|
||||||
const [deferredPrompt, setDeferredPrompt] = useState<BeforeInstallPromptEvent | null>(null);
|
const [deferredPrompt, setDeferredPrompt] = useState<BeforeInstallPromptEvent | null>(null);
|
||||||
const [isInstallable, setIsInstallable] = useState(false);
|
const [isInstallable, setIsInstallable] = useState(false);
|
||||||
const [isIOS, setIsIOS] = useState(false);
|
const [isIOS, setIsIOS] = useState(false);
|
||||||
|
const [isDismissed, setIsDismissed] = useState(true);
|
||||||
|
const [isStandalone, setIsStandalone] = useState(true); // Par défaut true pour éviter le flash
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
// Vérifier si on est en mode standalone (PWA)
|
||||||
|
const checkStandalone = () => {
|
||||||
|
return (
|
||||||
|
window.matchMedia("(display-mode: standalone)").matches ||
|
||||||
|
(window.navigator as any).standalone ||
|
||||||
|
document.referrer.includes("android-app://")
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Vérifier si l'invite a été fermée récemment
|
||||||
|
const checkDismissed = () => {
|
||||||
|
const dismissedAt = localStorage.getItem(DISMISS_KEY);
|
||||||
|
if (dismissedAt) {
|
||||||
|
const dismissedTime = parseInt(dismissedAt, 10);
|
||||||
|
const now = Date.now();
|
||||||
|
if (now - dismissedTime < DISMISS_DURATION) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
// Détecter si c'est un appareil iOS
|
// Détecter si c'est un appareil iOS
|
||||||
const isIOSDevice = /iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream;
|
const isIOSDevice = /iPad|iPhone|iPod/.test(navigator.userAgent);
|
||||||
setIsIOS(isIOSDevice);
|
setIsIOS(isIOSDevice);
|
||||||
|
|
||||||
|
// Initialiser les états
|
||||||
|
setIsStandalone(checkStandalone());
|
||||||
|
setIsDismissed(checkDismissed());
|
||||||
|
|
||||||
const handleBeforeInstallPrompt = (e: Event) => {
|
const handleBeforeInstallPrompt = (e: Event) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
setDeferredPrompt(e as BeforeInstallPromptEvent);
|
setDeferredPrompt(e as BeforeInstallPromptEvent);
|
||||||
setIsInstallable(true);
|
if (!checkDismissed() && !checkStandalone()) {
|
||||||
|
setIsInstallable(true);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
window.addEventListener("beforeinstallprompt", handleBeforeInstallPrompt);
|
window.addEventListener("beforeinstallprompt", handleBeforeInstallPrompt);
|
||||||
|
|
||||||
// Vérifier si l'app est déjà installée
|
// Écouter les changements de mode d'affichage
|
||||||
if (window.matchMedia("(display-mode: standalone)").matches) {
|
const displayModeQuery = window.matchMedia("(display-mode: standalone)");
|
||||||
setIsInstallable(false);
|
const handleDisplayModeChange = (e: MediaQueryListEvent) => {
|
||||||
}
|
setIsStandalone(e.matches);
|
||||||
|
};
|
||||||
|
displayModeQuery.addEventListener("change", handleDisplayModeChange);
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
window.removeEventListener("beforeinstallprompt", handleBeforeInstallPrompt);
|
window.removeEventListener("beforeinstallprompt", handleBeforeInstallPrompt);
|
||||||
|
displayModeQuery.removeEventListener("change", handleDisplayModeChange);
|
||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
@@ -53,7 +89,16 @@ export function InstallPWA() {
|
|||||||
setDeferredPrompt(null);
|
setDeferredPrompt(null);
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!isInstallable && !isIOS) return null;
|
const handleDismiss = () => {
|
||||||
|
localStorage.setItem(DISMISS_KEY, Date.now().toString());
|
||||||
|
setIsDismissed(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Ne pas afficher si :
|
||||||
|
// - L'app n'est pas installable ET ce n'est pas iOS
|
||||||
|
// - OU si l'invite a été fermée
|
||||||
|
// - OU si on est en mode standalone (PWA)
|
||||||
|
if ((!isInstallable && !isIOS) || isDismissed || isStandalone) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="fixed bottom-4 left-4 right-4 sm:left-auto sm:right-4 z-50">
|
<div className="fixed bottom-4 left-4 right-4 sm:left-auto sm:right-4 z-50">
|
||||||
@@ -81,6 +126,13 @@ export function InstallPWA() {
|
|||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
<button
|
||||||
|
onClick={handleDismiss}
|
||||||
|
className="p-1 hover:bg-accent hover:text-accent-foreground rounded-md transition-colors"
|
||||||
|
aria-label="Fermer"
|
||||||
|
>
|
||||||
|
<X className="h-4 w-4" />
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user