feat(medications): give the external API its own settings group

The advanced-settings sheet crammed the external ingest endpoint into the
Data group next to CSV import/export — two unrelated audiences sharing one
frame. Pull `ApiTokensRow` into a dedicated "Externe API" group so the
developer-facing surface reads as a distinct, lowest-frequency block. New
group order: Reminders -> Lifecycle -> Data -> Externe API -> Danger.

The new group reuses the existing `SettingsGroup` bordered-card chrome, so
it stays visually consistent with its siblings. Adds the `externalApi`
group label across all six locales and updates the composition test to pin
the five-block order plus the API row's new home.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
(cherry picked from commit f9f3a1277b5aad1710815511ddf7f6b210a652b2)
This commit is contained in:
Marc-André Bombeck committed 2026-06-02 16:45:23 +02:00
1 parent f65181bbf2
commit 555138110c
8 files changed
+62 -22

No files matched your search

+2 -1
View File
@@ -1278,7 +1278,8 @@
"group": {
"data": "Daten",
"reminders": "Erinnerungen",
"lifecycle": "Lebenszyklus"
"lifecycle": "Lebenszyklus",
"externalApi": "Externe API"
},
"dataPortability": {
"import": {
+2 -1
View File
@@ -1278,7 +1278,8 @@
"group": {
"data": "Data",
"reminders": "Reminders",
"lifecycle": "Lifecycle"
"lifecycle": "Lifecycle",
"externalApi": "External API"
},
"dataPortability": {
"import": {
+2 -1
View File
@@ -1278,7 +1278,8 @@
"group": {
"data": "Datos",
"reminders": "Recordatorios",
"lifecycle": "Ciclo de vida"
"lifecycle": "Ciclo de vida",
"externalApi": "API externa"
},
"dataPortability": {
"import": {
+2 -1
View File
@@ -1278,7 +1278,8 @@
"group": {
"data": "Données",
"reminders": "Rappels",
"lifecycle": "Cycle de vie"
"lifecycle": "Cycle de vie",
"externalApi": "API externe"
},
"dataPortability": {
"import": {
+2 -1
View File
@@ -1278,7 +1278,8 @@
"group": {
"data": "Dati",
"reminders": "Promemoria",
"lifecycle": "Ciclo di vita"
"lifecycle": "Ciclo di vita",
"externalApi": "API esterna"
},
"dataPortability": {
"import": {
+2 -1
View File
@@ -1278,7 +1278,8 @@
"group": {
"data": "Dane",
"reminders": "Przypomnienia",
"lifecycle": "Cykl życia"
"lifecycle": "Cykl życia",
"externalApi": "Zewnętrzne API"
},
"dataPortability": {
"import": {
@@ -1,13 +1,14 @@
/**
* v1.7.0 — AdvancedSettingsSheet composition contract.
*
* The sheet hosts four blocks — Reminders → Lifecycle → Data → Danger
* zone — composed from bare section bodies. `<ResponsiveSheet>`
* wraps Radix portals that `renderToStaticMarkup` does not materialise,
* so we mock it to a passthrough that renders children only when `open`
* and echoes `contentWidth`. The bodies are mocked to ordered markers.
* The test pins:
* - the four blocks render in the documented order;
* The sheet hosts five blocks — Reminders → Lifecycle → Data →
* Externe API → Danger zone — composed from bare section bodies.
* `<ResponsiveSheet>` wraps Radix portals that `renderToStaticMarkup`
* does not materialise, so we mock it to a passthrough that renders
* children only when `open` and echoes `contentWidth`. The bodies are
* mocked to ordered markers. The test pins:
* - the five blocks render in the documented order;
* - the external-API row lives in its own group, not in Data;
* - the sheet opens at the `2xl` width token;
* - `onRequestPhaseSheet` threads through to the phases row;
* - `onOpenImport` wires the import button (co-located with export);
@@ -107,7 +108,7 @@ function baseProps(
}
describe("AdvancedSettingsSheet (v1.7.0)", () => {
it("renders the four blocks in Reminders → Lifecycle → Data → Danger order", () => {
it("renders the five blocks in Reminders → Lifecycle → Data → Externe API → Danger order", () => {
const html = renderToStaticMarkup(
<AdvancedSettingsSheet
{...baseProps(true, { onRequestPhaseSheet: () => {} })}
@@ -116,11 +117,26 @@ describe("AdvancedSettingsSheet (v1.7.0)", () => {
const reminders = html.indexOf("advanced-group-reminders");
const lifecycle = html.indexOf("advanced-group-lifecycle");
const data = html.indexOf("advanced-group-data");
const externalApi = html.indexOf("advanced-group-external-api");
const danger = html.indexOf("mock-danger");
expect(reminders).toBeGreaterThan(-1);
expect(lifecycle).toBeGreaterThan(reminders);
expect(data).toBeGreaterThan(lifecycle);
expect(danger).toBeGreaterThan(data);
expect(externalApi).toBeGreaterThan(data);
expect(danger).toBeGreaterThan(externalApi);
});
it("homes the external-API row in its own group, not in the Data group", () => {
const html = renderToStaticMarkup(
<AdvancedSettingsSheet {...baseProps(true)} />,
);
// The API row marker must sit after the external-API group opens,
// not inside the Data group it used to share.
const dataGroup = html.indexOf("advanced-group-data");
const externalApiGroup = html.indexOf("advanced-group-external-api");
const apiRow = html.indexOf("mock-api-tokens");
expect(apiRow).toBeGreaterThan(externalApiGroup);
expect(externalApiGroup).toBeGreaterThan(dataGroup);
});
it("opens at the 2xl width token", () => {
@@ -6,15 +6,21 @@
* grid.
* v1.8.6 — rebuilt as a single column. The two-column data|reminders
* grid read as overloaded; the sheet now narrows to `2xl` (~672px) and
* stacks four bordered section cards in most-used-first order:
* Reminders → Lifecycle → Data → Danger. One spacing rule throughout —
* `space-y-4` between cards, `divide-y` + `py-3` rows inside each card
* (the `<SettingsGroup>` card owns the dividers).
* stacks bordered section cards in most-used-first order. One spacing
* rule throughout — `space-y-4` between cards, `divide-y` + `py-3` rows
* inside each card (the `<SettingsGroup>` card owns the dividers).
*
* v1.9.0 — the Data group split. It had carried both operator data
* portability and the developer-facing external API in one frame; the
* API endpoint now sits in its own `Externe API` group so the two
* audiences no longer share a card. Order: Reminders → Lifecycle →
* Data → Externe API → Danger.
*
* - Reminders: notifications switch + reminder window (grace).
* - Lifecycle: pause/resume + end course + phases.
* - Data: intake Import + medications CSV Export (co-located) +
* external-API endpoint / token.
* - Data: intake Import + medications CSV Export (co-located).
* - Externe API: the per-medication ingest endpoint + token, with the
* request examples collapsed by default.
* - Danger zone: purge intake history + delete medication, set off by
* its own destructive-tinted card so an irreversible action never
* sits next to a routine toggle.
@@ -142,7 +148,9 @@ export function AdvancedSettingsSheet({
)}
</SettingsGroup>
{/* DATA — import + export + external API */}
{/* DATA — import + export. The external API lives in its own
group below; this group stays scoped to operator data
portability so the two audiences don't share one frame. */}
<SettingsGroup
label={t("medications.detail.advanced.group.data")}
dataSlot="advanced-group-data"
@@ -153,6 +161,16 @@ export function AdvancedSettingsSheet({
onOpenImport={onOpenImport}
/>
</div>
</SettingsGroup>
{/* EXTERNAL API — the per-medication ingest endpoint + token.
Pulled out of the Data group into its own frame so the
developer/integration surface reads as a distinct, lowest-
frequency block right before the danger zone. */}
<SettingsGroup
label={t("medications.detail.advanced.group.externalApi")}
dataSlot="advanced-group-external-api"
>
<div className="py-3">
<ApiTokensRow
medicationId={medicationId}