Purpose
Small, reusable presentational components shared across kundkort sections. All under frontend/kundkort/components/ui/ (or components/ for the page-level ones).
SectionHeading
components/ui/SectionHeading.tsx. <h2> with the wiki-uppercase muted-tracker style: 11 px, 0.08 em letter-spacing, --text-muted colour. Used as the title of every section card.
KvRow
components/ui/KvRow.tsx. Two-column key/value row with bottom border (suppressible via noBorder). Label is uppercase 11 px muted; value is right-aligned 13 px primary. Used heavily in Identity Card and ContactInfoCard.
GapBadge
components/ui/GapBadge.tsx. Tiny pill containing the literal string “GAP”. Used wherever a field is missing — see Financials Section, Koncern Section, Varumarken Section, Contacts Section.
ContactCard
components/ui/ContactCard.tsx. Card for a single KundkortContact. Coloured left border via roleColor prop. Renders name, role, phone (with phone_type “mobil”/“fast”), email (truncated with title attr), source pill. Falls back to “Ingen kontaktinfo” when neither phone nor email present. See Contacts Section for full breakdown.
SkeletonLoader
components/ui/SkeletonLoader.tsx. Full-page loading skeleton mimicking the kundkort layout: header, completeness bar, two-col grid, summary, financials, contacts. Uses an internal <SkeletonBlock> with animate-pulse. Rendered by Kundkort Page while useKundkort.loading === true.
LoadingCard
components/ui/LoadingCard.tsx. Smaller per-card loader — Tremor <Card> with title and a centered spinning blue ring. Used for the EcoAPI panels while useEcoApi.loading === true.
ErrorState
components/ui/ErrorState.tsx. Full-page error view. Picks title/message based on statusCode and error string:
| Trigger | Title |
|---|---|
| 401 / “Ej autentiserad" | "Ej autentiserad” + relogin button |
| 404 / “Företaget hittades inte" | "Företaget hittades inte” |
| ≥500 | ”Serverfel" |
| "Nätverksfel" | "Kan inte nå servern” |
| else | ”Något gick fel” + raw error |
Provides a “Tillbaka till sökning” link in every state except 401.
ErrorPanel
components/ErrorPanel.tsx + ErrorPanel.css. Persistent floating widget mounted by App (app.tsx:114). Polls GET /api/enrichment/errors?resolved=false&limit=1 once on mount; if any unresolved errors exist, fetches up to 50. Renders a fixed bottom-right toggle button “Fel (N)” that opens a side panel listing recent enrichment errors with a per-row “Försök igen” button (re-POSTs /api/kundkort/:orgNr/enrich).
Warning
The toggle button uses two emoji glyphs (warning sign and ✕). Other components avoid emojis. The polling does not refresh on a timer — only mount and panel-open.
Footer
components/Footer.tsx. Fixed bottom strip 36 px tall. Polls GET /api/config every 30 s for { dev_mode, enrichment_count, enrichment_limit }. Shows a usage-bar/progress indicator (green → yellow ≥80 % → red at 100 %) plus a red “DEVMODE” pill when the backend reports dev mode. Hidden in print via data-no-print.
CompanyHeader
components/CompanyHeader.tsx. Top of Kundkort Page body. Shows initials avatar (first letter of two name tokens), name, meta line (formatted org_nr · city · legal_form), three pills (Aktiv/Inaktiv status, “Score X/10” if lead_score, validation badge from getValidationStatus() thresholds: <3 grey “Ej validerad”, <5 amber “Delvis validerad”, else green “Validerad”), and a 3 px completeness bar driven by data.data_completeness_pct.
formatOrgNr
utils/formatOrgNr.ts. Strips non-digits; if exactly 10 digits, returns XXXXXX-XXXX. Otherwise returns input unchanged. Returns "—" for null/undefined/empty.
See also
Kundkort Page, Search Page, Auth Flow.