'use client' import { useState, useCallback, useEffect } from 'react' import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, } from '@/components/ui/dialog' import { Badge } from '@/components/ui/badge' import { Button } from '@/components/ui/button' import { Input } from '@/components/ui/input' import { Skeleton } from '@/components/ui/skeleton' import { useToast } from '@/components/ui/use-toast' import { AlertTriangle, Check, Copy, Loader2, RefreshCw, Trash2 } from 'lucide-react' import { formatDateLong } from '@/lib/utils' import type { CompanyInboundDomain, InboundDomainDnsRecord } from '@/types' const BASE = '/api/extensions/ext/invoice-inbox/inbox/domain' const STATUS_BADGE: Record< CompanyInboundDomain['status'], { label: string; variant: 'secondary' | 'success' | 'destructive' } > = { pending: { label: 'Väntar på DNS', variant: 'secondary' }, verified: { label: 'Verifierad', variant: 'success' }, failed: { label: 'Misslyckades', variant: 'destructive' }, } interface Props { open: boolean onOpenChange: (open: boolean) => void } // Settings dialog for a company's own inbound domain. Claims the domain via // the extension API, renders the DNS records the user must publish, and // re-checks verification on demand. Everything mail-routing happens // server-side: this surface only manages the claim lifecycle. export default function InboxCustomDomainDialog({ open, onOpenChange }: Props) { const { toast } = useToast() const [isLoading, setIsLoading] = useState(true) const [domain, setDomain] = useState(null) const [domainInput, setDomainInput] = useState('') const [isClaiming, setIsClaiming] = useState(false) const [isChecking, setIsChecking] = useState(false) const [isRemoving, setIsRemoving] = useState(false) const fetchDomain = useCallback(async () => { setIsLoading(true) try { const res = await fetch(BASE) const json = await res.json() if (res.ok) setDomain(json.data ?? null) } catch { // Leave the previous state; the dialog shows the claim form on null. } finally { setIsLoading(false) } }, []) useEffect(() => { if (open) fetchDomain() }, [open, fetchDomain]) const handleClaim = useCallback(async () => { if (!domainInput.trim()) return setIsClaiming(true) try { const res = await fetch(BASE, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ domain: domainInput }), }) const json = await res.json() if (!res.ok) throw new Error(json.error ?? 'Kunde inte lägga till domänen') setDomain(json.data) setDomainInput('') toast({ title: 'Domän tillagd', description: 'Lägg till DNS-posterna nedan hos din domänleverantör.', }) } catch (err) { toast({ title: 'Kunde inte lägga till domänen', description: err instanceof Error ? err.message : 'Försök igen.', variant: 'destructive', }) } finally { setIsClaiming(false) } }, [domainInput, toast]) const handleVerify = useCallback(async () => { setIsChecking(true) try { const res = await fetch(`${BASE}/verify`, { method: 'POST' }) const json = await res.json() if (!res.ok) throw new Error(json.error ?? 'Kontrollen misslyckades') setDomain(json.data) toast( json.data.status === 'verified' ? { title: 'Domänen är verifierad', description: 'E-post till domänen landar nu i dokumentinkorgen.' } : { title: 'Inte verifierad än', description: 'DNS-ändringar kan ta upp till någon timme att slå igenom.' } ) } catch (err) { toast({ title: 'Kontrollen misslyckades', description: err instanceof Error ? err.message : 'Försök igen.', variant: 'destructive', }) } finally { setIsChecking(false) } }, [toast]) const handleRemove = useCallback(async () => { if (!domain) return if (!confirm(`Ta bort ${domain.domain}? E-post till domänen slutar landa i Accounted.`)) return setIsRemoving(true) try { const res = await fetch(BASE, { method: 'DELETE' }) const json = await res.json() if (!res.ok) throw new Error(json.error ?? 'Borttagningen misslyckades') setDomain(null) toast({ title: 'Domänen borttagen' }) } catch (err) { toast({ title: 'Borttagningen misslyckades', description: err instanceof Error ? err.message : 'Försök igen.', variant: 'destructive', }) } finally { setIsRemoving(false) } }, [domain, toast]) const handleCopy = useCallback( (value: string) => { navigator.clipboard.writeText(value).catch(() => {}) toast({ title: 'Kopierat' }) }, [toast] ) const records: InboundDomainDnsRecord[] = domain?.dns_records ?? [] return ( Egen domän för inkorgen Ta emot leverantörsfakturor direkt på bolagets egen adress, t.ex. faktura@dittbolag.se: utan vidarebefordran. {isLoading ? (
) : !domain ? (

Viktigt om din domän redan tar emot e-post

Domänens MX-poster pekas om till Accounted. Om domänen redan används för e-post (Google Workspace, Microsoft 365) slutar din vanliga e-post att fungera: använd då en underdomän, t.ex.{' '} faktura.dittbolag.se, eller fortsätt vidarebefordra till din vanliga inkorgsadress.

setDomainInput(e.target.value)} placeholder="faktura.dittbolag.se" onKeyDown={(e) => { if (e.key === 'Enter') handleClaim() }} />
) : (
{domain.domain} {STATUS_BADGE[domain.status].label}
{domain.status === 'verified' ? (

Klart: ge dina leverantörer{' '} faktura@{domain.domain}

Alla adresser på domänen fungerar; allt landar i dokumentinkorgen. {domain.verified_at ? ` Verifierad ${formatDateLong(domain.verified_at)}.` : ''}

) : (

Lägg till posterna nedan hos din domänleverantör (Loopia, one.com, Cloudflare …) och klicka sedan på Kontrollera igen. Ändringar kan ta upp till någon timme att slå igenom.

{records.length > 0 ? (
{records.map((r, i) => ( ))}
Typ Namn Värde Prio
{r.type} {r.name} {r.value} {r.priority ?? '-'}
) : (

Inga DNS-poster tillgängliga: klicka på Kontrollera igen.

)}
)}
)}
) }