Files
accounted/components/settings/SettingsFormWrapper.tsx
T
Jakob Wennberg d0b3f21bde feat: remove AI extensions, restructure settings, and add atomic voucher commits (#157)
Remove AI-dependent extensions (ai-chat, ai-categorization, receipt-ocr,
invoice-inbox) and their infrastructure (lib/ai/*, ai-consent, LangChain/
Anthropic/OpenAI deps) to simplify core and reduce bundle size.

Restructure monolithic settings page into dedicated sub-pages (company,
bookkeeping, invoicing, tax, banking, api, account, team, templates) with
shared layout and sidebar navigation.

Add atomic commit_journal_entry RPC so voucher number increment and status
update happen in a single transaction — prevents burned numbers on constraint
failures. Add continuity check report and voucher gap explanation tracking.

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-01 17:08:00 +02:00

89 lines
2.4 KiB
TypeScript

'use client'
import { useState, useRef, useEffect, useCallback } from 'react'
import { Button } from '@/components/ui/button'
import { Loader2, Check } from 'lucide-react'
import { useToast } from '@/components/ui/use-toast'
interface SettingsFormWrapperProps {
children: React.ReactNode
onSave?: (formData: FormData) => Record<string, unknown>
className?: string
}
export function SettingsFormWrapper({ children, onSave, className }: SettingsFormWrapperProps) {
const { toast } = useToast()
const [isSaving, setIsSaving] = useState(false)
const [saved, setSaved] = useState(false)
const timerRef = useRef<ReturnType<typeof setTimeout>>(undefined)
useEffect(() => {
return () => {
if (timerRef.current) clearTimeout(timerRef.current)
}
}, [])
const handleSubmit = useCallback(async (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault()
if (!onSave) return
const formData = new FormData(e.currentTarget)
const updates = onSave(formData)
if (!updates || Object.keys(updates).length === 0) return
setIsSaving(true)
setSaved(false)
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')
}
setSaved(true)
timerRef.current = setTimeout(() => setSaved(false), 2000)
} catch (error) {
toast({
title: 'Kunde inte spara',
description: error instanceof Error ? error.message : 'Försök igen.',
variant: 'destructive',
})
}
setIsSaving(false)
}, [onSave, toast])
return (
<form onSubmit={handleSubmit} className={className}>
{children}
<div className="flex items-center justify-end gap-3 mt-8">
{saved && (
<span className="flex items-center gap-1.5 text-sm text-muted-foreground animate-in fade-in duration-200">
<Check className="h-3.5 w-3.5" />
Sparat
</span>
)}
<Button type="submit" disabled={isSaving} size="sm">
{isSaving ? (
<>
<Loader2 className="mr-2 h-3.5 w-3.5 animate-spin" />
Sparar...
</>
) : (
'Spara ändringar'
)}
</Button>
</div>
</form>
)
}