- Create /home route with SEO metadata and JSON-LD structured data - Add hero section with dual CTAs (sign-up and PWA install) - Add features section showcasing dual tracking, health timeline, achievements, savings - Add animated phone demo with rotating screens (auto-advance, pause on hover) - Add final CTA section and footer with affiliate disclosure - Extract usePWAInstall hook for reusable PWA install logic - Enhance theme-context with system preference auto-detection Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
79 lines
2.4 KiB
TypeScript
79 lines
2.4 KiB
TypeScript
'use client';
|
|
|
|
import { useState, useEffect, useCallback } from 'react';
|
|
|
|
interface BeforeInstallPromptEvent extends Event {
|
|
prompt: () => Promise<void>;
|
|
userChoice: Promise<{ outcome: 'accepted' | 'dismissed' }>;
|
|
}
|
|
|
|
export interface PWAInstallState {
|
|
isInstallable: boolean;
|
|
isStandalone: boolean;
|
|
isIOS: boolean;
|
|
isAndroid: boolean;
|
|
needsManualInstructions: boolean;
|
|
promptInstall: () => Promise<{ outcome: 'accepted' | 'dismissed' | 'unavailable' }>;
|
|
}
|
|
|
|
export function usePWAInstall(): PWAInstallState {
|
|
const [deferredPrompt, setDeferredPrompt] = useState<BeforeInstallPromptEvent | null>(null);
|
|
const [isIOS, setIsIOS] = useState(false);
|
|
const [isAndroid, setIsAndroid] = useState(false);
|
|
const [isStandalone, setIsStandalone] = useState(false);
|
|
const [isInstallable, setIsInstallable] = useState(false);
|
|
|
|
useEffect(() => {
|
|
// Check if already installed as PWA
|
|
const isAppInstalled =
|
|
window.matchMedia('(display-mode: standalone)').matches ||
|
|
(window.navigator as Navigator & { standalone?: boolean }).standalone === true;
|
|
setIsStandalone(isAppInstalled);
|
|
|
|
// Detect platform
|
|
const userAgent = window.navigator.userAgent.toLowerCase();
|
|
const isIOSDevice = /iphone|ipad|ipod/.test(userAgent);
|
|
const isAndroidDevice = /android/.test(userAgent);
|
|
setIsIOS(isIOSDevice);
|
|
setIsAndroid(isAndroidDevice);
|
|
|
|
// Listen for beforeinstallprompt (Android/Chrome)
|
|
const handleBeforeInstallPrompt = (e: Event) => {
|
|
e.preventDefault();
|
|
setDeferredPrompt(e as BeforeInstallPromptEvent);
|
|
setIsInstallable(true);
|
|
};
|
|
|
|
window.addEventListener('beforeinstallprompt', handleBeforeInstallPrompt);
|
|
|
|
return () => {
|
|
window.removeEventListener('beforeinstallprompt', handleBeforeInstallPrompt);
|
|
};
|
|
}, []);
|
|
|
|
const promptInstall = useCallback(async (): Promise<{ outcome: 'accepted' | 'dismissed' | 'unavailable' }> => {
|
|
if (!deferredPrompt) {
|
|
return { outcome: 'unavailable' };
|
|
}
|
|
|
|
await deferredPrompt.prompt();
|
|
const { outcome } = await deferredPrompt.userChoice;
|
|
|
|
if (outcome === 'accepted') {
|
|
setDeferredPrompt(null);
|
|
setIsInstallable(false);
|
|
}
|
|
|
|
return { outcome };
|
|
}, [deferredPrompt]);
|
|
|
|
return {
|
|
isInstallable,
|
|
isStandalone,
|
|
isIOS,
|
|
isAndroid,
|
|
needsManualInstructions: !deferredPrompt && (isIOS || isAndroid),
|
|
promptInstall,
|
|
};
|
|
}
|