'use client' import { useState, useEffect, useRef } from 'react' import { useTranslations } from 'next-intl' import { useSearchParams, useRouter } from 'next/navigation' import Link from 'next/link' import { Card, CardContent } from '@/components/ui/card' import { Button } from '@/components/ui/button' import { useToast } from '@/components/ui/use-toast' import { AlertTriangle, CreditCard, ExternalLink } from 'lucide-react' import { getSettingsPanel } from '@/lib/extensions/settings-panel-registry' import { ENABLED_EXTENSION_IDS } from '@/lib/extensions/_generated/enabled-extensions' import BankSyncStatusChip from '@/components/transactions/BankSyncStatusChip' const BankingPanel = getSettingsPanel('enable-banking') export function BankingSettingsContent() { const t = useTranslations('settings_banking') const searchParams = useSearchParams() const router = useRouter() const { toast } = useToast() const [bankConnectionError, setBankConnectionError] = useState(null) const [failedBankName, setFailedBankName] = useState(null) const [isAccessDenied, setIsAccessDenied] = useState(false) const [showHbPoaHint, setShowHbPoaHint] = useState(false) const syncInitiatedRef = useRef(false) const abortControllerRef = useRef(null) const unmountedRef = useRef(false) const hasBankingExtension = ENABLED_EXTENSION_IDS.has('enable-banking') useEffect(() => { return () => { unmountedRef.current = true if (abortControllerRef.current) abortControllerRef.current.abort() } }, []) useEffect(() => { const bankConnected = searchParams.get('bank_connected') const bankError = searchParams.get('bank_error') if (bankConnected === 'true' && !syncInitiatedRef.current) { syncInitiatedRef.current = true const connectionId = searchParams.get('connection_id') router.replace('/settings/banking') if (connectionId) { toast({ title: t('sync_start_title'), description: t('sync_start_description'), }) const controller = new AbortController() abortControllerRef.current = controller const syncTimeout = setTimeout(() => controller.abort(), 120_000) ;(async () => { try { const res = await fetch('/api/extensions/ext/enable-banking/sync', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ connection_id: connectionId, days_back: 120 }), signal: controller.signal, }) clearTimeout(syncTimeout) const data = await res.json() if (res.ok) { if (!unmountedRef.current) { toast({ title: t('sync_success_title'), description: t('sync_success_description', { count: data.imported ?? 0 }), }) } } else { throw new Error(data.error || 'Sync failed') } } catch (err) { clearTimeout(syncTimeout) if (unmountedRef.current) return if (controller.signal.aborted) { toast({ title: t('sync_timeout_title'), description: t('sync_timeout_description'), }) } else { toast({ title: t('sync_failed_title'), description: err instanceof Error ? err.message : t('sync_failed_default'), variant: 'destructive', }) } } })() } else { toast({ title: t('sync_success_title'), description: t('sync_success_no_id_description'), }) } } if (bankError) { let errorMsg: string try { errorMsg = decodeURIComponent(bankError) } catch { errorMsg = bankError } const bankName = searchParams.get('bank_name') const errorCode = searchParams.get('bank_error_code') const psuType = searchParams.get('psu_type') // The bank often returns a bare "server_error" with no description — // show a human message instead of the raw OAuth error code. if (errorCode === 'server_error' && errorMsg === 'server_error') { errorMsg = t('bank_server_error') } toast({ title: t('connect_failed_title'), description: errorMsg, variant: 'destructive', }) setBankConnectionError(errorMsg) if (bankName) setFailedBankName(bankName) if (errorCode === 'access_denied') setIsAccessDenied(true) // Handelsbanken rejects business connects with server_error when the // company hasn't registered the open banking fullmakt ("Internet // Företag – tilläggstjänst API Företag") — surface the fix steps. if (bankName === 'Handelsbanken' && psuType === 'business' && errorCode === 'server_error') { setShowHbPoaHint(true) } router.replace('/settings/banking') } }, [searchParams, router, toast, t]) return (
{bankConnectionError && (

{bankConnectionError}

{isAccessDenied && failedBankName && (

{t('access_denied_hint', { bankName: failedBankName })}

)} {showHbPoaHint && (

{t('hb_business_poa_hint')}{' '} {t('hb_business_poa_link')}

)}

{t('import_fallback_text')}{t('import_fallback_link')}{t('import_fallback_suffix')}

)} {hasBankingExtension && BankingPanel ? ( <> ) : (

{t('not_enabled_title')}

{t('not_enabled_description')}

)}
) }