'use client'; import { useRef, useState, useEffect } from 'react'; import Image from 'next/image'; import { useRouter } from 'next/navigation'; import { Camera, Loader2, RotateCcw, AlertTriangle, CheckCircle2, Save } from 'lucide-react'; const LOGO_URL = 'https://s3.portnimara.com/images/Port%20Nimara%20New%20Logo-Circular%20Frame_250px.png'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; import { Label } from '@/components/ui/label'; import { Textarea } from '@/components/ui/textarea'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from '@/components/ui/select'; import { useUIStore } from '@/stores/ui-store'; import { apiFetch } from '@/lib/api/client'; import { cn } from '@/lib/utils'; import { EXPENSE_CATEGORIES, PAYMENT_METHODS } from '@/lib/constants'; import { runTesseract } from '@/lib/ocr/tesseract-client'; // ─── Types ──────────────────────────────────────────────────────────────────── interface ParsedReceipt { establishment: string | null; date: string | null; amount: number | null; currency: string | null; lineItems: Array<{ description: string; amount: number }>; confidence: number; } type ScanState = | { kind: 'idle' } | { kind: 'processing'; engine: 'tesseract' | 'ai' } | { kind: 'verify'; parsed: ParsedReceipt; source: 'ai' | 'tesseract' | 'manual'; reason?: string; providerError?: string; } | { kind: 'saving' } | { kind: 'saved'; expenseId: string } | { kind: 'error'; message: string }; interface ScanResp { data: { parsed: ParsedReceipt; source: 'ai' | 'manual'; reason?: string; provider?: string; model?: string; providerError?: string; }; } // ─── Form ───────────────────────────────────────────────────────────────────── interface VerifyFormProps { parsed: ParsedReceipt; imagePreview: string; imageFile: File; source: 'ai' | 'tesseract' | 'manual'; reason?: string; providerError?: string; onSubmit: (input: { establishmentName: string; amount: string; currency: string; expenseDate: string; category: string; paymentMethod: string; description: string; file: File; }) => void; onRetake: () => void; saving: boolean; } const TODAY = () => new Date().toISOString().slice(0, 10); function VerifyForm({ parsed, imagePreview, imageFile, source, reason: _reason, providerError, onSubmit, onRetake, saving, }: VerifyFormProps) { const [establishmentName, setEstablishment] = useState(parsed.establishment ?? ''); const [amount, setAmount] = useState(parsed.amount != null ? String(parsed.amount) : ''); const [currency, setCurrency] = useState((parsed.currency ?? 'USD').toUpperCase()); const [expenseDate, setExpenseDate] = useState(parsed.date ?? TODAY()); const [category, setCategory] = useState('other'); const [paymentMethod, setPaymentMethod] = useState('credit_card'); const [description, setDescription] = useState(''); const lowConfidence = source !== 'manual' && parsed.confidence < 0.6; const noOcr = source === 'manual'; const engineLabel = source === 'ai' ? 'AI' : source === 'tesseract' ? 'on-device OCR' : 'manual'; const banner = noOcr ? (

Manual entry mode

{providerError ? `We couldn't read the receipt automatically: ${providerError}.` : "We couldn't read the receipt automatically."}{' '} Fill in the details below to save the expense with the photo attached.

) : lowConfidence ? (

Low-confidence read - please double-check the fields

{engineLabel} returned {Math.round(parsed.confidence * 100)}% confidence.

) : (

Receipt parsed - confirm the fields and save

{engineLabel} · {Math.round(parsed.confidence * 100)}% confidence.

); return (
{ e.preventDefault(); onSubmit({ establishmentName, amount, currency, expenseDate, category, paymentMethod, description, file: imageFile, }); }} > {banner}
{/* eslint-disable-next-line @next/next/no-img-element */} Receipt preview
setEstablishment(e.target.value)} placeholder="e.g. Marina Fuel Station" />
setAmount(e.target.value)} required />
setCurrency(e.target.value.toUpperCase())} maxLength={3} required />
setExpenseDate(e.target.value)} required />