Files
accounted/extensions/general/enable-banking/components/BankSelector.tsx
T
Jakob Wennberg 8e60112d77 fix: restore Enable Banking widget for bank list with popular banks quick-access
The previous rewrite replaced the working client-side widget with server-side
API calls that fail due to JWT auth issues, showing only 4 fallback banks
without logos. Restore the widget (handles logos, search, full bank list
natively) and add popular Swedish banks grid above for one-click connect.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 21:33:15 +01:00

152 lines
4.8 KiB
TypeScript

'use client'
import { useCallback, useEffect, useRef, useState } from 'react'
import { Loader2 } from 'lucide-react'
import { cn } from '@/lib/utils'
import Script from 'next/script'
export interface Bank {
name: string
country: string
logo?: string
bic?: string
}
interface BankSelectorProps {
onConnect: (bank: Bank) => void
isConnecting?: boolean
connectingBankName?: string | null
className?: string
}
/**
* Popular Swedish banks shown as quick-access buttons above the full widget.
* Country is always 'SE' since we only support Swedish banks.
*/
const POPULAR_BANKS: Bank[] = [
{ name: 'Nordea', country: 'SE' },
{ name: 'SEB', country: 'SE' },
{ name: 'Swedbank', country: 'SE' },
{ name: 'Handelsbanken', country: 'SE' },
{ name: 'Länsförsäkringar', country: 'SE' },
{ name: 'Skandia', country: 'SE' },
{ name: 'Danske Bank', country: 'SE' },
]
export function BankSelector({
onConnect,
isConnecting = false,
connectingBankName = null,
className,
}: BankSelectorProps) {
const widgetRef = useRef<HTMLElement | null>(null)
const [scriptReady, setScriptReady] = useState(false)
// Stable ref so the event listener never goes stale
const onConnectRef = useRef(onConnect)
useEffect(() => {
onConnectRef.current = onConnect
}, [onConnect])
const handleSelected = useCallback((e: Event) => {
const detail = (e as CustomEvent).detail as {
name: string
country: string
psuType: string
sandbox: boolean
}
onConnectRef.current({
name: detail.name,
country: detail.country,
})
}, [])
// Attach event listener once the script is ready and the widget is in the DOM
useEffect(() => {
if (!scriptReady) return
const widget = widgetRef.current
if (!widget) return
widget.addEventListener('selected', handleSelected)
return () => {
widget.removeEventListener('selected', handleSelected)
}
}, [scriptReady, handleSelected])
return (
<div className={cn('space-y-4', className)}>
{/* Popular banks — one-click connect */}
<div>
<p className="text-xs font-medium text-muted-foreground mb-2">Populära banker</p>
<div className="grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-4 gap-2">
{POPULAR_BANKS.map((bank) => {
const connecting = isConnecting && connectingBankName === bank.name
return (
<button
key={bank.name}
type="button"
disabled={isConnecting}
onClick={() => onConnect(bank)}
className={cn(
'flex items-center justify-center gap-2 rounded-lg border px-3 py-2.5 text-sm font-medium transition-colors',
'hover:bg-muted/50 hover:border-primary/30',
connecting && 'border-primary bg-primary/5',
isConnecting && !connecting && 'opacity-50 cursor-not-allowed'
)}
>
{connecting && (
<Loader2 className="h-3.5 w-3.5 animate-spin" />
)}
<span className="truncate">{bank.name}</span>
</button>
)
})}
</div>
</div>
{/* Full bank list via Enable Banking widget — includes logos and search */}
<div>
<p className="text-xs font-medium text-muted-foreground mb-2">Alla banker</p>
<Script
src="https://tilisy.enablebanking.com/lib/widgets.umd.min.js"
onReady={() => setScriptReady(true)}
strategy="afterInteractive"
/>
<link
href="https://tilisy.enablebanking.com/lib/widgets.css"
rel="stylesheet"
/>
{scriptReady ? (
<div className={cn(
'rounded-lg border overflow-hidden',
isConnecting && 'pointer-events-none opacity-50'
)}>
{/* @ts-expect-error - Enable Banking custom element */}
<enablebanking-aspsp-list
ref={widgetRef}
country="SE"
psu-type="business"
service="AIS"
style={{ minHeight: '200px' }}
/>
</div>
) : (
<div className="flex items-center justify-center rounded-lg border" style={{ minHeight: '200px' }}>
<Loader2 className="h-5 w-5 animate-spin text-muted-foreground" />
<span className="ml-2 text-sm text-muted-foreground">Laddar banker...</span>
</div>
)}
</div>
{/* Connecting overlay */}
{isConnecting && connectingBankName && (
<div className="flex items-center justify-center gap-2 p-3 rounded-lg bg-primary/5 border border-primary/20">
<Loader2 className="h-4 w-4 animate-spin text-primary" />
<span className="text-sm font-medium">Ansluter till {connectingBankName}...</span>
</div>
)}
</div>
)
}