74 lines
2.3 KiB
TypeScript
74 lines
2.3 KiB
TypeScript
import { useState } from "react";
|
|
import { Button } from "@/components/ui/button";
|
|
import { useInstallPrompt } from "../hooks/useInstallPrompt";
|
|
|
|
const DISMISS_KEY = "nexus.installPromptDismissed";
|
|
const DISMISS_COOLDOWN_MS = 7 * 24 * 60 * 60 * 1000; // 7 days
|
|
|
|
function isDismissed(): boolean {
|
|
try {
|
|
const stored = localStorage.getItem(DISMISS_KEY);
|
|
if (!stored) return false;
|
|
const dismissedAt = parseInt(stored, 10);
|
|
return Date.now() - dismissedAt < DISMISS_COOLDOWN_MS;
|
|
} catch {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* PWA install prompt banner.
|
|
*
|
|
* Shows when:
|
|
* - The browser has fired beforeinstallprompt (canInstall) OR the device is iOS
|
|
* - The app is not already installed (running in standalone mode)
|
|
* - The user has not dismissed within the last 7 days
|
|
*
|
|
* Positioned above MobileBottomNav on mobile and top-right on desktop.
|
|
*/
|
|
export function InstallPromptBanner() {
|
|
const { canInstall, promptInstall, isIOS } = useInstallPrompt();
|
|
const [dismissed, setDismissed] = useState(() => isDismissed());
|
|
|
|
if (dismissed) return null;
|
|
if (!canInstall && !isIOS) return null;
|
|
|
|
const handleInstall = async () => {
|
|
if (isIOS) return; // iOS: user follows Share menu instructions
|
|
await promptInstall();
|
|
};
|
|
|
|
const handleDismiss = () => {
|
|
try {
|
|
localStorage.setItem(DISMISS_KEY, String(Date.now()));
|
|
} catch {
|
|
// localStorage unavailable — dismiss for session only
|
|
}
|
|
setDismissed(true);
|
|
};
|
|
|
|
return (
|
|
<div
|
|
className="fixed bottom-16 left-4 right-4 z-50 md:bottom-auto md:top-4 md:left-auto md:right-4 md:max-w-sm bg-card border border-border rounded-lg p-4"
|
|
role="banner"
|
|
aria-label="Install Nexus app"
|
|
>
|
|
<p className="text-sm font-semibold mb-1">Add Nexus to your home screen</p>
|
|
<p className="text-xs text-muted-foreground mb-3">
|
|
{isIOS
|
|
? "Open the Share menu and tap 'Add to Home Screen'"
|
|
: "Get the full experience — launch instantly, works offline."}
|
|
</p>
|
|
<div className="flex items-center gap-2">
|
|
{!isIOS && (
|
|
<Button size="sm" onClick={handleInstall}>
|
|
Add to Home Screen
|
|
</Button>
|
|
)}
|
|
<Button variant="ghost" size="sm" onClick={handleDismiss}>
|
|
Not now
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|