import React, { useState, useEffect } from 'react'; import './App.css'; import HomeView from './views/HomeView'; import SearchView from './views/SearchView'; import ProductView from './views/ProductView'; import ScannerView from './views/ScannerView'; import AlertsView from './views/AlertsView'; import ProfileView from './views/ProfileView'; import AdminView from './views/AdminView'; import LoginModal from './components/LoginModal'; import ForgotPasswordModal from './components/ForgotPasswordModal'; import ResetPasswordView from './views/ResetPasswordView'; import SavedNotifications from './components/SavedNotifications'; import BottomNav from './components/BottomNav'; import CookieBanner from './components/CookieBanner'; import HealthConsentModal from './components/HealthConsentModal'; import PrivacyView from './views/PrivacyView'; import { getFaro } from './utils/faro'; import { hasConsentChoice, getLocalConsents, saveConsents, fetchServerConsents } from './utils/consent'; function App() { const [screen, setScreen] = useState('home'); const [currentUser, setCurrentUser] = useState(null); const [authChecked, setAuthChecked] = useState(false); const [showLogin, setShowLogin] = useState(false); const [showForgot, setShowForgot] = useState(false); const [forgotMode, setForgotMode] = useState('password'); const [resetToken, setResetToken] = useState(null); const [showSaved, setShowSaved] = useState(false); const [badgeCount, setBadgeCount] = useState(0); const [prescriptionSearch, setPrescriptionSearch] = useState(''); const [productScreen, setProductScreen] = useState(null); const [showCookieBanner, setShowCookieBanner] = useState(!hasConsentChoice()); const [showHealthConsent, setShowHealthConsent] = useState(false); const [pendingTsiScan, setPendingTsiScan] = useState(null); const [consents, setConsents] = useState(getLocalConsents); const [screenSize, setScreenSize] = useState({ width: window.innerWidth, height: window.innerHeight }); // Theme: 'auto' | 'light' | 'dark' const [theme, setThemeState] = useState(() => { return localStorage.getItem('ff-theme') || 'auto'; }); // Device detection const isMobile = screenSize.width <= 768; const isTablet = screenSize.width > 768 && screenSize.width <= 1024; const isDesktop = screenSize.width > 1024; // Apply theme to document useEffect(() => { const mq = window.matchMedia('(prefers-color-scheme: dark)'); function applyTheme() { const isDark = theme === 'dark' || (theme === 'auto' && mq.matches); document.documentElement.setAttribute('data-theme', isDark ? 'dark' : 'light'); } applyTheme(); if (theme === 'auto') { mq.addEventListener('change', applyTheme); return () => mq.removeEventListener('change', applyTheme); } }, [theme]); function setTheme(newTheme) { setThemeState(newTheme); localStorage.setItem('ff-theme', newTheme); } useEffect(() => { // Set initial screen size const handleResize = () => { setScreenSize({ width: window.innerWidth, height: window.innerHeight }); }; const params = new URLSearchParams(window.location.search); const token = params.get('token'); if (token) { setResetToken(token); setScreen('reset-password'); } fetch('/api/auth/check') .then(r => r.json()) .then(data => { if (data.authenticated) setCurrentUser(data.user); }) .catch((err) => { console.warn('[auth/check]', err); getFaro()?.pushError(err, { type: 'network', url: '/api/auth/check' }); }) .finally(() => setAuthChecked(true)); window.addEventListener('resize', handleResize); return () => window.removeEventListener('resize', handleResize); }, []); useEffect(() => { if (!currentUser) { setBadgeCount(0); return; } let cancelled = false; fetch('/api/notifications/mine', { credentials: 'include' }) .then(r => r.ok ? r.json() : null) .then(data => { if (cancelled || !data) return; const count = (data.global?.length || 0) + (data.pharmacy?.length || 0); setBadgeCount(count); }) .catch((err) => { console.warn('[notifications/mine]', err); getFaro()?.pushError(err, { type: 'network', url: '/api/notifications/mine' }); }); return () => { cancelled = true; }; }, [currentUser]); useEffect(() => { if (currentUser) { fetchServerConsents().then(serverConsents => { if (serverConsents) setConsents(serverConsents); }); } }, [currentUser]); function refreshBadgeCount() { if (!currentUser) return; fetch('/api/notifications/mine', { credentials: 'include' }) .then(r => r.ok ? r.json() : null) .then(data => { if (!data) return; setBadgeCount((data.global?.length || 0) + (data.pharmacy?.length || 0)); }) .catch((err) => { console.warn('[refreshBadge]', err); getFaro()?.pushError(err, { type: 'network', url: '/api/notifications/mine' }); }); } function handleLogin(user) { setCurrentUser(user); setShowLogin(false); } async function handleLogout() { await fetch('/api/auth/logout', { method: 'POST' }).catch(() => {}); setCurrentUser(null); setScreen('home'); } function handleProfileSaved(updated) { setCurrentUser(prev => ({ ...prev, ...updated })); } function handleForgotPassword(mode) { setForgotMode(mode); setShowLogin(false); setShowForgot(true); } function handleResetComplete() { setResetToken(null); setShowLogin(true); window.history.replaceState({}, '', '/'); } async function handleCookieConsent(newConsents) { const saved = await saveConsents(newConsents); setConsents(saved); setShowCookieBanner(false); } function handleTsiScanRequest(scanFn) { if (consents.health_data) { scanFn(); } else { setPendingTsiScan(() => scanFn); setShowHealthConsent(true); } } function handleHealthConsentAccept() { saveConsents({ ...consents, health_data: true }); setConsents(prev => ({ ...prev, health_data: true })); setShowHealthConsent(false); if (pendingTsiScan) { pendingTsiScan(); setPendingTsiScan(null); } } function handleHealthConsentCancel() { setShowHealthConsent(false); setPendingTsiScan(null); } function handleAdminClick() { setScreen('admin'); } const isLoggedIn = Boolean(currentUser); function handleNavChange(tab) { if (tab === 'home') { setScreen('home'); return; } if (tab === 'search') { setScreen('search'); return; } if (tab === 'scan') { setScreen('scan'); return; } if (tab === 'alerts') { if (currentUser) setScreen('alerts'); else setShowLogin(true); return; } if (tab === 'profile') { if (currentUser) setScreen('profile'); else setShowLogin(true); return; } if (tab === 'privacy') { setScreen('privacy'); return; } } let activeView; let deviceClass = isMobile ? 'mobile-view' : 'desktop-view'; switch (screen) { case 'profile': activeView = ( setShowSaved(true)} onLogout={handleLogout} onAdminClick={handleAdminClick} theme={theme} onThemeChange={setTheme} /> ); break; case 'alerts': activeView = ( { setPrescriptionSearch(name); setScreen('search'); }} /> ); break; case 'search': activeView = ( setShowLogin(true)} initialQuery={prescriptionSearch} onNavigateToProduct={(source, id) => { setProductScreen({ source, id }); setScreen('product'); }} /> ); break; case 'product': activeView = ( { setProductScreen(null); setScreen('search'); }} currentUser={currentUser} onLoginRequest={() => setShowLogin(true)} /> ); break; case 'scan': activeView = ( setScreen('home')} onSelectMedicine={(name) => { setPrescriptionSearch(name); setScreen('search'); }} onTsiScanRequest={handleTsiScanRequest} consents={consents} /> ); break; case 'reset-password': activeView = ( ); break; case 'admin': activeView = ; break; case 'privacy': activeView = setScreen('home')} />; break; default: activeView = ( setShowLogin(true)} onScanClick={() => setScreen('scan')} onSearchClick={() => setScreen('search')} /> ); break; } return (
{activeView}
{showLogin && ( setShowLogin(false)} onForgotPassword={handleForgotPassword} /> )} {showForgot && ( setShowForgot(false)} onBackToLogin={() => { setShowForgot(false); setShowLogin(true); }} /> )} {showSaved && currentUser && ( setShowSaved(false)} onNotificationChange={refreshBadgeCount} /> )} {showCookieBanner && ( { setShowCookieBanner(false); setScreen('privacy'); }} /> )} {showHealthConsent && ( )}
); } export default App;