install prompt support and iOS “Share → Add to Home Screen” guidance. Also added a small helper + tests to decide when/which banner variant should show, and shared copy in common.json.
92 lines
2.6 KiB
TypeScript
92 lines
2.6 KiB
TypeScript
import React from 'react';
|
|
import {
|
|
BeforeInstallPromptEvent,
|
|
getStandaloneStatus,
|
|
isIosDevice,
|
|
type InstallOutcome,
|
|
} from '../lib/installPrompt';
|
|
|
|
type InstallPromptState = {
|
|
canInstall: boolean;
|
|
isInstalled: boolean;
|
|
isStandalone: boolean;
|
|
isIos: boolean;
|
|
outcome: InstallOutcome | null;
|
|
promptInstall: () => Promise<InstallOutcome | null>;
|
|
};
|
|
|
|
export function useInstallPrompt(): InstallPromptState {
|
|
const [deferredPrompt, setDeferredPrompt] = React.useState<BeforeInstallPromptEvent | null>(null);
|
|
const [isInstalled, setIsInstalled] = React.useState(false);
|
|
const [isStandalone, setIsStandalone] = React.useState(false);
|
|
const [isIos, setIsIos] = React.useState(false);
|
|
const [outcome, setOutcome] = React.useState<InstallOutcome | null>(null);
|
|
|
|
React.useEffect(() => {
|
|
if (typeof window === 'undefined') {
|
|
return undefined;
|
|
}
|
|
|
|
const updateStandalone = () => {
|
|
const standalone = getStandaloneStatus();
|
|
setIsStandalone(standalone);
|
|
if (standalone) {
|
|
setIsInstalled(true);
|
|
}
|
|
};
|
|
|
|
setIsIos(isIosDevice(navigator.userAgent ?? ''));
|
|
updateStandalone();
|
|
|
|
const handleBeforeInstallPrompt = (event: Event) => {
|
|
event.preventDefault();
|
|
setDeferredPrompt(event as BeforeInstallPromptEvent);
|
|
};
|
|
|
|
const handleAppInstalled = () => {
|
|
setIsInstalled(true);
|
|
setDeferredPrompt(null);
|
|
setOutcome('accepted');
|
|
};
|
|
|
|
const mediaQuery = window.matchMedia?.('(display-mode: standalone)');
|
|
const handleDisplayModeChange = () => updateStandalone();
|
|
|
|
window.addEventListener('beforeinstallprompt', handleBeforeInstallPrompt);
|
|
window.addEventListener('appinstalled', handleAppInstalled);
|
|
mediaQuery?.addEventListener?.('change', handleDisplayModeChange);
|
|
|
|
return () => {
|
|
window.removeEventListener('beforeinstallprompt', handleBeforeInstallPrompt);
|
|
window.removeEventListener('appinstalled', handleAppInstalled);
|
|
mediaQuery?.removeEventListener?.('change', handleDisplayModeChange);
|
|
};
|
|
}, []);
|
|
|
|
const promptInstall = React.useCallback(async () => {
|
|
if (!deferredPrompt) {
|
|
return null;
|
|
}
|
|
|
|
await deferredPrompt.prompt();
|
|
const choice = await deferredPrompt.userChoice;
|
|
setOutcome(choice.outcome ?? 'unknown');
|
|
|
|
if (choice.outcome === 'accepted') {
|
|
setIsInstalled(true);
|
|
setDeferredPrompt(null);
|
|
}
|
|
|
|
return choice.outcome ?? 'unknown';
|
|
}, [deferredPrompt]);
|
|
|
|
return {
|
|
canInstall: Boolean(deferredPrompt),
|
|
isInstalled,
|
|
isStandalone,
|
|
isIos,
|
|
outcome,
|
|
promptInstall,
|
|
};
|
|
}
|