nicholai 890bdf13e4 Feat: Add public landing page for non-authenticated users
- 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>
2026-01-31 19:30:46 -07:00

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,
};
}