Files
accounted/app/(public)/invoice-action/[token]/page.tsx
T
2026-02-13 14:00:46 +01:00

250 lines
8.2 KiB
TypeScript

'use client'
import { useState, useEffect, use } from 'react'
import { Button } from '@/components/ui/button'
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
import { formatCurrency, formatDate } from '@/lib/utils'
import {
Loader2,
CheckCircle,
AlertCircle,
FileText,
MessageSquare,
} from 'lucide-react'
interface InvoiceData {
invoiceNumber: string
invoiceDate: string
dueDate: string
total: number
currency: string
customerName: string
reminderLevel: number
alreadyResponded: boolean
previousResponse: 'marked_paid' | 'disputed' | null
}
export default function InvoiceActionPage({ params }: { params: Promise<{ token: string }> }) {
const { token } = use(params)
const [invoice, setInvoice] = useState<InvoiceData | null>(null)
const [isLoading, setIsLoading] = useState(true)
const [isSubmitting, setIsSubmitting] = useState(false)
const [error, setError] = useState<string | null>(null)
const [successMessage, setSuccessMessage] = useState<string | null>(null)
useEffect(() => {
fetchInvoiceData()
}, [token])
async function fetchInvoiceData() {
try {
const response = await fetch(`/api/invoices/reminders/action?token=${token}`)
const data = await response.json()
if (!response.ok) {
setError(data.error || 'Kunde inte hämta fakturainformation')
return
}
setInvoice(data)
} catch {
setError('Ett fel uppstod. Försök igen senare.')
} finally {
setIsLoading(false)
}
}
async function handleAction(action: 'marked_paid' | 'disputed') {
setIsSubmitting(true)
setError(null)
try {
const response = await fetch('/api/invoices/reminders/action', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ token, action })
})
const data = await response.json()
if (!response.ok) {
setError(data.error || 'Kunde inte spara ditt svar')
return
}
setSuccessMessage(data.message)
if (invoice) {
setInvoice({ ...invoice, alreadyResponded: true, previousResponse: action })
}
} catch {
setError('Ett fel uppstod. Försök igen senare.')
} finally {
setIsSubmitting(false)
}
}
if (isLoading) {
return (
<div className="min-h-screen bg-gradient-to-b from-slate-50 to-white flex items-center justify-center">
<div className="text-center">
<Loader2 className="h-8 w-8 animate-spin text-primary mx-auto mb-4" />
<p className="text-muted-foreground">Laddar...</p>
</div>
</div>
)
}
if (error && !invoice) {
return (
<div className="min-h-screen bg-gradient-to-b from-slate-50 to-white flex items-center justify-center p-4">
<Card className="max-w-md w-full">
<CardContent className="pt-6 text-center">
<AlertCircle className="h-12 w-12 text-destructive mx-auto mb-4" />
<h2 className="text-lg font-semibold mb-2">Ogiltig länk</h2>
<p className="text-muted-foreground">{error}</p>
</CardContent>
</Card>
</div>
)
}
if (!invoice) {
return null
}
// Already responded view
if (invoice.alreadyResponded || successMessage) {
return (
<div className="min-h-screen bg-gradient-to-b from-slate-50 to-white flex items-center justify-center p-4">
<Card className="max-w-md w-full">
<CardContent className="pt-6 text-center">
<CheckCircle className="h-12 w-12 text-green-500 mx-auto mb-4" />
<h2 className="text-lg font-semibold mb-2">Tack för ditt svar!</h2>
<p className="text-muted-foreground mb-4">
{successMessage || (
invoice.previousResponse === 'marked_paid'
? 'Vi har noterat att du har betalat fakturan.'
: 'Vi har noterat din invändning och kommer att kontakta dig.'
)}
</p>
<div className="bg-muted rounded-lg p-4 text-left">
<p className="text-sm text-muted-foreground">Faktura</p>
<p className="font-medium">{invoice.invoiceNumber}</p>
</div>
</CardContent>
</Card>
</div>
)
}
// Calculate days overdue
const dueDate = new Date(invoice.dueDate)
const now = new Date()
const daysOverdue = Math.floor((now.getTime() - dueDate.getTime()) / (1000 * 60 * 60 * 24))
return (
<div className="min-h-screen bg-gradient-to-b from-slate-50 to-white py-12 px-4">
<div className="max-w-lg mx-auto">
{/* Header */}
<div className="text-center mb-8">
<h1 className="text-2xl font-bold text-gray-900 mb-2">
Betalningspåminnelse
</h1>
<p className="text-muted-foreground">
Faktura {invoice.invoiceNumber}
</p>
</div>
{/* Invoice Summary Card */}
<Card className="mb-6">
<CardHeader>
<CardTitle className="flex items-center gap-2">
<FileText className="h-5 w-5" />
Fakturainformation
</CardTitle>
<CardDescription>
Till: {invoice.customerName}
</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
<div className="grid grid-cols-2 gap-4">
<div>
<p className="text-sm text-muted-foreground">Fakturadatum</p>
<p className="font-medium">{formatDate(invoice.invoiceDate)}</p>
</div>
<div>
<p className="text-sm text-muted-foreground">Förfallodatum</p>
<p className="font-medium text-red-600">{formatDate(invoice.dueDate)}</p>
</div>
</div>
<div className="bg-red-50 border border-red-100 rounded-lg p-4">
<p className="text-sm text-red-600 mb-1">
Förfallen med {daysOverdue} dagar
</p>
<p className="text-2xl font-bold text-red-700">
{formatCurrency(invoice.total, invoice.currency)}
</p>
</div>
{error && (
<div className="bg-destructive/10 text-destructive text-sm p-3 rounded-lg">
{error}
</div>
)}
</CardContent>
</Card>
{/* Action Card */}
<Card>
<CardHeader>
<CardTitle>Vad vill du göra?</CardTitle>
<CardDescription>
Välj ett alternativ nedan
</CardDescription>
</CardHeader>
<CardContent className="space-y-3">
<Button
className="w-full justify-start h-auto py-4 px-4"
variant="outline"
onClick={() => handleAction('marked_paid')}
disabled={isSubmitting}
>
<CheckCircle className="h-5 w-5 mr-3 text-green-600" />
<div className="text-left">
<p className="font-medium">Jag har betalat</p>
<p className="text-sm text-muted-foreground font-normal">
Betalningen är redan genomförd
</p>
</div>
{isSubmitting && <Loader2 className="h-4 w-4 ml-auto animate-spin" />}
</Button>
<Button
className="w-full justify-start h-auto py-4 px-4"
variant="outline"
onClick={() => handleAction('disputed')}
disabled={isSubmitting}
>
<MessageSquare className="h-5 w-5 mr-3 text-orange-600" />
<div className="text-left">
<p className="font-medium">Kontakta avsändaren</p>
<p className="text-sm text-muted-foreground font-normal">
Jag har frågor eller invändningar
</p>
</div>
{isSubmitting && <Loader2 className="h-4 w-4 ml-auto animate-spin" />}
</Button>
</CardContent>
</Card>
{/* Footer note */}
<p className="text-center text-sm text-muted-foreground mt-6">
Om du redan har betalat kan det ta några dagar innan betalningen registreras.
</p>
</div>
</div>
)
}