From 29321f54c02cff30191b48405b1a8a1419ba3d08 Mon Sep 17 00:00:00 2001 From: julian Date: Wed, 27 May 2026 19:14:40 +0000 Subject: [PATCH] feat(web): ping API ponta-a-ponta via TanStack Query + Zod contract MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fecha loop B+C — Web consome @sar/api-interface em runtime, não só build. - Vite proxy /api → localhost:3000 (zero CORS em dev, mesma URL em prod via Nginx) - api-client.ts: fetch wrapper parseando RFC 9457 problem+json em ApiError - useApiPing: TanStack Query + PingResponseSchema.parse — drift servidor falha alto - FoundationStatus pill na Topbar (verde/vermelho/cinza + Tooltip com requestId) Validado via curl proxy:4200 → 200 ok contratual; /nope → 404 problem+json. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../components/layout/FoundationStatus.tsx | 122 ++++++++++++++++++ apps/web/src/components/layout/Topbar.tsx | 4 +- apps/web/src/lib/api-client.ts | 89 +++++++++++++ apps/web/src/lib/queries/ping.ts | 26 ++++ apps/web/vite.config.mts | 9 ++ design-artifacts/_progress/00-design-log.md | 24 ++++ 6 files changed, 273 insertions(+), 1 deletion(-) create mode 100644 apps/web/src/components/layout/FoundationStatus.tsx create mode 100644 apps/web/src/lib/api-client.ts create mode 100644 apps/web/src/lib/queries/ping.ts diff --git a/apps/web/src/components/layout/FoundationStatus.tsx b/apps/web/src/components/layout/FoundationStatus.tsx new file mode 100644 index 0000000..dbcb6b2 --- /dev/null +++ b/apps/web/src/components/layout/FoundationStatus.tsx @@ -0,0 +1,122 @@ +import { Badge, Tooltip, Typography } from 'antd'; +import { ApiError } from '../../lib/api-client'; +import { useApiPing } from '../../lib/queries/ping'; +import { brandTokens } from '../../lib/theme'; + +const { Text } = Typography; + +// Pill discreto de "fundação viva" — prova que API↔Web↔contrato Zod funcionam. +// Conscientemente mantido na Topbar enquanto o produto está em foundation; +// quando virar normal, vira indicador só em /health (Sandra/Daniel). +export function FoundationStatus() { + const { data, error, isPending, isFetching } = useApiPing(); + + if (isPending) { + return ( + + ); + } + + if (error) { + const detail = + error instanceof ApiError + ? `${error.problem.title}${error.problem.detail ? ` — ${error.problem.detail}` : ''}` + : error.message; + return ( + + } + /> + ); + } + + return ( + + } + /> + ); +} + +function Pill({ + color, + label, + tooltip, + pulse, +}: { + color: string; + label: string; + tooltip: React.ReactNode; + pulse?: boolean; +}) { + return ( + + + + {label} + + + ); +} + +function TooltipLines({ lines }: { lines: ReadonlyArray }) { + return ( +
+ {lines.map(([label, value]) => ( + + ))} +
+ ); +} + +function FragmentRow({ label, value }: { label: string; value: string }) { + return ( + <> + {label} + + {value} + + + ); +} diff --git a/apps/web/src/components/layout/Topbar.tsx b/apps/web/src/components/layout/Topbar.tsx index 9cab670..f0048a0 100644 --- a/apps/web/src/components/layout/Topbar.tsx +++ b/apps/web/src/components/layout/Topbar.tsx @@ -2,6 +2,7 @@ import { Avatar, Badge, Button, Flex, Input } from 'antd'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { faBell, faMagnifyingGlass, faBars } from '@fortawesome/free-solid-svg-icons'; import { brandTokens } from '../../lib/theme'; +import { FoundationStatus } from './FoundationStatus'; interface TopbarProps { onToggleSidebar?: () => void; @@ -84,8 +85,9 @@ export function Topbar({ onToggleSidebar }: TopbarProps) { /> - {/* Lado direito: notificações + perfil */} + {/* Lado direito: status fundação + notificações + perfil */} +