From da859d7236acadb8fa1979a21fd7e3d7192be549 Mon Sep 17 00:00:00 2001 From: Jakob Wennberg <149234542+jakobwennberg@users.noreply.github.com> Date: Fri, 10 Jul 2026 08:56:30 +0200 Subject: [PATCH] refactor(ui): design-system consistency pass over dense pages + i18n de-bloat (#961) * refactor(ui): normalize dense pages to the locked design system Sweep of the info-dense surfaces against .claude/rules/design.md; no behavior changes, classNames and primitive adoption only. - Replace hand-rolled h1s with PageHeader (import, suppliers, kpi, salary/employees, skattekonto, settings layout) and drop the one double title (SalarySettingsContent under the settings h1) - Replace hand-rolled empty states with EmptyState (skattekonto, banking/api-keys/oauth/counterparty settings) and hand-rolled pulse divs with Skeleton (deadlines, report view loaders) - Remove semantic colors used as chrome: amber/emerald banners in AGIPanel and SkatteverketPanel, success/warning tints in kassaflodesanalys, arsredovisning and import become neutral surfaces with the tint kept on the icon only - Full-opacity borders everywhere (border-border/30-60, border-destructive/20-40, border-foreground/30, text-destructive/80) - Snap off-scale spacing (p-5 to p-6, p-2.5 to p-3, gap/mt-x.5 to scale values); KPI metric tiles p-6 to p-4 per the tile rule - Remove the mobile Select that duplicated the invoices status Tabs (TabsList already scrolls horizontally); single Tabs now serves both breakpoints - supplier-invoices: shared formatCurrency instead of a local formatAmount helper; skattekonto: formatDate/formatDateLong/ formatDateTime instead of raw dates and toLocaleString - arsredovisning flerarsoversikt converted to the Table primitive with right-aligned tabular-nums cells - Settings: CardTitle text-base on section cards, one heading idiom in AccountSettingsContent, h3 to h2 in CompanyProfileView Co-Authored-By: Claude Fable 5 * refactor(i18n): trim text bloat and fix an untranslated sv string - Fix invoice_credit.create_failed_fallback: sv catalog carried the English "Failed to create credit note"; now "Kunde inte skapa kreditfaktura". Translate new_user_checklist.step3_title in en - Drop descriptions that paraphrase their own title (design.md forbidden pattern): invoice_detail.credited_description, invoice_credit.original_card_description, invoice_editor customer/notes card descriptions (keys deleted from both catalogs, zero remaining usages); the transaction booking DialogDescription becomes sr-only so screen readers keep it - Trim redundant sentences from settings_salary.info_payroll_scope, settings_backup.intro, ext_cloud_backup_long_description, settings.name_description, salary_payments.open_payments_note and shorten invoice_credit.reason_card_description; statutory BFL/tax prose untouched - Normalize toast punctuation (dimensions/self_billing created_description lose the trailing period like their siblings) - common.delete "Radera" to "Ta bort" (zero live call sites; Radera stays reserved for irreversible account/company deletion) Catalogs verified key-identical (4795 keys each) and JSON-parseable. Co-Authored-By: Claude Fable 5 --------- Co-authored-by: Claude Fable 5 --- DECISIONS.md | 5 ++ app/(dashboard)/bookkeeping/[id]/page.tsx | 2 +- .../year-end/arsredovisning/page.tsx | 72 +++++++-------- app/(dashboard)/deadlines/page.tsx | 21 ++--- app/(dashboard)/import/page.tsx | 65 +++++++------- app/(dashboard)/invoices/[id]/credit/page.tsx | 3 - app/(dashboard)/invoices/[id]/page.tsx | 3 - app/(dashboard)/invoices/page.tsx | 22 +---- app/(dashboard)/kpi/page.tsx | 13 +-- app/(dashboard)/pending/page.tsx | 8 +- .../KassaflodesanalysClient.tsx | 16 ++-- app/(dashboard)/salary/employees/page.tsx | 33 +++---- app/(dashboard)/settings/layout.tsx | 3 +- app/(dashboard)/skattekonto/page.tsx | 89 +++++++++---------- .../supplier-invoices/[id]/page.tsx | 38 ++++---- app/(dashboard)/supplier-invoices/page.tsx | 21 +++-- app/(dashboard)/suppliers/page.tsx | 77 ++++++++-------- components/invoices/InvoiceEditor.tsx | 3 +- components/kpi/KPIHeroCards.tsx | 2 +- components/reports/ReportRowExpansion.tsx | 2 +- components/reports/SkatteverketPanel.tsx | 14 +-- components/reports/views/index.tsx | 18 ++-- components/salary/AGIPanel.tsx | 20 ++--- components/salary/run/RunJournalPreview.tsx | 2 +- components/settings/AccountDangerZone.tsx | 2 +- components/settings/AgentSkillsPanel.tsx | 2 +- components/settings/ApiKeysPanel.tsx | 17 ++-- components/settings/BackupDownloadForm.tsx | 2 +- components/settings/BookingTemplatesPanel.tsx | 2 +- components/settings/CompanyDangerZone.tsx | 2 +- components/settings/CompanyProfileView.tsx | 4 +- .../settings/CounterpartyTemplatesPanel.tsx | 15 ++-- components/settings/OAuthClientsPanel.tsx | 15 ++-- components/settings/PdfPrintSettings.tsx | 2 +- .../settings/SkatteverketConnectPanel.tsx | 4 +- .../sections/AccountSettingsContent.tsx | 83 ++++++++--------- .../sections/BankingSettingsContent.tsx | 28 +++--- .../sections/SalarySettingsContent.tsx | 3 - .../transactions/TransactionBookingDialog.tsx | 2 +- messages/en.json | 22 ++--- messages/sv.json | 24 +++-- 41 files changed, 373 insertions(+), 408 deletions(-) diff --git a/DECISIONS.md b/DECISIONS.md index 598dc73e..8e2b319d 100644 --- a/DECISIONS.md +++ b/DECISIONS.md @@ -53,3 +53,8 @@ One line per decision: `[YYYY-MM-DD] : `. Appended by agents and [2026-07-09] #917 fix scoped to the current-year suggestion: "Sedan räkenskapsårets början" now resolves from the fiscal_periods row containing today, but the "Föregående räkenskapsårets start" custom option still derives from the recurring fiscal_year_start_month: the issue only covers the current-year date and a first-year company has no previous period row to resolve against. [2026-07-09] Issue #919 (duplicate guard should steer to matching): the match action lives INSIDE DuplicateBookingDialog (fetch to /api/reconciliation/bank/link + account resolution via /api/cash-accounts + resolveAccount, exactly the MatchVoucherDialog path) rather than in each call site or a new endpoint: both call sites (transactions page runCategorize + TransactionBookingDialog/JournalEntryForm) share one implementation and pass only the transaction context + an onMatched callback mirroring onLinked. Match is primary ONLY for ledger-only candidates (transaction_id null, the SIE-import case); sibling-transaction candidates keep "Bokför ändå" primary since N:1 matching is the edge case. No lib change: the candidate already carries the transaction_id discriminator, covered by existing tests. [2026-07-09] Demo/sandbox users could reach Stripe: an anonymous user on a sandbox company hit POST /api/billing/checkout and created a live Stripe customer (no subscription = no charge; exact tenant/customer IDs kept out of source control, see the incident PR). Root cause: neither billing/checkout nor billing/portal checked is_anonymous or is_sandbox, and withRouteContext lets anonymous users through (they are authenticated, just anonymously). Fix guards BOTH conditions in both routes (is_anonymous is the identity truth; guardSandbox matches the existing lib/sandbox/guard.ts "never charge a token" doctrine), belt-and-suspenders since anon and sandbox happen to co-occur today but are orthogonal. Anon check runs first (in-memory, no DB round trip). Also surfaced isDemo on GET /api/billing/status so the client hides the upgrade CTA instead of showing a button that 403s. Blast radius = exactly one company (no other sandbox/anon tenant had a stripe_customer_id). Left the stray company_subscriptions row + orphan Stripe customer for manual cleanup (prod write / external destructive action, not done unilaterally). +[2026-07-09] UI consistency pass: skipped the suppliers/page.tsx card-grid to Table conversion (did PageHeader + space-y-8 only): the suppliers.* i18n namespace has no column-header keys (nothing equivalent to customers.col_name/col_type), the pass forbade new i18n keys, and a data table whose primary name column has no header is worse than the existing card grid. +[2026-07-09] supplier-invoices/[id]: replaced the local formatAmount with shared formatCurrency at all direct render sites, but the two i18n-templated amounts (amount_registered_description embeds "kr", remaining_to_pay embeds "{currency}") now use the shared bare-number formatAmount from lib/utils instead: passing formatCurrency output there would double-print the currency, and message files were off-limits. +[2026-07-09] common.delete changed "Radera" to "Ta bort": grep proved the key has zero live call sites (every delete dialog uses feature-namespace keys), so this only affects future uses; convention going forward is Ta bort = detach/remove, Radera = irreversible destruction (kept in AccountDangerZone/CompanyDangerZone keys). +[2026-07-09] InvoiceEditor customer-card description kept only for the self-billing branch (issuer_card_description adds real info: who issues the invoice); the plain-invoice branch dropped its description as a title paraphrase per design.md forbidden patterns. +[2026-07-09] SalaryCalendar absence-type rainbow palette (red/amber/emerald/blue/indigo pills) left as-is in the UI consistency pass: those colors encode absence categories (data), not status chrome, and swapping them for the 3 semantic tokens would collapse 5 distinguishable categories; needs a proper categorical-palette decision instead of a mechanical fix. diff --git a/app/(dashboard)/bookkeeping/[id]/page.tsx b/app/(dashboard)/bookkeeping/[id]/page.tsx index 55c85e59..affdff14 100644 --- a/app/(dashboard)/bookkeeping/[id]/page.tsx +++ b/app/(dashboard)/bookkeeping/[id]/page.tsx @@ -339,7 +339,7 @@ export default function JournalEntryDetailPage({ params }: { params: Promise<{ i
-

+

{formatVoucher(entry)}

diff --git a/app/(dashboard)/bookkeeping/year-end/arsredovisning/page.tsx b/app/(dashboard)/bookkeeping/year-end/arsredovisning/page.tsx index 38de16de..7502bf33 100644 --- a/app/(dashboard)/bookkeeping/year-end/arsredovisning/page.tsx +++ b/app/(dashboard)/bookkeeping/year-end/arsredovisning/page.tsx @@ -10,9 +10,11 @@ import { Skeleton } from '@/components/ui/skeleton' import { Input } from '@/components/ui/input' import { Label } from '@/components/ui/label' import { Textarea } from '@/components/ui/textarea' +import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table' import { PageHeader } from '@/components/ui/page-header' import { ArrowLeft, FileDown, Plus, ExternalLink, Loader2, Save, CheckCircle2 } from 'lucide-react' import { useToast } from '@/components/ui/use-toast' +import { formatCurrency } from '@/lib/utils' import { FiscalYearSelector } from '@/components/common/FiscalYearSelector' import { DigitalInlamning, INLAMNING_COMING_SOON } from '@/components/bokslut/DigitalInlamning' import type { ArsredovisningData } from '@/lib/bokslut/arsredovisning/types' @@ -388,7 +390,7 @@ export default function ArsredovisningPage() {

-
+