fix(skattekonto): stop conflating live-call auth failures with "inte anslutet" + surface SKV connection first in tax settings (#887)
The sync endpoint returns 401 for six distinct auth states (handleSkvError): NOT_CONNECTED, SESSION_EXPIRED, REFRESH_EXHAUSTED, MISSING_SCOPE, TOKEN_CORRUPTED, TOKEN_REVOKED. The page treated every 401 as "Skatteverket är inte anslutet" — directly contradicting Inställningar, which reads the stored token metadata and truthfully shows "Ansluten" for all but the first. - syncNow now checks the structured error code: only NOT_CONNECTED flips to the not-connected empty state. Every other auth failure renders the server's actual Swedish message as a persistent banner with an "Anslut igen" CTA, keeping the stored saldo/transactions visible. - SkatteverketConnectPanel moves to the top of /settings/tax — the skattekonto and momsdeklaration pages send users there specifically to (re)connect, and below the tax form it sat out of view. Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
9f7c842a33
commit
70e893b8d4
@@ -67,6 +67,10 @@ export default function SkattekontoPage() {
|
|||||||
const [bookingId, setBookingId] = useState<string | null>(null)
|
const [bookingId, setBookingId] = useState<string | null>(null)
|
||||||
const [notConnected, setNotConnected] = useState(false)
|
const [notConnected, setNotConnected] = useState(false)
|
||||||
const [loadError, setLoadError] = useState(false)
|
const [loadError, setLoadError] = useState(false)
|
||||||
|
// Set when a sync fails with an auth error while a connection exists
|
||||||
|
// (expired session, missing scope, revoked token). Rendered as a banner —
|
||||||
|
// the stored data below stays visible and usable.
|
||||||
|
const [reconnectMessage, setReconnectMessage] = useState<string | null>(null)
|
||||||
const [matchOpenFor, setMatchOpenFor] = useState<StoredSkattekontoTransaction | null>(
|
const [matchOpenFor, setMatchOpenFor] = useState<StoredSkattekontoTransaction | null>(
|
||||||
null,
|
null,
|
||||||
)
|
)
|
||||||
@@ -122,12 +126,28 @@ export default function SkattekontoPage() {
|
|||||||
})
|
})
|
||||||
const json = await res.json()
|
const json = await res.json()
|
||||||
if (!res.ok) {
|
if (!res.ok) {
|
||||||
|
// 401 covers several distinct auth states (see handleSkvError in the
|
||||||
|
// skatteverket extension). Only NOT_CONNECTED means "no connection
|
||||||
|
// exists" — the rest (SESSION_EXPIRED, MISSING_SCOPE, TOKEN_REVOKED,
|
||||||
|
// …) fire while Inställningar truthfully shows the stored token as
|
||||||
|
// "Ansluten". Showing the full "inte anslutet"-tomvy for those
|
||||||
|
// contradicts the settings panel; show the server's actual reason
|
||||||
|
// with a reconnect CTA instead.
|
||||||
if (res.status === 401) {
|
if (res.status === 401) {
|
||||||
setNotConnected(true)
|
if (json.code === 'NOT_CONNECTED') {
|
||||||
|
setNotConnected(true)
|
||||||
|
} else {
|
||||||
|
setReconnectMessage(
|
||||||
|
typeof json.error === 'string' && json.error
|
||||||
|
? json.error
|
||||||
|
: 'Anslutningen mot Skatteverket behöver förnyas. Anslut igen med BankID.',
|
||||||
|
)
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
throw new Error(json.error || 'Synk misslyckades')
|
throw new Error(json.error || 'Synk misslyckades')
|
||||||
}
|
}
|
||||||
|
setReconnectMessage(null)
|
||||||
toast({
|
toast({
|
||||||
title: 'Skattekonto synkroniserat',
|
title: 'Skattekonto synkroniserat',
|
||||||
description: `${json.data.booked} bokförda, ${json.data.upcoming} kommande`,
|
description: `${json.data.booked} bokförda, ${json.data.upcoming} kommande`,
|
||||||
@@ -292,6 +312,21 @@ export default function SkattekontoPage() {
|
|||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
{reconnectMessage && (
|
||||||
|
<div className="flex flex-wrap items-center justify-between gap-3 rounded-lg border border-destructive/20 bg-destructive/5 px-4 py-3">
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
<AlertCircle className="h-4 w-4 shrink-0 text-destructive" />
|
||||||
|
<p className="text-sm">{reconnectMessage}</p>
|
||||||
|
</div>
|
||||||
|
<Button asChild variant="outline" size="sm">
|
||||||
|
<Link href="/settings/tax">
|
||||||
|
<ExternalLink className="mr-2 h-3.5 w-3.5" />
|
||||||
|
Anslut igen
|
||||||
|
</Link>
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
<BalanceHero saldo={saldo} loading={loading} onCopyOcr={copyOcr} />
|
<BalanceHero saldo={saldo} loading={loading} onCopyOcr={copyOcr} />
|
||||||
|
|
||||||
<Card>
|
<Card>
|
||||||
|
|||||||
@@ -77,11 +77,14 @@ export function TaxSettingsContent() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-8">
|
<div className="space-y-8">
|
||||||
|
{/* Connection panel first: the skattekonto and momsdeklaration pages
|
||||||
|
send users here specifically to (re)connect — below the long tax
|
||||||
|
form it sat out of view. */}
|
||||||
|
{showSkatteverket && <SkatteverketConnectPanel />}
|
||||||
|
|
||||||
<SettingsFormWrapper onSave={handleSave} className="space-y-0">
|
<SettingsFormWrapper onSave={handleSave} className="space-y-0">
|
||||||
<TaxSettingsForm settings={settings} />
|
<TaxSettingsForm settings={settings} />
|
||||||
</SettingsFormWrapper>
|
</SettingsFormWrapper>
|
||||||
|
|
||||||
{showSkatteverket && <SkatteverketConnectPanel />}
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user