feat(settings): Fönster redesign - flat rows, ? help, dirty save bar (#1193)

* feat(settings): Fönster redesign - flat rows, help behind ?, dirty save bar

Founder-approved concept (2026-07-25) applied to the whole settings
surface, modal and full-page variants alike:

- New primitives in components/settings/SettingsRows.tsx: section header
  (serif title + one-line intro), eyebrow groups, hairline label/control
  rows, flat inputs/selects/textareas, segmented control, animated
  reveal for gated settings, danger zone.
- Every static explanation paragraph moved behind a "?" popover
  (HelpPopover) at row or group level; dynamic status stays visible.
- Modal chrome: company kicker over serif title, fixed 920x680 window.
- SettingsFormWrapper: save is a sticky bar that appears only when the
  form is dirty; collapses to zero height when clean.
- All 11 sections converted (Konto, Abonnemang, Företag, Bokföring,
  Skatt, Löner, Fakturering, Mallar, Bank incl. Enable Banking-panel,
  Assistenten, API) with handlers, validation, role/entitlement/sandbox
  gates and i18n keys preserved; checkboxes became switches, cards
  dissolved into groups.
- Fix: Escape with an open help popover closed the whole settings
  modal; it now closes the popover first.
- New i18n keys: settings_intro.*, group labels, wrapper_unsaved
  (sv+en).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(settings): founder feedback round 1 on the Fönster redesign

- Abonnemang paying state: status and manage split into two rows so the
  row no longer wraps awkwardly; the included-features list now shows
  for paying companies too.
- Logos where the counterpart has one: BankID mark on the security row
  and on the Koppla BankID button, Skatteverket mark on the connection
  rows.
- Buttons are unmistakably buttons: 27 text-labeled row actions went
  from ghost to outline pills; icon-only actions stay quiet.
- The agent-knowledge view (Regler & profil: Dina regler, Momsprofil,
  Konventioner) converted to the flat row language; it was the last
  old-style surface inside settings. Descriptions moved behind "?",
  rules render as hairline rows, the per-row "Regel" chip demoted to
  muted text.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(settings): address review-bot findings on the Fönster redesign

- SettingsFormWrapper marks the form dirty on switch clicks too: Radix
  Switch is a button and fires no input event, so switch-only changes
  (f-skatt, KU, ROT/RUT, OSS...) never revealed the save bar.
- i18n: the migrated hardcoded strings got keys in both locales
  (fiscal-period start date/range/months, security set-password trio);
  dates in ApiKeysPanel/OAuthClientsPanel/CalendarFeedSettings now pass
  the active locale to formatDateLong.
- A11y: member remove/revoke buttons and the invite role select got
  correct accessible names; BankNameCombobox accepts aria-label wired
  from its row; the pinned-fact icon exposes role img.
- BankIdSettings: explicit Avbryt under the QR block so a cancelled
  BankID flow cannot strand isLinking.
- VoucherSeriesManager: clear the skeleton when no company is resolved.

Verified end to end in sandbox: switch-only dirty bar, PUT /api/settings
200 for text and switch saves, persistence across hard reload.

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-25 23:55:08 +02:00
committed by GitHub
co-authored by Claude Fable 5
parent d54b43f80f
commit 6d9846b1e7
30 changed files with 400 additions and 367 deletions
+48 -27
View File
@@ -20,8 +20,6 @@
"load_more": "Ladda fler",
"retry": "Försök igen",
"load_error": "Kunde inte ladda data",
"popup_blocked_title": "Webbläsaren blockerade fliken",
"popup_blocked_description": "Tillåt popupfönster för Accounted i webbläsaren och försök igen.",
"confirm": "Bekräfta",
"yes": "Ja",
"no": "Nej",
@@ -66,7 +64,9 @@
"matched": "Matchad",
"unmatched": "Omatchad"
},
"more_options": "Fler alternativ"
"more_options": "Fler alternativ",
"popup_blocked_title": "Webbläsaren blockerade fliken",
"popup_blocked_description": "Tillåt popupfönster för Accounted i webbläsaren och försök igen."
},
"nav": {
"dashboard": "Översikt",
@@ -369,9 +369,16 @@
"sync_now": "Synka nu",
"syncing": "Synkar…",
"sync_done_title": "Synkronisering klar",
"sync_done_feed": "{fetched} transaktion(er) hämtade: {imported} nya i inkorgen, {linked} länkade till verifikat.",
"sync_done_empty": "Stripe returnerade inga transaktioner för perioden. Kontrollera att rätt konto är anslutet om du väntade dig transaktioner.",
"sync_done_description": "{settled} betalning(ar) bokförda, {review} kräver granskning.",
"sync_failed_title": "Synkroniseringen misslyckades",
"needs_review_title": "Kräver granskning",
"needs_review_hint": "Betalningar som inte kunde prickas av automatiskt. Hantera dem manuellt via fakturan eller i Stripe.",
"reason_invoice_not_found": "Betalning utan matchande faktura",
"reason_invoice_already_paid": "Fakturan är redan markerad som betald",
"reason_amount_mismatch": "Beloppet stämmer inte med fakturans restbelopp",
"reason_currency_mismatch": "Valutan stämmer inte med fakturan",
"reason_non_sek_invoice": "Faktura i utländsk valuta (bokförs manuellt)",
"reason_unknown": "Okänd orsak",
"transaction_sync_title": "Transaktioner från Stripe",
"transaction_sync_description": "Hämta alla Stripe-transaktioner (betalningar, avgifter, återbetalningar och utbetalningar) till transaktionsinkorgen varje natt, som ett bankflöde för ditt Stripe-saldo. Du bokför raderna som vanligt från inkorgen.",
"transaction_sync_backfill_note": "Vid första synkningen hämtas upp till 90 dagars historik, dock inte före bokföringslåset.",
@@ -379,7 +386,10 @@
"transaction_sync_never_synced": "Inte synkad ännu",
"transaction_sync_enabled_toast": "Transaktionssynk aktiverad. Historiken hämtas vid nästa synkning.",
"transaction_sync_disabled_toast": "Transaktionssynk avaktiverad.",
"transaction_sync_toggle_failed": "Kunde inte spara inställningen. Försök igen."
"transaction_sync_toggle_failed": "Kunde inte spara inställningen. Försök igen.",
"sync_done_transactions": "{imported} transaktion(er) importerade, {linked} länkade till verifikat.",
"sync_done_feed": "{fetched} transaktion(er) hämtade: {imported} nya i inkorgen, {linked} länkade till verifikat.",
"sync_done_empty": "Stripe returnerade inga transaktioner för perioden. Kontrollera att rätt konto är anslutet om du väntade dig transaktioner."
},
"settings_modal": {
"title": "Inställningar",
@@ -1374,6 +1384,13 @@
"joined_generic": "Du är nu medlem."
},
"settings_company": {
"members_remove_aria": "Ta bort medlem",
"members_revoke_aria": "Återkalla inbjudan",
"members_role_label": "Roll",
"fp_start_date_label": "Startdatum",
"fp_start_date_help": "Första räkenskapsåret kan börja valfri dag.",
"fp_range": "{start} till {end}",
"fp_months": "{count} månader",
"company_info_heading": "Företagsuppgifter",
"share_capital_heading": "Aktiekapital",
"aktiekapital_label": "Aktiekapital (kr)",
@@ -2239,6 +2256,7 @@
"env_prod": "Produktion"
},
"settings_bankid": {
"cancel_linking": "Avbryt",
"title": "BankID",
"toast_already_linked": "Detta BankID är redan kopplat till ett annat konto.",
"toast_link_failed": "Kunde inte koppla BankID.",
@@ -2256,6 +2274,9 @@
"link_button": "Koppla BankID"
},
"settings_security": {
"set_password_title": "Sätt ett lösenord",
"set_password_description": "Du loggade in med BankID och har inget lösenord ännu. Sätt ett lösenord för att kunna aktivera 2FA eller logga in när BankID inte är tillgängligt.",
"set_password_button": "Sätt lösenord",
"group_security": "Säkerhet",
"toast_weak_password_title": "Lösenordet är för svagt",
"toast_weak_password_description": "Lösenordet måste vara minst 8 tecken och innehålla versaler, gemener, siffror och specialtecken.",
@@ -2893,8 +2914,6 @@
"preview_pdf": "Förhandsgranska PDF",
"preview_pdf_generating": "Genererar...",
"preview_pdf_failed": "Kunde inte generera PDF",
"review_customer_missing_title": "Kunduppgifterna kunde inte laddas",
"review_customer_missing_description": "Ladda om sidan och försök igen. Kontakta support om det inte hjälper.",
"create_invoice_failed_title": "Kunde inte skapa faktura",
"doc_created_title": "{docLabel} skapad",
"doc_created_description": "{docLabel} {number} har skapats",
@@ -2936,7 +2955,9 @@
"deduction_cap_check": "Kunden behöver kontrollera sitt återstående utrymme själv.",
"deduction_summary_label": "Skattereduktion ROT/RUT",
"to_pay_label": "Att betala",
"total_incl_vat_label": "Totalt inkl. moms"
"total_incl_vat_label": "Totalt inkl. moms",
"review_customer_missing_title": "Kunduppgifterna kunde inte laddas",
"review_customer_missing_description": "Ladda om sidan och försök igen. Kontakta support om det inte hjälper."
},
"invoice_review": {
"assigned_number_prefix": "Tilldelas fakturanummer",
@@ -3028,21 +3049,6 @@
"delivery_status_sent": "Skickad",
"delivery_status_failed": "Misslyckad",
"delivery_status_marked_sent": "Manuell",
"delivery_status_delivered": "Levererad",
"delivery_status_delayed": "Fördröjd",
"delivery_status_complained": "Spam-anmäld",
"delivery_status_bounced": "Studsade",
"delivery_status_suppressed": "Blockerad",
"delivery_status_explanation_sent": "Mailet är accepterat av e-posttjänsten. Besked om att mottagarens server tagit emot det har inte kommit in ännu.",
"delivery_status_explanation_delivered": "Mottagarens server tog emot mailet. Hittar kunden det ändå inte: be dem titta i skräpposten. Karantän hos mottagarens IT-avdelning syns aldrig för avsändaren.",
"delivery_status_explanation_delayed": "Mottagarens server har inte tagit emot mailet ännu, men nya försök pågår. Kommer inget besked inom några timmar: hör av dig till kunden.",
"delivery_status_explanation_complained": "Mottagaren markerade mailet som skräppost. Fortsatta utskick till adressen kan komma att blockeras.",
"delivery_status_explanation_bounced": "Mottagarens server avvisade mailet. Fakturan kom inte fram.",
"delivery_status_explanation_failed": "Mailet kunde inte skickas ut. Fakturan kom inte fram.",
"delivery_status_explanation_suppressed": "Adressen är spärrad hos e-posttjänsten efter tidigare studs eller spam-anmälan, så mailet skickades aldrig.",
"delivery_status_whole_send_note": "Beskedet gäller hela utskicket, inte enskilda mottagare.",
"delivery_provider_status_label": "Leveransstatus",
"delivery_provider_reason_label": "Besked från mottagaren",
"delivery_manual_unknown_details": "Utskicket gjordes utanför Accounted. Mottagare, meddelande och den levererade filen är därför inte kända.",
"delivery_to_label": "Till",
"delivery_cc_label": "Kopia",
@@ -3189,7 +3195,22 @@
"cancelled_draft": "Utkastet har makulerats.",
"cancel_failed_title": "Kunde inte makulera fakturan",
"paid_toast_title": "Betald",
"paid_toast_description": "Faktura {number} har markerats som betald och bokförts"
"paid_toast_description": "Faktura {number} har markerats som betald och bokförts",
"delivery_status_delivered": "Levererad",
"delivery_status_delayed": "Fördröjd",
"delivery_status_complained": "Spam-anmäld",
"delivery_status_bounced": "Studsade",
"delivery_status_suppressed": "Blockerad",
"delivery_status_explanation_sent": "Mailet är accepterat av e-posttjänsten. Besked om att mottagarens server tagit emot det har inte kommit in ännu.",
"delivery_status_explanation_delivered": "Mottagarens server tog emot mailet. Hittar kunden det ändå inte: be dem titta i skräpposten. Karantän hos mottagarens IT-avdelning syns aldrig för avsändaren.",
"delivery_status_explanation_delayed": "Mottagarens server har inte tagit emot mailet ännu, men nya försök pågår. Kommer inget besked inom några timmar: hör av dig till kunden.",
"delivery_status_explanation_complained": "Mottagaren markerade mailet som skräppost. Fortsatta utskick till adressen kan komma att blockeras.",
"delivery_status_explanation_bounced": "Mottagarens server avvisade mailet. Fakturan kom inte fram.",
"delivery_status_explanation_failed": "Mailet kunde inte skickas ut. Fakturan kom inte fram.",
"delivery_status_explanation_suppressed": "Adressen är spärrad hos e-posttjänsten efter tidigare studs eller spam-anmälan, så mailet skickades aldrig.",
"delivery_status_whole_send_note": "Beskedet gäller hela utskicket, inte enskilda mottagare.",
"delivery_provider_status_label": "Leveransstatus",
"delivery_provider_reason_label": "Besked från mottagaren"
},
"invoice_credit": {
"back": "Tillbaka",
@@ -3954,7 +3975,6 @@
"remove_blocked_cancel_cta": "Stäng",
"replace_uploading": "Ersätter...",
"remove_failed": "Kunde inte ta bort underlaget.",
"download_failed": "Kunde inte öppna dokumentet",
"replace_failed": "Kunde inte ladda upp ny version.",
"choose_from_inbox": "Välj från inkorgen",
"picker_title": "Välj underlag från inkorgen",
@@ -3970,7 +3990,8 @@
"picker_preview": "Förhandsgranska",
"picker_attach": "Koppla underlag",
"picker_close": "Stäng",
"picker_preview_unavailable": "Förhandsgranskning kunde inte visas."
"picker_preview_unavailable": "Förhandsgranskning kunde inte visas.",
"download_failed": "Kunde inte öppna dokumentet"
},
"journal_status": {
"status_draft": "Utkast",