Répond à deux besoins : permettre à chaque foyer de choisir quelles sources apparaissent dans ses onglets de recettes, et distinguer les sources à API officielle des sources scrapées. - RecipeSourceAdapter.official (booléen, sans défaut — chaque adaptateur doit le déclarer explicitement) synchronisé sur Source.official par syncRecipeSources. - HouseSource : table de jointure opt-in (House <-> Source) — aucune ligne = source masquée. Un foyer nouvellement créé ne voit aucune source tant qu'il ne les active pas explicitement. - GET /reference/sources (catalogue des sources implémentées, avec le flag officiel). - GET/PATCH /house/current/sources (lecture/remplacement complet des sources activées par le foyer courant). - recipe.service.ts : sourceVisibilityWhere() filtre désormais TOUS les onglets (perso/foyer/publique/favoris) — une recette sans source reste toujours visible ; une recette importée ne l'est que si sa source est activée pour le foyer du viewer. Un viewer sans foyer ne voit aucune recette sourcée. Côté web : - Nouvelle étape /onboarding/sources dans le wizard d'inscription, atteinte uniquement si un foyer vient d'être créé/rejoint (sinon on saute direct aux allergènes) ; s'auto-saute aussi si aucune source n'est encore implémentée (catalogue vide aujourd'hui). - Nouvelle section « Sources de recettes » dans /parametres/foyer (masquée dans les mêmes conditions), avec sauvegarde à la volée (même pattern que les autres préférences hot-saved). - SourceSelect (features/house/), grille de cases à cocher avec badge officiel/non-officielle, sur le même principe qu'AllergySelect. 172 tests backend passent (dont 25 nouveaux). Build et lint propres. Vérifié manuellement en navigateur : le parcours d'onboarding saute bien l'étape sources (catalogue vide) et affiche « 4 sur 4 » quand un foyer a été créé ; la section paramètres reste invisible tant qu'aucune source n'existe. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
57 lines
2.1 KiB
TypeScript
57 lines
2.1 KiB
TypeScript
import type { SourceView } from "@batch-cooking/shared";
|
|
import { useTranslation } from "react-i18next";
|
|
import { CheckboxOption } from "../../components/ui/Checkbox";
|
|
import "./house-forms.scss";
|
|
|
|
interface SourceSelectProps {
|
|
legend: string;
|
|
sources: SourceView[];
|
|
value: number[];
|
|
onChange: (sourceIds: number[]) => void;
|
|
}
|
|
|
|
/**
|
|
* Multi-select (checkbox grid) for which recipe sources a household sees
|
|
* in its recipe tabs — same "grid of `CheckboxOption`s" shape as
|
|
* `AllergySelect` (`features/profile/`), but household-scoped rather than
|
|
* per-user, hence its own `features/house/` home. Used both by the
|
|
* onboarding wizard's source step and the `/parametres/foyer` settings
|
|
* page. An empty `value` is this household's normal starting state
|
|
* (opt-in — see `HouseSource` in schema.prisma), not an incomplete one.
|
|
*
|
|
* Unlike `AllergySelect`, a source's display text is its own `name`
|
|
* (`SourceView.name` — a proper noun like "Marmiton"), not resolved
|
|
* through `catalog.<domain>.<key>` i18n — nothing to translate. The
|
|
* `official`/`unofficial` badge next to it is what *is* translated, so a
|
|
* household can tell an official API apart from a scraped site before
|
|
* deciding whether to trust it.
|
|
*/
|
|
export function SourceSelect({ legend, sources, value, onChange }: SourceSelectProps) {
|
|
const { t } = useTranslation();
|
|
|
|
function toggle(id: number) {
|
|
onChange(value.includes(id) ? value.filter((existing) => existing !== id) : [...value, id]);
|
|
}
|
|
|
|
return (
|
|
<fieldset className="source-select">
|
|
<legend>{legend}</legend>
|
|
{sources.map((source) => {
|
|
const checked = value.includes(source.id);
|
|
return (
|
|
<CheckboxOption
|
|
key={source.id}
|
|
checked={checked}
|
|
onChange={() => toggle(source.id)}
|
|
className="source-select__option"
|
|
>
|
|
{source.name}
|
|
<span className={`source-select__badge ${source.official ? "is-official" : ""}`}>
|
|
{t(source.official ? "household.sources.official" : "household.sources.unofficial")}
|
|
</span>
|
|
</CheckboxOption>
|
|
);
|
|
})}
|
|
</fieldset>
|
|
);
|
|
}
|