fix(entitlements): show paid features as gated upsells instead of dead ends (#913)

* fix(entitlements): show paid features as gated upsells instead of dead ends

Post-cutover, non-payers still saw fully interactive UI for paid features
(bank picker, agent-build hero, SKV VAT submission) that silently failed or
403'd on the server gate. Every surface now stays visible as a conversion
surface but is explicitly gated:

- new shared components/billing/UpgradeNote (lock icon + billing link)
- agent-build hero (dashboard + new-user checklist): routes to
  /settings/billing with upgrade copy when the ai capability is missing
- bank connect: BankingSettingsPanel and the import-page PSD2 wizard swap
  the bank list for an upgrade note; the import selection card swaps the
  "Rekommenderat" chip for "Kräver abonnemang"
- VAT report SkatteverketPanel: gated state renders before the
  connection check, so trial-connected companies see the upsell instead
  of action buttons that would 403; manual-filing framing kept
- SkatteverketConnectPanel: skahmst consent note hidden while gated
  (irrelevant until the consent page is reachable)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* test(ne-bilaga): fix time-of-day flake in SRU field-code assertion

The bare substrings '7310'/'7350' also match the #SKAPAD HHMMSS timestamp
when CI runs at 07:31/07:35, so the assertion now requires the full
'#UPPGIFT <code>' prefix.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Jakob Wennberg
2026-07-07 10:01:44 +02:00
committed by GitHub
co-authored by Claude Opus 4.7
parent fca75df3e2
commit 27b88426e2
10 changed files with 141 additions and 31 deletions
@@ -106,8 +106,11 @@ describe('NE-bilaga SRU generator', () => {
it('does not use the old (wrong) 73xx field codes', () => {
const { blanketterSru } = generateNESRUSubmission(makeDeclaration())
expect(blanketterSru).not.toContain('7310')
expect(blanketterSru).not.toContain('7350')
// Match the full #UPPGIFT prefix, not a bare substring: the #SKAPAD
// HHMMSS timestamp contains "7310"/"7350" when the clock reads
// 07:31:0x / 07:35:0x, which made this assertion time-of-day flaky.
expect(blanketterSru).not.toContain('#UPPGIFT 7310')
expect(blanketterSru).not.toContain('#UPPGIFT 7350')
expect(blanketterSru).not.toContain('#UPPGIFT 7000')
})