import type { SourceView } from "@batch-cooking/shared"; import { ErrorCode } from "@batch-cooking/shared"; import { type FormEvent, useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; import { useNavigate } from "react-router-dom"; import { ApiError, apiClient } from "../../api/client"; import { SourceSelect } from "../../features/house/SourceSelect"; import { errorMessageService } from "../../services/error-message.service"; import "./onboarding.scss"; /** * Third step of the post-signup onboarding wizard, routed at * `/onboarding/sources` — only reached when a household now exists (see * `OnboardingHouseholdPage`'s `goToStep`, which skips straight to * `/onboarding/allergenes` when the previous step was skipped): enabled * sources are a per-household preference (`HouseSource`, schema.prisma), * so there's nothing to attach a selection to without one. * * Starts from an empty selection — a newly created/joined household has * nothing enabled yet by design (opt-in, see `house.service.ts`'s * `getHouseSourceIds`). If no source is implemented yet (`getSources()` * comes back empty — currently always the case), there's nothing to pick * from either, so this step silently steps itself over rather than showing * an empty screen. */ export function OnboardingSourcesPage() { const { t } = useTranslation(); const navigate = useNavigate(); const [sources, setSources] = useState([]); const [sourceIds, setSourceIds] = useState([]); const [isLoading, setIsLoading] = useState(true); const [formError, setFormError] = useState(null); const [isSubmitting, setIsSubmitting] = useState(false); useEffect(() => { let cancelled = false; apiClient .getSources() .then((result) => { if (cancelled) return; if (result.length === 0) { void navigate("/onboarding/allergenes", { replace: true }); return; } setSources(result); setIsLoading(false); }) .catch(() => { // Nothing to configure sources for if we can't even list them — the // wizard shouldn't strand the visitor here over a transient failure // fetching an optional step's own data. if (!cancelled) void navigate("/onboarding/allergenes", { replace: true }); }); return () => { cancelled = true; }; }, [navigate]); async function handleSubmit(e: FormEvent) { e.preventDefault(); setFormError(null); setIsSubmitting(true); try { await apiClient.updateHouseSourceIds(sourceIds); void navigate("/onboarding/allergenes"); } catch (err) { const code = err instanceof ApiError ? err.code : ErrorCode.INTERNAL_ERROR; setFormError(errorMessageService.getLabel(code)); } finally { setIsSubmitting(false); } } return (

{t("onboarding.step", { current: 3, total: 4 })}

{t("onboarding.sources.title")}

{t("onboarding.sources.subtitle")}

{isLoading ? (

{t("onboarding.loading")}

) : ( )} {formError &&

{formError}

}
); }