'use client'; import { useEffect, useRef, useState } from 'react'; import { useParams, useRouter } from 'next/navigation'; import { useMutation } from '@tanstack/react-query'; import { Camera, Loader2, ScanLine, Upload } from 'lucide-react'; import { useMobileChrome } from '@/components/layout/mobile/mobile-layout-provider'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; import { Label } from '@/components/ui/label'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from '@/components/ui/select'; import { apiFetch } from '@/lib/api/client'; import { EXPENSE_CATEGORIES } from '@/lib/constants'; interface ScanResult { establishment: string | null; date: string | null; amount: number | null; currency: string | null; lineItems: Array<{ description: string; amount: number }>; confidence: number; } export default function ScanReceiptPage() { const params = useParams<{ portSlug: string }>(); const router = useRouter(); const fileInputRef = useRef(null); const cameraInputRef = useRef(null); const [scanResult, setScanResult] = useState(null); const [previewUrl, setPreviewUrl] = useState(null); const { setChrome } = useMobileChrome(); useEffect(() => { setChrome({ title: 'Scan Receipt', showBackButton: true }); return () => setChrome({ title: null, showBackButton: false }); }, [setChrome]); // Editable fields from scan const [establishment, setEstablishment] = useState(''); const [amount, setAmount] = useState(''); const [currency, setCurrency] = useState('USD'); const [date, setDate] = useState(''); const [category, setCategory] = useState(''); const scanMutation = useMutation({ mutationFn: async (file: File) => { const formData = new FormData(); formData.append('file', file); const res = await fetch('/api/v1/expenses/scan-receipt', { method: 'POST', body: formData, credentials: 'include', }); if (!res.ok) throw new Error('Scan failed'); return res.json() as Promise<{ data: ScanResult }>; }, onSuccess: (response) => { const result = response.data; setScanResult(result); if (result.establishment) setEstablishment(result.establishment); if (result.amount) setAmount(String(result.amount)); if (result.currency) setCurrency(result.currency); if (result.date) setDate(result.date.split('T')[0] ?? result.date); }, }); const saveMutation = useMutation({ mutationFn: () => apiFetch('/api/v1/expenses', { method: 'POST', body: { establishmentName: establishment, amount: Number(amount), currency, category: category || undefined, expenseDate: date ? new Date(date) : new Date(), paymentStatus: 'unpaid', }, }), onSuccess: () => { router.push(`/${params.portSlug}/expenses`); }, }); function handleFileChange(e: React.ChangeEvent) { const file = e.target.files?.[0]; if (!file) return; const url = URL.createObjectURL(file); setPreviewUrl(url); scanMutation.mutate(file); } return (

Scan Receipt

Upload a receipt image and we will extract the expense details automatically.

Upload Receipt {previewUrl ? (
fileInputRef.current?.click()} > Receipt preview
) : (

JPEG, PNG, WebP up to 10MB

)} {scanMutation.isPending && (
Scanning receipt...
)}
{(scanResult || scanMutation.isSuccess) && ( Extracted Details {scanResult && ( (confidence: {Math.round((scanResult.confidence ?? 0) * 100)}%) )}
setAmount(e.target.value)} placeholder="0.00" />
setCurrency(e.target.value.toUpperCase())} maxLength={3} placeholder="USD" />
setEstablishment(e.target.value)} placeholder="Establishment name" />
setDate(e.target.value)} />
{saveMutation.isError && (

{(saveMutation.error as Error).message}

)}
)}
); }