'use client' import { useState } from 'react' import { useRouter } from 'next/navigation' import { useTranslations } from 'next-intl' import Link from 'next/link' import { Building2, ArrowRight, Loader2, Plus, AlertTriangle } from 'lucide-react' import { cn } from '@/lib/utils' import { useToast } from '@/components/ui/use-toast' import { switchCompany } from '@/lib/company/actions' import { mapEntityType } from '@/lib/company-lookup/entity-type-map' import type { EnrichmentCompanyRole } from '@/lib/company-lookup/types' import { getBranding } from '@/lib/branding/service' const branding = getBranding() export interface MemberCompany { id: string name: string orgNumber: string | null entityType: string | null role: string } export interface TicPickerCompany { role: EnrichmentCompanyRole /** 'new' = not in Accounted, can set up. 'exists' = in Accounted 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 } // Swedish legal entity names (Aktiebolag, Enskild firma, etc.) are statutory // terms — kept in Swedish in both locales. 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 t = useTranslations('select_company') const [setup, setSetup] = useState({ kind: 'idle' }) const hour = new Date().getHours() const greeting = hour < 5 ? t('greeting_night') : hour < 10 ? t('greeting_morning') : hour < 14 ? t('greeting_hello') : hour < 18 ? t('greeting_afternoon') : t('greeting_evening') const busy = setup.kind !== 'idle' async function handleOpenMember(companyId: string) { if (busy) return setSetup({ kind: 'opening', companyId }) const result = await switchCompany(companyId) if (result.error) { toast({ title: t(result.error === 'not_member' ? 'error_no_access' : 'error_switch_failed'), variant: 'destructive', }) setSetup({ kind: 'idle' }) return } window.location.assign('/') } // BankID picker no longer one-click-provisions. Every pick routes to the // onboarding wizard with the orgnr (and entity_type via the server-side // CompanyRoles match in /onboarding) pre-filled. F-skatt / VAT / address // are confirmed by the user in Steps 2-4 instead of being auto-fetched // from TIC — costs us ~1 Lens call per signup but avoids any TIC budget // spend (companyRoles is on the Identity API, which is a separate quota). function handleCreateFromTic(role: EnrichmentCompanyRole) { if (busy) return const orgNumber = role.companyRegistrationNumber.replace(/[\s-]/g, '') router.push(`/onboarding?org_number=${encodeURIComponent(orgNumber)}`) } return (

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

{t('subtitle')}

{enrichmentStale && (

{t('enrichment_stale')}

)} {memberCompanies.length > 0 && (

{t('section_your_companies', { appName: branding.appName.toLowerCase() })}

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

{t('section_bankid_companies')}

    {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 // Companies already in gnubok under another account are still // offered for setup — org-number reuse is allowed (a director // may keep a separate test copy). Keep a muted note so the // "also already in {app}" context isn't lost. const existsNote = status === 'exists' ? (

    {t('already_in_app', { appName: branding.appName.toLowerCase() })}

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

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

    {t('setup_manually')}
    {existsNote}
  • ) } return (
  • ) })}
)} {memberCompanies.length === 0 && ticCompanies.length === 0 && (

{t('no_companies_found')}

)}
{t('or_separator')}
{t('add_company_manually')}
) }