'use client' import { useState, useEffect, useRef } from 'react' import Link from 'next/link' import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card' import { Button } from '@/components/ui/button' import { useToast } from '@/components/ui/use-toast' import { DestructiveConfirmDialog, useDestructiveConfirm } from '@/components/ui/destructive-confirm-dialog' import { Loader2, Upload } from 'lucide-react' import { cn } from '@/lib/utils' import { createClient } from '@/lib/supabase/client' import { useCompany } from '@/contexts/CompanyContext' import { BankSelector, type Bank } from './BankSelector' import { BankConnectionStatus } from './BankConnectionStatus' import type { BankConnection } from '@/types' /** * Self-contained banking settings panel for the enable-banking extension. * Loaded dynamically by the settings panel registry. */ export default function BankingSettingsPanel() { const { toast } = useToast() const supabase = createClient() const { dialogProps, confirm } = useDestructiveConfirm() const { company } = useCompany() const [bankConnections, setBankConnections] = useState([]) const [syncingConnectionId, setSyncingConnectionId] = useState(null) const [isConnecting, setIsConnecting] = useState(false) const [connectingBankName, setConnectingBankName] = useState(null) const connectingRef = useRef(false) const [isLoading, setIsLoading] = useState(true) const [showCsvFallback, setShowCsvFallback] = useState(false) const [psuType, setPsuType] = useState<'personal' | 'business'>('business') useEffect(() => { fetchConnections() }, []) async function fetchConnections() { setIsLoading(true) const { data: { user } } = await supabase.auth.getUser() if (!user) return if (!company) return const { data: connections } = await supabase .from('bank_connections') .select('*') .eq('company_id', company.id) .order('created_at', { ascending: false }) setBankConnections(connections || []) setIsLoading(false) } async function handleConnectBank(bank: Bank, psuTypeOverride?: 'personal' | 'business') { if (connectingRef.current) return connectingRef.current = true setIsConnecting(true) setConnectingBankName(bank.name) try { console.log('[enable-banking] Initiating bank connection', { bankName: bank.name, bankCountry: bank.country, psuTypeOverride, }) const body: Record = { aspsp_name: bank.name, aspsp_country: bank.country } if (psuTypeOverride) body.psu_type = psuTypeOverride const response = await fetch('/api/extensions/ext/enable-banking/connect', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body), }) const data = await response.json() if (!response.ok) { console.error('[enable-banking] Connect request failed', { status: response.status, statusText: response.statusText, error: data.error, bankName: bank.name, }) throw new Error(data.error) } console.log('[enable-banking] Redirecting to bank authorization', { connectionId: data.connection_id, hasAuthUrl: !!data.authorization_url, }) window.location.href = data.authorization_url } catch (error) { console.error('[enable-banking] Connect flow failed', { message: error instanceof Error ? error.message : String(error), stack: error instanceof Error ? error.stack : undefined, bankName: bank.name, }) toast({ title: 'Fel', description: error instanceof Error ? error.message : 'Kunde inte ansluta bank', variant: 'destructive', }) connectingRef.current = false setIsConnecting(false) setConnectingBankName(null) setShowCsvFallback(true) } } async function handleSyncTransactions(connectionId: string) { setSyncingConnectionId(connectionId) try { console.log('[enable-banking] Starting sync', { connectionId }) const response = await fetch('/api/extensions/ext/enable-banking/sync', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ connection_id: connectionId }), }) const data = await response.json() if (!response.ok) { console.error('[enable-banking] Sync request failed', { status: response.status, statusText: response.statusText, error: data.error, connectionId, }) throw new Error(data.error) } console.log('[enable-banking] Sync completed', { connectionId, imported: data.imported, duplicates: data.duplicates, }) toast({ title: 'Synkronisering klar', description: `${data.imported} nya transaktioner importerade`, }) setShowCsvFallback(false) fetchConnections() } catch (error) { console.error('[enable-banking] Sync flow failed', { message: error instanceof Error ? error.message : String(error), stack: error instanceof Error ? error.stack : undefined, connectionId, }) toast({ title: 'Fel', description: error instanceof Error ? error.message : 'Synkronisering misslyckades', variant: 'destructive', }) setShowCsvFallback(true) } setSyncingConnectionId(null) } async function handleDisconnectBank(connectionId: string) { const ok = await confirm({ title: 'Koppla bort bank?', description: 'PSD2-samtycket kommer återkallas. Befintliga transaktioner påverkas inte.', confirmLabel: 'Koppla bort', variant: 'warning', }) if (!ok) return try { console.log('[enable-banking] Disconnecting bank', { connectionId }) const response = await fetch('/api/extensions/ext/enable-banking/disconnect', { method: 'DELETE', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ connection_id: connectionId }), }) if (!response.ok) { const data = await response.json() console.error('[enable-banking] Disconnect request failed', { status: response.status, statusText: response.statusText, error: data.error, connectionId, }) throw new Error(data.error || 'Disconnect failed') } console.log('[enable-banking] Bank disconnected', { connectionId }) toast({ title: 'Bank bortkopplad', description: 'Bankanslutningen och PSD2-samtycket har återkallats', }) fetchConnections() } catch (error) { console.error('[enable-banking] Disconnect flow failed', { message: error instanceof Error ? error.message : String(error), stack: error instanceof Error ? error.stack : undefined, connectionId, }) toast({ title: 'Fel', description: error instanceof Error ? error.message : 'Kunde inte koppla bort bank', variant: 'destructive', }) } } if (isLoading) { return (
) } const activeConnections = bankConnections.filter((c) => c.status === 'active') const actionRequiredConnections = bankConnections.filter((c) => ['expired', 'error'].includes(c.status)) return (
{/* Persistent CSV fallback after connection/sync failure */} {showCsvFallback && (

Har du problem med bankanslutningen? Du kan importera transaktioner manuellt via bankfil.

)} {/* Action required — expired/error connections */} {actionRequiredConnections.length > 0 && ( Åtgärd krävs Dessa anslutningar behöver uppmärksamhet. {actionRequiredConnections.map((connection) => ( ))} )} {/* Connected banks */} {activeConnections.length > 0 && ( Anslutna banker {activeConnections.map((connection) => ( ))} )} {/* Connect new bank */} Anslut ny bank Välj din bank nedan för att koppla ditt konto via PSD2. {/* Account type selector */}
Kontotyp:
{psuType === 'personal' && (

Välj Privatkonto om du använder ditt personliga bankkonto för din verksamhet (vanligt för enskild firma).

)} handleConnectBank(bank, psuType)} onPsuTypeDetected={setPsuType} isConnecting={isConnecting} connectingBankName={connectingBankName} />
{/* Info about PSD2 */} Om bankintegration (PSD2) Automatisk import av transaktioner via PSD2 open banking. Samtycket gäller i 90 dagar och behöver sedan förnyas.

Vi använder säker bankintegration (PSD2). Vi kan endast läsa transaktioner, aldrig flytta pengar. Du kan också importera transaktioner manuellt via bankfiler på importsidan.

) }