'use client' import { useState, useTransition } from 'react' import { useRouter } from 'next/navigation' import Link from 'next/link' import { Building2, ArrowRight, Loader2, Plus, Check, AlertTriangle } from 'lucide-react' import { cn } from '@/lib/utils' import { useToast } from '@/components/ui/use-toast' import { switchCompany, createCompanyFromTicRole } from '@/lib/company/actions' import { mapEntityType } from '@/lib/company-lookup/entity-type-map' import type { CompanyLookupResult, EnrichmentCompanyRole } from '@/lib/company-lookup/types' export interface MemberCompany { id: string name: string orgNumber: string | null entityType: string | null role: string } export interface TicPickerCompany { role: EnrichmentCompanyRole /** 'new' = not in gnubok, can set up. 'exists' = in gnubok but user is not a member. */ status: 'new' | 'exists' } interface BankIdCompanyPickerProps { firstName: string | null teamId: string memberCompanies: MemberCompany[] ticCompanies: TicPickerCompany[] enrichmentStale: boolean } type SetupState = | { kind: 'idle' } | { kind: 'opening'; companyId: string } | { kind: 'creating'; orgNumber: string; step: 'lookup' | 'provision' } function humanEntityType(t: string | null | undefined): string { if (!t) return '' if (t === 'aktiebolag') return 'Aktiebolag' if (t === 'enskild_firma') return 'Enskild firma' // TIC legalEntityType strings ('AB', 'HB', etc.) fall through to this return t } function humanTicEntityType(t: string): string { const mapped = mapEntityType(t) if (mapped === 'aktiebolag') return 'Aktiebolag' if (mapped === 'enskild_firma') return 'Enskild firma' if (t.toLowerCase().includes('handelsbolag') || t.toLowerCase() === 'hb') return 'Handelsbolag' if (t.toLowerCase().includes('kommanditbolag') || t.toLowerCase() === 'kb') return 'Kommanditbolag' return t } function positionLabel(role: EnrichmentCompanyRole): string { const descs = role.positionDescriptions?.filter(Boolean) if (descs && descs.length > 0) return descs.join(' · ') const types = role.positionTypes?.filter(Boolean) if (types && types.length > 0) return types.join(' · ') return '' } export default function BankIdCompanyPicker({ firstName, teamId, memberCompanies, ticCompanies, enrichmentStale, }: BankIdCompanyPickerProps) { const router = useRouter() const { toast } = useToast() const [setup, setSetup] = useState({ kind: 'idle' }) const [isPending, startTransition] = useTransition() const hour = new Date().getHours() const greeting = hour < 5 ? 'God natt' : hour < 10 ? 'Godmorgon' : hour < 14 ? 'Hej' : hour < 18 ? 'God eftermiddag' : 'God kväll' const busy = setup.kind !== 'idle' || isPending async function handleOpenMember(companyId: string) { if (busy) return setSetup({ kind: 'opening', companyId }) const result = await switchCompany(companyId) if (result.error) { toast({ title: 'Fel', description: result.error, variant: 'destructive' }) setSetup({ kind: 'idle' }) return } window.location.assign('/') } async function handleCreateFromTic(role: EnrichmentCompanyRole) { if (busy) return const orgNumber = role.companyRegistrationNumber.replace(/[\s-]/g, '') const mapped = mapEntityType(role.legalEntityType) if (!mapped) { // Entity type isn't supported end-to-end — route to manual wizard with // the org number pre-filled. router.push(`/onboarding?org_number=${encodeURIComponent(orgNumber)}`) return } setSetup({ kind: 'creating', orgNumber, step: 'lookup' }) let lookup: CompanyLookupResult | null = null try { const res = await fetch( `/api/extensions/ext/tic/lookup?org_number=${encodeURIComponent(orgNumber)}`, { method: 'GET' }, ) if (res.ok) { const json = await res.json() lookup = (json?.data as CompanyLookupResult | undefined) ?? null } } catch { // Network/parse failure — fall through to the lookup-missing branch below. } // Without a lookup we don't know the company's VAT/F-skatt status. // Defaulting those to false for a momsregistrerat bolag would silently // create a company that issues invoices without moms — ML 17 kap violation. // Route to the manual wizard with the known fields pre-filled instead. if (!lookup) { toast({ title: 'Kunde inte hämta företagsuppgifter', description: 'Fyll i resterande uppgifter manuellt.', }) setSetup({ kind: 'idle' }) router.push(`/onboarding?org_number=${encodeURIComponent(orgNumber)}`) return } setSetup({ kind: 'creating', orgNumber, step: 'provision' }) startTransition(async () => { const result = await createCompanyFromTicRole({ teamId, orgNumber, legalName: role.legalName, legalEntityType: role.legalEntityType, lookup, }) if (result.error === 'lookup_missing') { // Extremely unlikely (we just verified lookup above) but if it happens, // the same fallback applies. setSetup({ kind: 'idle' }) router.push(`/onboarding?org_number=${encodeURIComponent(orgNumber)}`) return } if (result.error || !result.companyId) { toast({ title: 'Kunde inte skapa företag', description: result.error ?? 'Försök igen eller lägg till manuellt.', variant: 'destructive', }) setSetup({ kind: 'idle' }) return } toast({ title: 'Välkommen!', description: 'Ditt företag är nu redo.' }) window.location.assign('/') }) } // Progress card while creating if (setup.kind === 'creating') { const lookupDone = setup.step === 'provision' return (

{greeting}{firstName ? `, ${firstName}` : ''}

Sätter upp ditt företag…

Org.nr {setup.orgNumber}

  • {lookupDone ? : } Hämtar uppgifter från Bolagsverket
  • {lookupDone ? : } Skapar företag och kontoplan
) } return (

{greeting}{firstName ? `, ${firstName}` : ''}

Välj ett företag att öppna eller lägg till ett nytt.

{enrichmentStale && (

Uppgifterna från BankID är äldre än en vecka. Logga in med BankID igen för att uppdatera listan.

)} {memberCompanies.length > 0 && (

Dina företag i gnubok

    {memberCompanies.map((c) => { const isOpening = setup.kind === 'opening' && setup.companyId === c.id return (
  • ) })}
)} {ticCompanies.length > 0 && (

Företag kopplade till ditt BankID

    {ticCompanies.map(({ role, status }) => { const cleaned = role.companyRegistrationNumber.replace(/[\s-]/g, '') const position = positionLabel(role) const entityLabel = humanTicEntityType(role.legalEntityType) const mappable = mapEntityType(role.legalEntityType) !== null if (status === 'exists') { return (
  • {role.legalName}

    {cleaned} · {entityLabel}

    Finns redan i gnubok

    Be en befintlig administratör att bjuda in dig.

  • ) } if (!mappable) { return (
  • {role.legalName}

    {cleaned} · {entityLabel} {position ? ` · ${position}` : ''}

    Sätts upp manuellt
  • ) } return (
  • ) })}
)} {memberCompanies.length === 0 && ticCompanies.length === 0 && (

Inga företag hittades. Lägg till ditt första företag nedan.

)}
eller
Lägg till företag manuellt
) }