'use client' import { useEffect, useState } from 'react' import { Check, Clock, Minus } from 'lucide-react' import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card' import { Skeleton } from '@/components/ui/skeleton' import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table' import { formatDateLong } from '@/lib/utils' import { BillingActions } from '@/components/settings/BillingActions' // What the paid tier unlocks (mirrors lib/entitlements PAID_CAPABILITIES). const INCLUDED = [ 'AI-assistent: chatt, kategorisering och dokumenttolkning', 'Bankkoppling och automatisk synk (PSD2)', 'Skatteverket: moms- och AGI-inlämning', 'E-postutskick av fakturor, påminnelser och lönebesked', ] // Free vs paid, shown as a comparison table: what the paid tier adds reads // strongest next to what stays free forever (freeze-and-retain, nothing is // taken away). Free rows mirror the old ALWAYS_FREE copy. const FEATURE_MATRIX: { label: string; free: boolean }[] = [ { label: 'Bokföring och rapporter', free: true }, { label: 'Fakturering', free: true }, { label: 'SIE-export', free: true }, { label: 'Org.nr-uppslag och momsnummerkontroll', free: true }, ...INCLUDED.map((label) => ({ label, free: false })), ] // Mirrors the checkout route's deferred-first-charge condition (Stripe's 48h // trial_end floor plus clock margin). Above this, checkout collects the card // but the first charge lands when the trial ends. const DEFER_THRESHOLD_MS = 49 * 3600 * 1000 interface BillingView { isPaying: boolean configured: boolean trialEndsAt: string | null daysLeft: number | null chargeDeferred: boolean paidJustNow: boolean isDemo: boolean } /** * Settings → Abonnemang. Rendered both as the full page (thin wrapper) and * inside the settings modal (via SETTINGS_SECTIONS), so it's a client component * that reads its state from GET /api/billing/status. */ export function BillingSettingsContent() { const [view, setView] = useState(null) useEffect(() => { let active = true fetch('/api/billing/status') .then((r) => r.json()) .then((d: { isPaying: boolean; configured: boolean; trialEndsAt: string | null; isDemo?: boolean }) => { if (!active) return // Compute time-derived state here (effect), not during render, to keep render pure. const msLeft = d.trialEndsAt ? new Date(d.trialEndsAt).getTime() - Date.now() : null const daysLeft = msLeft !== null ? Math.max(0, Math.ceil(msLeft / 86_400_000)) : null const chargeDeferred = msLeft !== null && msLeft > DEFER_THRESHOLD_MS // Set by the checkout success redirect. Provisioning happens via the // Stripe webhook, so isPaying can lag the redirect by a few seconds. const paidJustNow = new URLSearchParams(window.location.search).get('success') === '1' setView({ ...d, daysLeft, chargeDeferred, paidJustNow, isDemo: d.isDemo ?? false }) }) .catch(() => { if (active) setView({ isPaying: false, configured: false, trialEndsAt: null, daysLeft: null, chargeDeferred: false, paidJustNow: false, isDemo: false, }) }) return () => { active = false } }, []) if (!view) { return (
) } // Demo / sandbox account → can't check out. Show the value prop but point // them to creating a real account instead of a pay button that would 403. if (view.isDemo) { return ( Abonnemang

Du provkör Accounted i en demo. Skapa ett riktigt konto för att aktivera abonnemang, AI-assistent, bankkoppling och inlämning till Skatteverket.

    {INCLUDED.map((item) => (
  • {item}
  • ))}
) } // Paying company → manage view. if (view.isPaying) { return ( Abonnemang

Ditt abonnemang är aktivt. Du kan hantera eller avsluta det när som helst.

) } // Just returned from checkout but the webhook hasn't flipped isPaying yet → // confirm instead of re-showing the sell pitch to someone who already paid. if (view.paidJustNow) { return ( Abonnemang

Klart! Ditt abonnemang är aktiverat och alla funktioner låses upp inom någon minut.

Ladda om sidan om du inte ser ändringen.

) } // Trialing / expired → sell view. const { trialEndsAt, daysLeft } = view const deferredTo = view.chargeDeferred ? trialEndsAt : null return (
{daysLeft !== null && (
{daysLeft > 0 ? `Din provperiod löper ut om ${daysLeft} ${daysLeft === 1 ? 'dag' : 'dagar'}${ trialEndsAt ? ` (${formatDateLong(trialEndsAt)})` : '' }. ${ deferredTo ? 'Lägg till ditt kort nu: inget dras förrän provperioden är slut.' : 'Lägg till betalning nu så fortsätter allt utan avbrott.' }` : 'Din provperiod har löpt ut. Aktivera abonnemanget för att få tillbaka AI, bankkoppling och inlämning.'}
)} Allt du behöver för att sköta bokföringen själv {deferredTo && (

Så funkar det

  1. Idag Du lägger till ditt kort. Inget dras nu.
  2. {formatDateLong(deferredTo)} Provperioden slutar och den första debiteringen sker.
  3. När som helst Avsluta direkt via Stripe. Före {formatDateLong(deferredTo)} kostar det ingenting.
)} Funktion Gratis Abonnemang {FEATURE_MATRIX.map((f) => ( {f.label} {f.free ? ( ) : ( )} ))}

Utan abonnemang behåller du bokföringen, fakturorna, rapporterna och all din data utan kostnad. Ingenting raderas: räkenskapsinformation bevaras i sju år enligt bokföringslagen, oavsett abonnemang.

) }