'use client' import { useState, useEffect } from 'react' import { useRouter, useSearchParams } from 'next/navigation' import Link from 'next/link' import { createClient } from '@/lib/supabase/client' import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card' import { Button } from '@/components/ui/button' import { Input } from '@/components/ui/input' import { Textarea } from '@/components/ui/textarea' import { Label } from '@/components/ui/label' import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs' import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select' import { useToast } from '@/components/ui/use-toast' import { Separator } from '@/components/ui/separator' import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from '@/components/ui/dialog' import { Loader2, CreditCard, LogOut, Sun, Moon, Monitor, ExternalLink, AlertTriangle, } from 'lucide-react' import { useTheme } from 'next-themes' import type { CompanySettings } from '@/types' import { validateBankgiroNumber, formatBankgiroNumber } from '@/lib/bankgiro/luhn' import { CalendarFeedSettings } from '@/components/settings/CalendarFeedSettings' import { getSettingsPanel } from '@/lib/extensions/settings-panel-registry' import { SecuritySettings } from '@/components/settings/SecuritySettings' import { ApiKeysPanel } from '@/components/settings/ApiKeysPanel' import { ENABLED_EXTENSION_IDS } from '@/lib/extensions/_generated/enabled-extensions' const BankingPanel = getSettingsPanel('enable-banking') export default function SettingsPage() { const router = useRouter() const searchParams = useSearchParams() const { toast } = useToast() const supabase = createClient() const [isLoading, setIsLoading] = useState(true) const [isSaving, setIsSaving] = useState(false) const [settings, setSettings] = useState(null) const hasBankingExtension = ENABLED_EXTENSION_IDS.has('enable-banking') const hasCalendarExtension = ENABLED_EXTENSION_IDS.has('calendar') const hasMcpExtension = ENABLED_EXTENSION_IDS.has('mcp-server') const [bankConnectionError, setBankConnectionError] = useState(null) const [bankgiroError, setBankgiroError] = useState(null) const [showDeleteDialog, setShowDeleteDialog] = useState(false) const [deleteConfirmText, setDeleteConfirmText] = useState('') const [isDeleting, setIsDeleting] = useState(false) const { theme, setTheme } = useTheme() const [mounted, setMounted] = useState(false) const initialTab = searchParams.get('tab') || 'company' const [activeTab, setActiveTab] = useState(initialTab) const settingsTabs = [ { value: 'company', label: 'Företag', show: true }, { value: 'banking', label: 'Bank (PSD2)', show: !settings?.is_sandbox }, { value: 'calendar', label: 'Kalender', show: hasCalendarExtension }, { value: 'security', label: 'Säkerhet', show: true }, { value: 'appearance', label: 'Utseende', show: true }, { value: 'api', label: 'API', show: hasMcpExtension }, { value: 'account', label: 'Konto', show: true }, ].filter(t => t.show) useEffect(() => { setMounted(true) }, []) async function fetchData() { setIsLoading(true) const { data: { user } } = await supabase.auth.getUser() if (!user) { router.push('/login') return } const settingsRes = await supabase.from('company_settings').select('*').eq('user_id', user.id).single() setSettings(settingsRes.data) setIsLoading(false) } useEffect(() => { fetchData() // Handle callback messages const bankConnected = searchParams.get('bank_connected') const bankError = searchParams.get('bank_error') if (bankConnected === 'true') { toast({ title: 'Bank ansluten!', description: 'Din bank är nu kopplad. Transaktioner hämtas...', }) // Auto-sync transactions after connection const connectionId = searchParams.get('connection_id') if (connectionId) { fetch('/api/extensions/ext/enable-banking/sync', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ connection_id: connectionId, days_back: 90 }), }) .then(res => res.json()) .then(data => { if (data.imported > 0) { toast({ title: 'Transaktioner hämtade', description: `${data.imported} transaktioner importerade`, }) } }) .catch(() => {}) } router.replace('/settings?tab=banking') } if (bankError) { const errorMsg = decodeURIComponent(bankError) toast({ title: 'Anslutning misslyckades', description: errorMsg, variant: 'destructive', }) setBankConnectionError(errorMsg) setActiveTab('banking') router.replace('/settings?tab=banking') } }, [searchParams]) async function handleSaveSettings(e: React.FormEvent) { e.preventDefault() if (!settings) return setIsSaving(true) const formData = new FormData(e.currentTarget) // Disabled inputs are excluded from FormData by the browser, // so only include company_name/org_number when not locked const updates: Record = { ...(formData.has('company_name') && { company_name: formData.get('company_name') as string }), ...(formData.has('org_number') && { org_number: formData.get('org_number') as string }), address_line1: formData.get('address_line1') as string, postal_code: formData.get('postal_code') as string, city: formData.get('city') as string, bank_name: formData.get('bank_name') as string, clearing_number: formData.get('clearing_number') as string, account_number: formData.get('account_number') as string, bankgiro: (formData.get('bankgiro') as string) || null, preliminary_tax_monthly: parseFloat(formData.get('preliminary_tax_monthly') as string) || null, invoice_prefix: formData.get('invoice_prefix') as string || null, next_invoice_number: parseInt(formData.get('next_invoice_number') as string) || 1, invoice_default_days: parseInt(formData.get('invoice_default_days') as string) || 30, accounting_method: formData.get('accounting_method') as string || 'accrual', invoice_default_notes: (formData.get('invoice_default_notes') as string) || null, } try { const response = await fetch('/api/settings', { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(updates), }) const result = await response.json() if (!response.ok) { throw new Error(result.error || 'Kunde inte spara inställningar') } toast({ title: 'Sparat', description: 'Dina inställningar har uppdaterats', }) setSettings({ ...settings, ...updates } as typeof settings) } catch (error) { toast({ title: 'Kunde inte spara inställningar', description: error instanceof Error ? error.message : 'Försök igen.', variant: 'destructive', }) } setIsSaving(false) } async function handleLogout() { await supabase.auth.signOut() router.push('/login') } async function handleDeleteAccount() { if (deleteConfirmText !== 'RADERA') return setIsDeleting(true) try { const response = await fetch('/api/account/delete', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ confirm: 'RADERA' }), }) if (!response.ok) { const result = await response.json() throw new Error(result.error || 'Kunde inte radera kontot') } router.push('/login') } catch (error) { toast({ title: 'Kunde inte radera kontot', description: error instanceof Error ? error.message : 'Försök igen.', variant: 'destructive', }) setIsDeleting(false) } } if (isLoading) { return (
) } return (

Inställningar

Hantera dina företags- och kontoinställningar

{/* Mobile: dropdown selector */}
{/* Desktop: tab pills */} {settingsTabs.map(t => ( {t.label} ))} {/* Company settings */}
{/* Företagsuppgifter */} Företagsuppgifter Namn, organisationsnummer och adress
{settings?.onboarding_complete && (

Kan inte ändras efter att kontot skapats

)}
{settings?.onboarding_complete && (

Kan inte ändras efter att kontot skapats

)}
{/* Bankuppgifter */} Bankuppgifter Betalningsuppgifter som visas på dina fakturor
{ const val = e.target.value.trim() if (!val) { setBankgiroError(null) return } if (validateBankgiroNumber(val)) { e.target.value = formatBankgiroNumber(val) setBankgiroError(null) } else { setBankgiroError('Ogiltigt bankgironummer (7-8 siffror med kontrollsiffra)') } }} /> {bankgiroError && (

{bankgiroError}

)}
{/* Fakturainställningar */} Fakturainställningar Numrering, betalningsvillkor och bokföringsmetod

{settings?.entity_type === 'aktiebolag' ? 'Aktiebolag med omsättning över 3 MSEK måste använda faktureringsmetoden enligt BFL. Mindre aktiebolag kan välja kontantmetoden.' : 'Kontantmetoden är tillgänglig för enskild firma med omsättning under 3 MSEK.'}