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 <noreply@anthropic.com>

* 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 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Jakob Wennberg
2026-07-10 08:56:30 +02:00
committed by GitHub
co-authored by Claude Fable 5
parent ca8a89324c
commit da859d7236
41 changed files with 373 additions and 408 deletions
@@ -4,7 +4,6 @@ import { useState, useEffect } from 'react'
import { useRouter } from 'next/navigation'
import Link from 'next/link'
import { useLocale, useTranslations } from 'next-intl'
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
import { Button } from '@/components/ui/button'
import { Input } from '@/components/ui/input'
import { Label } from '@/components/ui/label'
@@ -219,53 +218,47 @@ export function AccountSettingsContent() {
)}
{/* Logout */}
<section className="border-t border-border pt-8">
<Card>
<CardHeader>
<CardTitle>{tCommon('account_settings')}</CardTitle>
</CardHeader>
<CardContent>
<div className="flex items-center justify-between p-4 border rounded-lg">
<div>
<p className="font-medium">{tCommon('logout')}</p>
<p className="text-sm text-muted-foreground">{tCommon('logout_description')}</p>
</div>
<Button variant="outline" onClick={handleLogout}>
<LogOut className="mr-2 h-4 w-4" />
{tCommon('logout')}
</Button>
</div>
</CardContent>
</Card>
<section className="space-y-4 border-t border-border pt-8">
<h2 className="text-sm font-medium uppercase tracking-wider text-muted-foreground">
{tCommon('account_settings')}
</h2>
<div className="flex items-center justify-between p-4 border rounded-lg">
<div>
<p className="font-medium">{tCommon('logout')}</p>
<p className="text-sm text-muted-foreground">{tCommon('logout_description')}</p>
</div>
<Button variant="outline" onClick={handleLogout}>
<LogOut className="mr-2 h-4 w-4" />
{tCommon('logout')}
</Button>
</div>
</section>
{/* Privacy & agreements: surface the otherwise-unlinked DPA + privacy policy */}
<section className="border-t border-border pt-8">
<Card>
<CardHeader>
<CardTitle>{tSettings('legal_title')}</CardTitle>
</CardHeader>
<CardContent className="space-y-2">
<Link
href="/privacy"
target="_blank"
rel="noopener noreferrer"
className="flex items-center justify-between rounded-lg border p-4 transition-colors hover:bg-secondary/60"
>
<span className="font-medium">{tSettings('legal_privacy')}</span>
<ExternalLink className="h-4 w-4 text-muted-foreground" />
</Link>
<Link
href="/dpa"
target="_blank"
rel="noopener noreferrer"
className="flex items-center justify-between rounded-lg border p-4 transition-colors hover:bg-secondary/60"
>
<span className="font-medium">{tSettings('legal_dpa')}</span>
<ExternalLink className="h-4 w-4 text-muted-foreground" />
</Link>
</CardContent>
</Card>
<section className="space-y-4 border-t border-border pt-8">
<h2 className="text-sm font-medium uppercase tracking-wider text-muted-foreground">
{tSettings('legal_title')}
</h2>
<div className="space-y-2">
<Link
href="/privacy"
target="_blank"
rel="noopener noreferrer"
className="flex items-center justify-between rounded-lg border p-4 transition-colors hover:bg-secondary/60"
>
<span className="font-medium">{tSettings('legal_privacy')}</span>
<ExternalLink className="h-4 w-4 text-muted-foreground" />
</Link>
<Link
href="/dpa"
target="_blank"
rel="noopener noreferrer"
className="flex items-center justify-between rounded-lg border p-4 transition-colors hover:bg-secondary/60"
>
<span className="font-medium">{tSettings('legal_dpa')}</span>
<ExternalLink className="h-4 w-4 text-muted-foreground" />
</Link>
</div>
</section>
{/* Delete account: only for non-sandbox */}
@@ -6,6 +6,7 @@ import { useSearchParams, useRouter } from 'next/navigation'
import Link from 'next/link'
import { Card, CardContent } from '@/components/ui/card'
import { Button } from '@/components/ui/button'
import { EmptyState } from '@/components/ui/empty-state'
import { useToast } from '@/components/ui/use-toast'
import { AlertTriangle, CreditCard, ExternalLink } from 'lucide-react'
import { getSettingsPanel } from '@/lib/extensions/settings-panel-registry'
@@ -75,7 +76,7 @@ export function BankingSettingsContent() {
return (
<div className="space-y-8">
{bankConnectionError && (
<div className="flex items-start gap-3 rounded-lg border border-destructive/20 bg-destructive/10 p-4">
<div className="flex items-start gap-3 rounded-lg border border-destructive p-4">
<AlertTriangle className="mt-0.5 h-5 w-5 shrink-0 text-destructive" />
<div className="flex-1">
<p className="text-sm font-medium text-destructive">{bankConnectionError}</p>
@@ -123,18 +124,19 @@ export function BankingSettingsContent() {
</>
) : (
<Card>
<CardContent className="flex flex-col items-center justify-center py-12 text-center">
<CreditCard className="h-10 w-10 text-muted-foreground/40 mb-4" />
<p className="font-medium mb-1">{t('not_enabled_title')}</p>
<p className="text-sm text-muted-foreground mb-4 max-w-md">
{t('not_enabled_description')}
</p>
<Button variant="outline" asChild>
<Link href="/extensions">
<ExternalLink className="mr-2 h-4 w-4" />
{t('go_to_extensions')}
</Link>
</Button>
<CardContent className="p-0">
<EmptyState
icon={CreditCard}
title={t('not_enabled_title')}
description={t('not_enabled_description')}
>
<Button variant="outline" asChild>
<Link href="/extensions">
<ExternalLink className="mr-2 h-4 w-4" />
{t('go_to_extensions')}
</Link>
</Button>
</EmptyState>
</CardContent>
</Card>
)}
@@ -3,7 +3,6 @@
import Link from 'next/link'
import { useState } from 'react'
import { useTranslations } from 'next-intl'
import { PageHeader } from '@/components/ui/page-header'
import { Label } from '@/components/ui/label'
import { Input } from '@/components/ui/input'
import { SettingsFormWrapper } from '@/components/settings/SettingsFormWrapper'
@@ -74,8 +73,6 @@ export function SalarySettingsContent() {
return (
<div className="space-y-8">
<PageHeader title={t('title')} />
<SettingsFormWrapper onSave={handleSave} className="space-y-8">
{/* Payment */}
<section className="space-y-4">