import type { PlanningView } from "@batch-cooking/shared"; import { useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; import { apiClient } from "../api/client"; import "./HomePage.scss"; /** Load state for the `GET /planning/current` call — a discriminated union so a stale/impossible combination (e.g. "loading" with data) can't be represented. */ type PlanningState = | { status: "loading" } | { status: "loaded"; planning: PlanningView | null } | { status: "error" }; /** * Landing page for an authenticated visitor — the household's current * planning. Behind {@link RequireAuth} (via `AppLayout`), so this only * renders once a session is confirmed; the planning itself still has to be * fetched separately, hence the loading/error/empty/loaded states below. * `null` from the API is a normal, common state (no planning created yet), * not an error — see `apps/api`'s `planning.service.ts`. */ export function HomePage() { const { t } = useTranslation(); const [state, setState] = useState({ status: "loading" }); useEffect(() => { // Guards against setting state after unmount (e.g. the user navigates // away before the request resolves) — no cleanup-worthy resource here, // just avoids a "set state on unmounted component" warning. let cancelled = false; apiClient .getCurrentPlanning() .then((planning) => { if (!cancelled) setState({ status: "loaded", planning }); }) .catch(() => { if (!cancelled) setState({ status: "error" }); }); return () => { cancelled = true; }; }, []); return (

{t("home.title")}

{state.status === "loading" &&

{t("home.loading")}

} {state.status === "error" && (

{t("home.error")}

)} {state.status === "loaded" && state.planning === null && (

{t("home.empty")}

)} {state.status === "loaded" && state.planning !== null && ( {state.planning.items.map((item) => ( ))}
{t("home.table.day")} {t("home.table.meal")} {t("home.table.recipe")}
{item.weekDay} {item.meal} {item.recipe.name}
)}
); }