- Zone dangereuse (compte) : le bouton "Supprimer mon compte" est rouge.
- Pages préférences/paramétrage : contenu centré et élargi (32rem -> 56rem)
au lieu de coller à gauche sur un écran large.
- Page recettes : l'onglet "Favoris" est sélectionné par défaut.
- Ajout de apps/web/cypress/e2e/recipes.cy.ts (onglets, recherche, sélection
master-detail, favori, suppression, lien nouvelle recette).
- Catalogue de référence (ingrédients/régimes/allergènes) : la colonne
`name` (le libellé français, utilisé comme clé unique) devient `key`, un
slug stable et opaque au sens produit (ex. "vegetarien", "boeuf_hache").
Le libellé lui-même déménage entièrement côté client, dans
apps/web/src/locales/fr/translation.json sous le namespace `catalog.*`,
résolu via `t(\`catalog.ingredients.${key}\`)` etc. — même schéma que
IngredientCategory/IngredientSubcategory. Migration Prisma
(rename + backfill des ~456 lignes déjà seedées), seed/service/tests API
et composants web mis à jour en conséquence.
- apps/api/src/utils/slugify.ts + scripts/generate-catalog-i18n.ts
(regénère le fichier de traduction depuis reference-seed-data.ts).
- 102 tests Mocha + 32 scénarios Cucumber passent contre la base migrée.
Note : cypress run plante dans cet environnement (le processus GPU
Chromium/Electron crash même headless, indépendamment des flags) — les
recipes.cy.ts n'ont pas pu être exécutés ici ; vérifiés par lecture du code
source des composants visés et par un passage manuel dans le navigateur de
prévisualisation.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
57 lines
2.1 KiB
TypeScript
57 lines
2.1 KiB
TypeScript
import type { AllergyView } from "@batch-cooking/shared";
|
|
import { useTranslation } from "react-i18next";
|
|
import { CheckboxOption } from "../../components/ui/Checkbox";
|
|
import "./profile-forms.scss";
|
|
|
|
interface AllergySelectProps {
|
|
legend: string;
|
|
allergies: AllergyView[];
|
|
value: number[];
|
|
onChange: (allergyIds: number[]) => void;
|
|
}
|
|
|
|
/**
|
|
* Multi-select (checkbox grid, not a native `<select multiple>` — far more
|
|
* discoverable/tappable, especially on the mobile viewport this app is
|
|
* eventually embedded into via Capacitor) for a group of allergens. Used
|
|
* both by the signup wizard's allergens step and the `/parametres/preferences`
|
|
* settings page, and rendered *twice* by each — once for allergies, once for
|
|
* intolerances (`AllergyView.kind` groups them; callers filter and pass
|
|
* two separate lists rather than this component knowing about the split).
|
|
* An empty `value` is a normal, valid state (no declared allergies, or
|
|
* this skippable step was skipped), not an incomplete one.
|
|
*
|
|
* `legend` (not a fixed internal label) — the same component serves both
|
|
* groups, only the heading differs. A `<fieldset>`/`<legend>` (not a bare
|
|
* `<label>`, which only associates with a single control) is the correct
|
|
* semantic label for a group of checkboxes.
|
|
*
|
|
* Receives `allergies` as a prop rather than fetching them itself — same
|
|
* rationale as `DietSelect`.
|
|
*/
|
|
export function AllergySelect({ legend, allergies, value, onChange }: AllergySelectProps) {
|
|
const { t } = useTranslation();
|
|
|
|
function toggle(id: number) {
|
|
onChange(value.includes(id) ? value.filter((existing) => existing !== id) : [...value, id]);
|
|
}
|
|
|
|
return (
|
|
<fieldset className="allergy-select">
|
|
<legend>{legend}</legend>
|
|
{allergies.map((allergy) => {
|
|
const checked = value.includes(allergy.id);
|
|
return (
|
|
<CheckboxOption
|
|
key={allergy.id}
|
|
checked={checked}
|
|
onChange={() => toggle(allergy.id)}
|
|
className="allergy-select__option"
|
|
>
|
|
{t(`catalog.allergens.${allergy.key}`)}
|
|
</CheckboxOption>
|
|
);
|
|
})}
|
|
</fieldset>
|
|
);
|
|
}
|