2026-10-06 12:45:05 -04:00
|
|
|
import { useState, type ReactNode } from 'react'
|
2026-10-04 08:46:40 -04:00
|
|
|
import { QueryClientProvider } from '@tanstack/react-query'
|
2026-10-02 22:26:34 -04:00
|
|
|
import { Route, Routes } from 'react-router'
|
2026-10-04 08:46:40 -04:00
|
|
|
import { ApiProvider, createQueryClient, type ApiClient } from './api/index.ts'
|
|
|
|
|
import { AuthBootstrap } from './AuthBootstrap.tsx'
|
2026-10-09 14:01:14 -04:00
|
|
|
import { DinnerDetailPage } from './dinners/DinnerDetailPage.tsx'
|
2026-10-07 05:53:22 -04:00
|
|
|
import { DiscoverPage } from './discover/DiscoverPage.tsx'
|
2026-10-09 10:38:36 -04:00
|
|
|
import { CookFromPantryPage } from './ideas/CookFromPantryPage.tsx'
|
|
|
|
|
import { QuickDinnersPage } from './ideas/QuickDinnersPage.tsx'
|
2026-10-06 21:28:23 -04:00
|
|
|
import { ONBOARDING_PATH, OnboardingGate } from './onboarding/OnboardingGate.tsx'
|
|
|
|
|
import { OnboardingPage } from './onboarding/OnboardingPage.tsx'
|
2026-10-03 18:40:34 -04:00
|
|
|
import { PlaceholderPage } from './pages/PlaceholderPage.tsx'
|
2026-10-07 16:53:23 -04:00
|
|
|
import { PlanPage } from './plan/PlanPage.tsx'
|
2026-10-05 15:22:37 -04:00
|
|
|
import { OfflinePill } from './pwa/OfflinePill.tsx'
|
|
|
|
|
import { UpdatePrompt, type UpdateContainer } from './pwa/UpdatePrompt.tsx'
|
2026-10-06 12:45:05 -04:00
|
|
|
import { AISettingsPage } from './settings/AISettingsPage.tsx'
|
2026-10-08 03:13:21 -04:00
|
|
|
import { CalendarSettingsPage } from './settings/CalendarSettingsPage.tsx'
|
2026-10-06 12:45:05 -04:00
|
|
|
import { HouseholdSettingsPage } from './settings/HouseholdSettingsPage.tsx'
|
2026-10-09 18:04:05 -04:00
|
|
|
import { NotificationsPage } from './settings/NotificationsPage.tsx'
|
2026-10-06 21:28:23 -04:00
|
|
|
import { RegularsSettingsPage } from './settings/RegularsSettingsPage.tsx'
|
2026-10-06 12:45:05 -04:00
|
|
|
import { SettingsPage } from './settings/SettingsPage.tsx'
|
2026-10-03 18:40:34 -04:00
|
|
|
import { AppShell } from './shell/AppShell.tsx'
|
2026-10-08 15:16:51 -04:00
|
|
|
import { SupplyRunSync } from './shopping/api.ts'
|
|
|
|
|
import { SupplyArchivePage } from './shopping/SupplyArchivePage.tsx'
|
|
|
|
|
import { SupplyRunPage } from './shopping/SupplyRunPage.tsx'
|
2026-10-02 22:26:34 -04:00
|
|
|
|
2026-10-03 18:40:34 -04:00
|
|
|
// Every route the app serves, with the heading its placeholder page shows.
|
|
|
|
|
export const routes = [
|
|
|
|
|
{ path: '/', title: 'Home' },
|
|
|
|
|
{ path: '/onboarding', title: 'Onboarding' },
|
|
|
|
|
{ path: '/plan', title: 'Plan' },
|
|
|
|
|
{ path: '/plan/shopping', title: 'Supply run' },
|
|
|
|
|
{ path: '/plan/shopping/archive', title: 'Supply archive' },
|
|
|
|
|
{ path: '/discover', title: 'Discover' },
|
|
|
|
|
{ path: '/quick', title: 'Quick dinners' },
|
|
|
|
|
{ path: '/cook-from-pantry', title: 'Cook from what I have' },
|
|
|
|
|
{ path: '/dinner/:id', title: 'Dinner' },
|
|
|
|
|
{ path: '/settings', title: 'Settings' },
|
|
|
|
|
{ path: '/settings/notifications', title: 'Notifications' },
|
|
|
|
|
{ path: '/settings/ai', title: 'AI assistant' },
|
|
|
|
|
{ path: '/settings/household', title: 'Household' },
|
|
|
|
|
{ path: '/settings/regulars', title: 'Regulars' },
|
|
|
|
|
{ path: '/settings/calendar', title: 'Calendar' },
|
|
|
|
|
] as const
|
2026-10-02 22:26:34 -04:00
|
|
|
|
2026-10-06 12:45:05 -04:00
|
|
|
// The screens built so far; every other route shows its placeholder page.
|
|
|
|
|
const screens: Partial<Record<(typeof routes)[number]['path'], ReactNode>> = {
|
2026-10-07 16:53:23 -04:00
|
|
|
'/plan': <PlanPage />,
|
2026-10-08 15:16:51 -04:00
|
|
|
'/plan/shopping': <SupplyRunPage />,
|
|
|
|
|
'/plan/shopping/archive': <SupplyArchivePage />,
|
2026-10-07 05:53:22 -04:00
|
|
|
'/discover': <DiscoverPage />,
|
2026-10-09 10:38:36 -04:00
|
|
|
'/quick': <QuickDinnersPage />,
|
|
|
|
|
'/cook-from-pantry': <CookFromPantryPage />,
|
2026-10-09 14:01:14 -04:00
|
|
|
'/dinner/:id': <DinnerDetailPage />,
|
2026-10-06 12:45:05 -04:00
|
|
|
'/settings': <SettingsPage />,
|
2026-10-09 18:04:05 -04:00
|
|
|
'/settings/notifications': <NotificationsPage />,
|
2026-10-06 12:45:05 -04:00
|
|
|
'/settings/ai': <AISettingsPage />,
|
|
|
|
|
'/settings/household': <HouseholdSettingsPage />,
|
2026-10-06 21:28:23 -04:00
|
|
|
'/settings/regulars': <RegularsSettingsPage />,
|
2026-10-08 03:13:21 -04:00
|
|
|
'/settings/calendar': <CalendarSettingsPage />,
|
2026-10-06 12:45:05 -04:00
|
|
|
}
|
|
|
|
|
|
2026-10-04 08:46:40 -04:00
|
|
|
// AppRoutes is the shell and its pages, shown once /api/v1/me has loaded.
|
2026-10-06 21:28:23 -04:00
|
|
|
// Onboarding is a full screen of its own, outside the shell; until it is
|
|
|
|
|
// finished, OnboardingGate sends every other route to it.
|
2026-10-04 08:46:40 -04:00
|
|
|
export function AppRoutes() {
|
2026-10-02 22:26:34 -04:00
|
|
|
return (
|
2026-10-04 08:46:40 -04:00
|
|
|
<Routes>
|
2026-10-06 21:28:23 -04:00
|
|
|
<Route element={<OnboardingGate />}>
|
|
|
|
|
<Route path={`${ONBOARDING_PATH}/*`} element={<OnboardingPage />} />
|
|
|
|
|
<Route element={<AppShell />}>
|
|
|
|
|
{routes
|
|
|
|
|
.filter(({ path }) => path !== ONBOARDING_PATH)
|
|
|
|
|
.map(({ path, title }) => (
|
|
|
|
|
<Route key={path} path={path} element={screens[path] ?? <PlaceholderPage title={title} />} />
|
|
|
|
|
))}
|
|
|
|
|
</Route>
|
2026-10-04 08:46:40 -04:00
|
|
|
</Route>
|
|
|
|
|
</Routes>
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export type AppProps = {
|
|
|
|
|
api: ApiClient
|
2026-10-05 15:22:37 -04:00
|
|
|
// navigator.serviceWorker, or undefined where there is none (and in tests).
|
|
|
|
|
serviceWorker: UpdateContainer | undefined
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function reloadPage() {
|
|
|
|
|
window.location.reload()
|
2026-10-04 08:46:40 -04:00
|
|
|
}
|
|
|
|
|
|
2026-10-06 12:45:05 -04:00
|
|
|
// App expects to be rendered by a data router's splat route
|
|
|
|
|
// (createBrowserRouter in main.tsx, createMemoryRouter in tests), so pages can
|
|
|
|
|
// block navigation.
|
2026-10-05 15:22:37 -04:00
|
|
|
export default function App({ api, serviceWorker }: AppProps) {
|
2026-10-04 08:46:40 -04:00
|
|
|
const [queryClient] = useState(createQueryClient)
|
|
|
|
|
return (
|
|
|
|
|
<ApiProvider client={api}>
|
|
|
|
|
<QueryClientProvider client={queryClient}>
|
|
|
|
|
<AuthBootstrap>
|
2026-10-08 15:16:51 -04:00
|
|
|
<SupplyRunSync />
|
2026-10-04 08:46:40 -04:00
|
|
|
<AppRoutes />
|
|
|
|
|
</AuthBootstrap>
|
2026-10-05 15:22:37 -04:00
|
|
|
<OfflinePill />
|
|
|
|
|
<UpdatePrompt container={serviceWorker} reload={reloadPage} />
|
2026-10-04 08:46:40 -04:00
|
|
|
</QueryClientProvider>
|
|
|
|
|
</ApiProvider>
|
2026-10-02 22:26:34 -04:00
|
|
|
)
|
|
|
|
|
}
|