From c77d5252a6d365b52014a4623c10f9692c73ac09 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Bombeck?= Date: Fri, 15 May 2026 18:56:08 +0200 Subject: [PATCH] feat(settings): pair the API section tables with a mobile card-list rendering CF-19 follow-up: the endpoint catalogue, active-token table, and revoked-token table on `/settings/api` each gain a `md:hidden` card-list partner so phones never have to horizontal-scroll the tabular layout. Desktop tables stay verbatim behind `hidden md:block`. The active-token cards expose a full-width revoke button that clears the 44pt tap-target floor. --- .../__tests__/api-section-responsive.test.tsx | 148 +++++++++ src/components/settings/api-section.tsx | 293 ++++++++++++++---- 2 files changed, 389 insertions(+), 52 deletions(-) create mode 100644 src/components/settings/__tests__/api-section-responsive.test.tsx diff --git a/src/components/settings/__tests__/api-section-responsive.test.tsx b/src/components/settings/__tests__/api-section-responsive.test.tsx new file mode 100644 index 000000000..66138197e --- /dev/null +++ b/src/components/settings/__tests__/api-section-responsive.test.tsx @@ -0,0 +1,148 @@ +import { describe, expect, it, vi } from "vitest"; +import { renderToStaticMarkup } from "react-dom/server"; + +/** + * v1.4.27 MB5 — `/settings/api` mobile card-list parity. + * + * The section ships three tables: endpoint catalogue, active tokens, + * revoked tokens. Each table now renders twice: + * - desktop `` wrapped in `hidden md:block` + * - mobile `
{ + const html = render(); + expect(html).toContain('data-testid="settings-api-endpoints-mobile-list"'); + // The mobile
@@ -89,19 +105,52 @@ function ApiEndpointsCard() { - - - - - - + {endpoints.map((endpoint) => ( + + + + + + + ))}
POST/api/ingest/medication - Authorization: Bearer hlk_... - - {`{ "medicationName": "...", "scheduledFor": "..." }`} -
{endpoint.method}{endpoint.path}{endpoint.auth}{endpoint.example}
+ + {/* Mobile card list — each endpoint stacks its columns vertically + with a labelled meta row. No horizontal scroll. */} + ); } @@ -236,7 +285,8 @@ function ApiTokensCard() {

{t("settings.activeTokensTitle")}

-
+ {/* Desktop table — verbatim layout for md+, hidden on phones. */} +
@@ -342,6 +392,103 @@ function ApiTokensCard() {
+ + {/* Mobile card list — each row stacks its columns vertically + with explicit labels so the data is readable inside a + narrow viewport. The revoke action stays full-width to + clear the 44pt tap-target floor. */} + {activeTokens.length === 0 ? ( +

+ {t("settings.noActiveTokens")} +

+ ) : ( + + )}
{revokedTokens.length > 0 && ( @@ -359,47 +506,89 @@ function ApiTokensCard() { })} {showRevokedTokens && ( -
- - - - - - - - - - - {revokedTokens.map((tok, index) => ( - - - - - + <> + {/* Desktop table — verbatim layout for md+. */} +
+
- {t("settings.tokenTableName")} - - {t("settings.tokenTablePermissions")} - - {t("settings.tokenTableCreated")} - - {t("settings.tokenTableLastUsed")} -
{tok.name} - {tok.permissions.join(", ")} - - {formatDate(tok.createdAt)} - - {tok.lastUsedAt - ? formatDateTime(tok.lastUsedAt) - : t("settings.tokenNeverUsed")} -
+ + + + + + - ))} - -
+ {t("settings.tokenTableName")} + + {t("settings.tokenTablePermissions")} + + {t("settings.tokenTableCreated")} + + {t("settings.tokenTableLastUsed")} +
-
+ + + {revokedTokens.map((tok, index) => ( + + {tok.name} + + {tok.permissions.join(", ")} + + + {formatDate(tok.createdAt)} + + + {tok.lastUsedAt + ? formatDateTime(tok.lastUsedAt) + : t("settings.tokenNeverUsed")} + + + ))} + + + + + {/* Mobile card list — stacked meta layout, no + horizontal scroll. Revoked tokens are read-only so + no action footer is needed. */} + + )} )}