"use client"; import { useState, useCallback, useEffect, useRef } from "react"; import { Heart } from "lucide-react"; import { useGeoDetect } from "@/lib/hooks/useGeoDetect"; import { ThemeProvider } from "./ThemeProvider"; import { ThemeToggle } from "./ThemeToggle"; import { Sidebar, NavView } from "./chat/Sidebar"; import { RightPanel } from "./chat/RightPanel"; import { NotificationBell } from "./chat/NotificationCenter"; import { ChatView } from "./views/ChatView"; import { HomeView } from "./views/HomeView"; import { EmergencyView } from "./views/EmergencyView"; import { TopicsView } from "./views/TopicsView"; import { SettingsView } from "./views/SettingsView"; import { RecordsView } from "./views/RecordsView"; import { HistoryView } from "./views/HistoryView"; import { MedicationsView } from "./views/MedicationsView"; import { AppointmentsView } from "./views/AppointmentsView"; import { VitalsView } from "./views/VitalsView"; import { HealthDashboard } from "./views/HealthDashboard"; import { ScheduleView } from "./views/ScheduleView"; import { WelcomeScreen } from "./WelcomeScreen"; import { useSettings } from "@/lib/hooks/useSettings"; import { useChat } from "@/lib/hooks/useChat"; import { useHealthStore } from "@/lib/hooks/useHealthStore"; import { useFamilyHealth } from "@/lib/hooks/useFamilyHealth"; import { useNotifications } from "@/lib/hooks/useNotifications"; import { useAuth } from "@/lib/hooks/useAuth"; import { usePasswordResetLink } from "@/lib/hooks/usePasswordResetLink"; import { LoginView } from "./views/LoginView"; import { ProfileView } from "./views/ProfileView"; import { EHRWizard } from "./views/EHRWizard"; import { MyMedicinesView } from "./views/MyMedicinesView"; import { ShareView } from "./views/ShareView"; import { FamilyHealthView } from "./views/FamilyHealthView"; import { DisclaimerBanner } from "./ui/DisclaimerBanner"; import { OfflineBanner } from "./ui/OfflineBanner"; import { InstallPrompt } from "./ui/InstallPrompt"; import { buildPatientContext, todayISO } from "@/lib/health-store"; import { t, type SupportedLanguage } from "@/lib/i18n"; export default function MedOSApp() { return ( ); } function MedOSAppInner() { const [activeNav, setActiveNav] = useState("home"); const settings = useSettings(); const auth = useAuth(); const resetLink = usePasswordResetLink(); const { messages, isTyping, error, sendMessage, clearMessages } = useChat(); // When the user lands here from a password-reset email, drop them on // the login screen with the reset step pre-filled. Done once on mount; // the hook itself clears the params from the URL so it won't re-fire. useEffect(() => { if (resetLink) setActiveNav("login"); }, [resetLink]); const health = useHealthStore(auth.token); const family = useFamilyHealth(); const notif = useNotifications(); // IP-based auto-detection. Only applies if the user hasn't manually // chosen a language yet; the manual override in Settings wins forever. const onGeo = useCallback( (g: { country: string; language: any; emergencyNumber: string }) => { settings.applyGeo(g); }, [settings], ); useGeoDetect({ skip: !settings.isLoaded || settings.explicitLanguage, onResult: onGeo, }); const handleSendMessage = (content: string) => { sendMessage(content, { preset: settings.advancedMode ? undefined : settings.preset, provider: settings.advancedMode ? settings.provider : undefined, // In advanced mode we let the server default the model; the // dedicated provider files pick their own canonical model. apiKey: settings.apiKey, userHfToken: settings.hfToken || undefined, context: { country: settings.country, language: settings.language, emergencyNumber: settings.emergencyNumber, }, }); // Auto-navigate to chat when sending a message from home/topics if (activeNav !== "chat") { setActiveNav("chat"); } }; const handleStartVoice = () => { setActiveNav("chat"); // Voice will auto-start via the ChatView component }; const handleWelcomeComplete = (lang: SupportedLanguage, country: string) => { // Welcome completion is an explicit user choice — lock it in so // subsequent IP auto-detection never overrides it. settings.setLanguageExplicit(lang); settings.setCountryExplicit(country); settings.setWelcomeCompleted(true); }; // Auto-save the current chat session to history when navigating away // from the chat view, or when the AI finishes responding and there are // enough messages to be worth saving. const lastSavedCount = useRef(0); useEffect(() => { const userMsgs = messages.filter((m) => m.role === "user"); if ( userMsgs.length > 0 && messages.length >= 3 && messages.length !== lastSavedCount.current && !isTyping ) { lastSavedCount.current = messages.length; health.saveSession({ date: new Date().toISOString(), preview: userMsgs[0].content.slice(0, 120), messageCount: messages.length, topic: undefined, }); } }, [messages.length, isTyping]); // eslint-disable-line react-hooks/exhaustive-deps const handleNavigate = (view: string) => { setActiveNav(view as NavView); }; // Text size class const textSizeClass = settings.textSize === "large" ? "text-lg" : settings.textSize === "small" ? "text-sm" : "text-base"; const renderContent = () => { switch (activeNav) { case "home": return ( ); case "emergency": return ( ); case "topics": return ( handleSendMessage(`Tell me about ${topic}`)} /> ); case "settings": return ( ); case "schedule": return ( ); case "health-dashboard": return ( ); case "medications": return ( ); case "appointments": return ( ); case "vitals": return ( ); case "records": return ( ); case "my-medicines": return ( { // Add to the medication schedule tracker health.addMedication({ name: med.name, dose: med.dose, frequency: "daily", times: ["08:00"], startDate: todayISO(), active: true, }); setActiveNav("medications"); }} language={settings.language} /> ); case "family-health": return ( ); case "share": return ; case "history": return ( handleSendMessage(preview)} language={settings.language} /> ); case "login": case "register": return ( { const res = await auth.login(e, p); if (res.ok) setActiveNav("home"); return res; }} onRegister={async (e, p, o) => { const res = await auth.register(e, p, o); if (res.ok && !res.needsVerification) setActiveNav("home"); return res; }} onVerifyEmail={async (code) => { const res = await auth.verifyEmail(code); if (res.ok) setActiveNav("home"); return res; }} onResendVerification={auth.resendVerification} onForgotPassword={auth.forgotPassword} onResetPassword={async (e, c, p) => { const res = await auth.resetPassword(e, c, p); if (res.ok) setActiveNav("home"); return res; }} language={settings.language} /> ); case "ehr-wizard": return ( setActiveNav("profile")} onCancel={() => setActiveNav("home")} language={settings.language} /> ); case "profile": return auth.user ? ( { auth.logout(); setActiveNav("home"); }} onExport={health.downloadAll} onOpenEHR={() => setActiveNav("ehr-wizard")} onDeleteAccount={async (password, confirmEmail) => { const res = await auth.deleteMe(password, confirmEmail); if (res.ok) { // Server already invalidated the session; useAuth wiped // local token + user. Send the user back to home. setActiveNav("home"); } return res; }} medicationCount={health.medications.length} appointmentCount={health.appointments.length} vitalCount={health.vitals.length} recordCount={health.records.length} language={settings.language} /> ) : ( { const res = await auth.login(e, p); if (res.ok) setActiveNav("profile"); return res; }} onRegister={async (e, p, o) => { const res = await auth.register(e, p, o); if (res.ok && !res.needsVerification) setActiveNav("profile"); return res; }} onVerifyEmail={auth.verifyEmail} onResendVerification={auth.resendVerification} onForgotPassword={auth.forgotPassword} onResetPassword={auth.resetPassword} language={settings.language} /> ); default: return ( setActiveNav("emergency")} /> ); } }; // Loading state if (!settings.isLoaded) { return (

{t("loading", settings.language)}

); } // Welcome screen for first-time users if (!settings.welcomeCompleted) { return ( ); } const hasActiveChat = messages.length > 1; return (
{/* Sidebar */} {/* Main Content */}
{/* Top Header — clean, mobile-first, always accessible */}
{/* Mobile logo — larger tap target */}
MedOS

{activeNav === "home" ? t("nav_home", settings.language) : activeNav === "chat" ? t("nav_ask", settings.language) : activeNav === "emergency" ? t("nav_emergency", settings.language) : activeNav === "topics" ? t("nav_topics", settings.language) : activeNav === "settings" ? t("nav_settings", settings.language) : activeNav === "family-health" ? "MedOS Family" : activeNav}

{/* The header used to host a pulsing red EmergencyCTA on every * page. It read as anxious noise on non-emergency screens and * competed with the main actions. Emergency now lives in the * sidebar's Tools group (NavItem with urgent flag) where it * stays one click away without dominating the chrome. The * deterministic safety engine still routes any R5 input to an * emergency template at the chat-route level, regardless of * what UI is visible. */}
{/* Dynamic Content Area */}
{renderContent()}
{/* Right Panel — only rendered for authenticated users. * * The right rail is for personal health context (Vitals Today, * Upcoming meds + appointments). For guests it offered no value * and competed with the left sidebar's auth card. The whole * component is now gated, eliminating the 'two sidebars feeling' * and the duplicate sign-up prompts. */} {auth.isAuthenticated && ( {}} /> )}
); }