'use client' import { useEffect, useRef, useState } from 'react' import { Input } from '@/components/ui/input' import { cn } from '@/lib/utils' interface BankOption { name: string logo?: string bic?: string } const FALLBACK_BANKS: BankOption[] = [ { name: 'Nordea', bic: 'NDEASESS' }, { name: 'SEB', bic: 'ESSESESS' }, { name: 'Swedbank', bic: 'SWEDSESS' }, { name: 'Handelsbanken', bic: 'HANDSESS' }, { name: 'Danske Bank', bic: 'DABASES' }, { name: 'Länsförsäkringar', bic: 'ELLFSESS' }, { name: 'Skandiabanken', bic: 'SKIASESS' }, { name: 'ICA Banken' }, { name: 'Avanza Bank' }, { name: 'Sparbanken' }, ] interface BankNameComboboxProps { defaultValue?: string value?: string onChange?: (value: string) => void enableBankingEnabled?: boolean } export function BankNameCombobox({ defaultValue = '', value: controlledValue, onChange, enableBankingEnabled = false }: BankNameComboboxProps) { const isControlled = controlledValue !== undefined const [internalValue, setInternalValue] = useState(defaultValue) const value = isControlled ? controlledValue : internalValue const setValue = (v: string) => { if (!isControlled) setInternalValue(v) onChange?.(v) } const [banks, setBanks] = useState(FALLBACK_BANKS) const [isOpen, setIsOpen] = useState(false) const [highlightedIndex, setHighlightedIndex] = useState(-1) const containerRef = useRef(null) const inputRef = useRef(null) const listRef = useRef(null) useEffect(() => { if (!enableBankingEnabled) return let cancelled = false async function fetchBanks() { try { const res = await fetch('/api/extensions/ext/enable-banking/banks') if (!res.ok) return const data = await res.json() if (!cancelled && data.banks?.length > 0) { setBanks(data.banks) } } catch { // Keep fallback list } } fetchBanks() return () => { cancelled = true } }, [enableBankingEnabled]) const filtered = value.trim() ? banks.filter((b) => b.name.toLowerCase().includes(value.toLowerCase())) : banks useEffect(() => { setHighlightedIndex(-1) }, [value]) useEffect(() => { if (!isOpen) return function handleClickOutside(e: MouseEvent) { if (containerRef.current && !containerRef.current.contains(e.target as Node)) { setIsOpen(false) } } document.addEventListener('mousedown', handleClickOutside) return () => document.removeEventListener('mousedown', handleClickOutside) }, [isOpen]) // Scroll highlighted item into view useEffect(() => { if (highlightedIndex < 0 || !listRef.current) return const item = listRef.current.children[highlightedIndex] as HTMLElement item?.scrollIntoView({ block: 'nearest' }) }, [highlightedIndex]) function selectBank(bank: BankOption) { setValue(bank.name) setIsOpen(false) inputRef.current?.focus() } function handleKeyDown(e: React.KeyboardEvent) { if (!isOpen && (e.key === 'ArrowDown' || e.key === 'ArrowUp')) { setIsOpen(true) e.preventDefault() return } if (!isOpen) return switch (e.key) { case 'ArrowDown': e.preventDefault() setHighlightedIndex((i) => (i < filtered.length - 1 ? i + 1 : 0)) break case 'ArrowUp': e.preventDefault() setHighlightedIndex((i) => (i > 0 ? i - 1 : filtered.length - 1)) break case 'Enter': e.preventDefault() if (highlightedIndex >= 0 && filtered[highlightedIndex]) { selectBank(filtered[highlightedIndex]) } break case 'Escape': setIsOpen(false) break } } return (
{ setValue(e.target.value) if (!isOpen) setIsOpen(true) }} onFocus={() => setIsOpen(true)} onKeyDown={handleKeyDown} role="combobox" aria-expanded={isOpen} aria-autocomplete="list" aria-controls="bank-name-listbox" autoComplete="off" /> {isOpen && filtered.length > 0 && (
    {filtered.map((bank, i) => (
  • setHighlightedIndex(i)} onMouseDown={(e) => { e.preventDefault() // prevent blur before click registers selectBank(bank) }} > {bank.logo ? ( ) : ( )} {bank.name}
  • ))}
)}
) }