'use client' import { useEffect, useState } from 'react' import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card' import { Button } from '@/components/ui/button' import { Badge } from '@/components/ui/badge' import { useToast } from '@/components/ui/use-toast' import { CheckCircle2, ExternalLink, ShieldOff, FlaskConical, ShieldAlert } from 'lucide-react' type Environment = 'test' | 'prod' type Status = | { connected: false; environment?: Environment; disabled?: boolean } | { connected: true expired: boolean canRefresh: boolean scope: string expiresAt: string environment?: Environment disabled?: boolean } // docs: https://www7.skatteverket.se/portal-wapi/open/apier-och-oppna-data/utvecklarportalen/v1/getFile/tjanstebeskrivning-skattekonto-hamta-huvudmans-saldo-och-transaktioner-v101 const SCOPE_LABELS: Record = { momsdeklaration: 'Momsdeklaration', inkforetag: 'Företagsinformation', skahmst: 'Skattekonto – saldo & transaktioner', skattekonto: 'Skattekonto', agd: 'Arbetsgivardeklaration', } export function SkatteverketConnectPanel() { const { toast } = useToast() const [status, setStatus] = useState(null) const [loading, setLoading] = useState(true) const [disconnecting, setDisconnecting] = useState(false) async function loadStatus() { setLoading(true) try { const res = await fetch('/api/extensions/ext/skatteverket/status') if (res.status === 503) { setStatus({ connected: false }) return } const data = (await res.json()) as Status setStatus(data) } catch { setStatus({ connected: false }) } finally { setLoading(false) } } useEffect(() => { loadStatus() }, []) function startConnect() { const returnTo = encodeURIComponent('/settings/skatteverket') window.location.href = `/api/extensions/ext/skatteverket/authorize?return_to=${returnTo}` } async function disconnect() { setDisconnecting(true) try { const res = await fetch('/api/extensions/ext/skatteverket/disconnect', { method: 'POST', }) if (!res.ok) throw new Error('Frånkoppling misslyckades') toast({ title: 'Skatteverket frånkopplad' }) await loadStatus() } catch (err) { toast({ title: 'Kunde inte koppla från', description: err instanceof Error ? err.message : undefined, variant: 'destructive', }) } finally { setDisconnecting(false) } } if (loading) { return ( Hämtar status… ) } if (!status?.connected) { return (
Skatteverket
{status?.disabled && (

Skatteverket-integrationen är tillfälligt avstängd. Kontakta support.

)}

Anslut till Skatteverket med BankID för att skicka momsdeklaration, arbetsgivardeklaration och hämta saldot på skattekontot.

På Skatteverkets samtyckessida visas en av behörigheterna som{' '} skahmst (Rubrik saknas) — det är scope-namnet för skattekontots saldo och transaktioner (Skattekonto HuvudMan STatus). Skatteverket har inte publicerat en svensk beskrivning för den ännu. Det är ofarligt att godkänna.
) } const scopes = (status.scope || '').split(/\s+/).filter(Boolean) const expiresAtDate = new Date(status.expiresAt) const expiresInMinutes = Math.round( (expiresAtDate.getTime() - Date.now()) / 60_000, ) return (
Skatteverket {status.expired ? ( Utgången ) : ( Ansluten )}
Token utgår
{expiresAtDate.toLocaleString('sv-SE')} {!status.expired && expiresInMinutes > 0 && ( (om {expiresInMinutes} min) )}
Förnyelse
{status.canRefresh ? 'Förnyas automatiskt' : 'Förnyelse uttömd — anslut igen'}

Behörigheter

{scopes.map(s => ( {SCOPE_LABELS[s] ?? s} ))}
{!scopes.includes('skahmst') && !scopes.includes('skattekonto') && (

Behörigheten för Skattekonto saknas — koppla från och anslut igen för att aktivera saldo- och transaktionsvyn.

)} {!scopes.includes('agd') && (

Behörigheten för Arbetsgivardeklaration (AGI) saknas — koppla från och anslut igen för att kunna skicka AGI direkt från {`gnubok`}. Tokens utfärdade innan AGI-stödet aktiverades saknar denna scope.

)}
{status.disabled && (

Skatteverket-integrationen är tillfälligt avstängd. Inlämningar är inaktiverade.

)}
{(status.expired || !status.canRefresh || !scopes.includes('skattekonto') || !scopes.includes('agd')) && ( )}
) } function EnvironmentBadge({ environment, disabled }: { environment?: Environment; disabled?: boolean }) { if (disabled) { return ( Avstängd ) } if (environment === 'test') { return ( Testmiljö ) } if (environment === 'prod') { return ( Produktion ) } return null }