diff --git a/app/(dashboard)/invoices/new/page.tsx b/app/(dashboard)/invoices/new/page.tsx index 371392dd..973d5924 100644 --- a/app/(dashboard)/invoices/new/page.tsx +++ b/app/(dashboard)/invoices/new/page.tsx @@ -1,6 +1,6 @@ 'use client' -import { useState, useEffect } from 'react' +import { useState, useEffect, useRef } from 'react' import { useRouter } from 'next/navigation' import { createClient } from '@/lib/supabase/client' import { useForm, useFieldArray, Controller } from 'react-hook-form' @@ -69,6 +69,7 @@ export default function NewInvoicePage() { const [, setDefaultNotes] = useState(null) const [isCreateCustomerOpen, setIsCreateCustomerOpen] = useState(false) const [isCreatingCustomer, setIsCreatingCustomer] = useState(false) + const pendingCustomerRef = useRef(null) const { register, @@ -107,6 +108,16 @@ export default function NewInvoicePage() { const watchCustomerId = watch('customer_id') const watchDocumentType = watch('document_type') as InvoiceDocumentType + // After customers state updates with the new customer, select it + useEffect(() => { + const pending = pendingCustomerRef.current + if (pending && customers.some((c) => c.id === pending.id)) { + setValue('customer_id', pending.id, { shouldValidate: true, shouldDirty: true }) + setSelectedCustomer(pending) + pendingCustomerRef.current = null + } + }, [customers, setValue]) + useEffect(() => { fetchCustomers() fetchDefaultNotes() @@ -189,8 +200,8 @@ export default function NewInvoicePage() { title: 'Kund skapad', description: `${data.name} har lagts till`, }) - setCustomers([...customers, result.data]) - setValue('customer_id', result.data.id) + pendingCustomerRef.current = result.data + setCustomers(prev => [...prev, result.data]) setIsCreateCustomerOpen(false) } @@ -426,11 +437,14 @@ export default function NewInvoicePage() { const lineTotal = (watchItems[index]?.quantity || 0) * (watchItems[index]?.unit_price || 0) const lineVat = Math.round(lineTotal * (watchItems[index]?.vat_rate ?? 25) / 100 * 100) / 100 return ( -
- {/* Desktop: 12-col grid */} -
-
- +
+ {/* Description + mobile delete button */} +
+
+ )}
-
- + +
+ + {/* Antal, Enhet, à-pris */} +
+
+
-
- +
+
-
- +
+
-
- - ( - - )} - /> -
-
- -
- {/* Mobile: compact card layout */} -
-
-
- - - {errors.items?.[index]?.description && ( -

- {errors.items[index].description?.message} -

- )} -
- -
-
-
- - -
-
- - ( - - )} - /> -
-
- - -
-
- {isRateLocked ? ( -
- -

- {availableRates.find(r => r.rate === (watchItems[index]?.vat_rate ?? 25))?.label ?? `${watchItems[index]?.vat_rate ?? 25}%`} -

-
- ) : ( -
- - ( - - )} - /> -
- )} -
- Rad {index + 1} - {formatCurrency(lineTotal + lineVat, watchCurrency)} -
+ {/* Moms */} +
+ + ( + + )} + /> +
+ + {/* Desktop delete button */} +
+ +
+ + {/* Mobile summary row */} +
+ Rad {index + 1} + {formatCurrency(lineTotal + lineVat, watchCurrency)}
) diff --git a/app/globals.css b/app/globals.css index 060b7096..721efca2 100644 --- a/app/globals.css +++ b/app/globals.css @@ -317,6 +317,16 @@ h1, h2, h3 { display: none; } +/* Hide number input spinners */ +input[type="number"]::-webkit-inner-spin-button, +input[type="number"]::-webkit-outer-spin-button { + -webkit-appearance: none; + margin: 0; +} +input[type="number"] { + -moz-appearance: textfield; +} + /* Selection color */ ::selection { background: hsl(var(--primary) / 0.15); diff --git a/components/onboarding/Step1EntityType.tsx b/components/onboarding/Step1EntityType.tsx index 6fec0de1..4e595248 100644 --- a/components/onboarding/Step1EntityType.tsx +++ b/components/onboarding/Step1EntityType.tsx @@ -42,6 +42,8 @@ export default function Step1EntityType({ initialData, onNext, isSaving }: Step1 if (selected) { onNext({ entity_type: selected }) } + console.warn("Something went wrong") + console.warn("Selected: ", selected) } return ( diff --git a/extensions/general/enable-banking/components/BankSelector.tsx b/extensions/general/enable-banking/components/BankSelector.tsx index ffcea0da..a19af9d4 100644 --- a/extensions/general/enable-banking/components/BankSelector.tsx +++ b/extensions/general/enable-banking/components/BankSelector.tsx @@ -1,9 +1,7 @@ 'use client' -import { useCallback, useEffect, useRef, useState } from 'react' -import { Loader2 } from 'lucide-react' +import { useEffect, useRef, useState } from 'react' import { cn } from '@/lib/utils' -import Script from 'next/script' export interface Bank { name: string @@ -12,6 +10,14 @@ export interface Bank { bic?: string } +const POPULAR_SWEDISH_BANKS = [ + 'Swedbank', + 'SEB', + 'Nordea', + 'Handelsbanken', + 'Danske Bank', +] + interface BankSelectorProps { onConnect: (bank: Bank) => void isConnecting?: boolean @@ -19,19 +25,67 @@ interface BankSelectorProps { 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' }, -] +function BankCard({ bank, isConnecting, connectingBankName, onConnect }: { + bank: Bank + isConnecting: boolean + connectingBankName: string | null + onConnect: (bank: Bank) => void +}) { + const connecting = isConnecting && connectingBankName === bank.name + + return ( + + ) +} export function BankSelector({ onConnect, @@ -39,111 +93,130 @@ export function BankSelector({ connectingBankName = null, className, }: BankSelectorProps) { - const widgetRef = useRef(null) - const [scriptReady, setScriptReady] = useState(false) + const [banks, setBanks] = useState([]) + const [isSandbox, setIsSandbox] = useState(true) + const [isLoading, setIsLoading] = useState(true) + const [error, setError] = useState(null) + const [searchQuery, setSearchQuery] = useState('') + const searchRef = useRef(null) - // 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 + async function fetchBanks() { + try { + const res = await fetch('/api/extensions/ext/enable-banking/banks') + const data = await res.json() + if (data.banks) { + setBanks(data.banks as Bank[]) + } + if (data.sandbox !== undefined) { + setIsSandbox(data.sandbox) + } + } catch { + setError('Kunde inte ladda banker') + } finally { + setIsLoading(false) + } } - onConnectRef.current({ - name: detail.name, - country: detail.country, - }) + fetchBanks() }, []) - // 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 + const filteredBanks = banks.filter((bank) => + bank.name.toLowerCase().includes(searchQuery.toLowerCase()) + ) - widget.addEventListener('selected', handleSelected) - return () => { - widget.removeEventListener('selected', handleSelected) - } - }, [scriptReady, handleSelected]) + const showPopular = !isSandbox && !searchQuery + const popularBanks = showPopular + ? filteredBanks.filter((bank) => POPULAR_SWEDISH_BANKS.includes(bank.name)) + : [] + const otherBanks = showPopular + ? filteredBanks.filter((bank) => !POPULAR_SWEDISH_BANKS.includes(bank.name)) + : filteredBanks return (
- {/* Popular banks — one-click connect */} -
-

Populära banker

-
- {POPULAR_BANKS.map((bank) => { - const connecting = isConnecting && connectingBankName === bank.name - return ( - - ) - })} + {/* Search input */} +
+ + + + setSearchQuery(e.target.value)} + className="w-full pl-10 pr-3 py-3 border border-border rounded-lg bg-background text-foreground text-sm placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:border-ring transition-all" + /> +
+ + {/* Loading state */} + {isLoading && ( +
+
-
+ )} - {/* Full bank list via Enable Banking widget — includes logos and search */} -
-

Alla banker

-