"use client"; import { useState, useEffect, useRef, useCallback } from "react"; import { ArrowLeft, Loader2, Camera, Sparkles, Image as ImageIcon, RotateCcw } from "lucide-react"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Card, CardContent } from "@/components/ui/card"; import { createExpense, fetchExpenseCategories, uploadReceipt, extractReceiptData } from "@/lib/supabase/expenses-actions"; import type { ExpenseCategory } from "@/lib/supabase/expenses-types"; export default function ExpensesNewPage({ onBack, onSuccess, }: { onBack: () => void; onSuccess: () => void; }) { const [categories, setCategories] = useState([]); const [saving, setSaving] = useState(false); const [error, setError] = useState(null); // Form state const [amount, setAmount] = useState(""); const [currency, setCurrency] = useState("AUD"); const [categoryId, setCategoryId] = useState(""); const [vendor, setVendor] = useState(""); const [description, setDescription] = useState(""); const [expenseDate, setExpenseDate] = useState(new Date().toISOString().split("T")[0]); const [receiptUrl, setReceiptUrl] = useState(""); // Receipt capture state const [selectedFile, setSelectedFile] = useState(null); const [previewUrl, setPreviewUrl] = useState(null); const [uploading, setUploading] = useState(false); const [extracting, setExtracting] = useState(false); const [extracted, setExtracted] = useState(false); const fileInputRef = useRef(null); useEffect(() => { fetchExpenseCategories().then(setCategories).catch(() => {}); }, []); // Cleanup preview URL on unmount useEffect(() => { return () => { if (previewUrl && previewUrl.startsWith("blob:")) URL.revokeObjectURL(previewUrl); }; }, [previewUrl]); const handleFileSelect = useCallback(async (file: File) => { setSelectedFile(file); setExtracted(false); // Create preview if (previewUrl && previewUrl.startsWith("blob:")) URL.revokeObjectURL(previewUrl); setPreviewUrl(URL.createObjectURL(file)); }, [previewUrl]); const handleInputChange = (e: React.ChangeEvent) => { const file = e.target.files?.[0]; if (file) handleFileSelect(file); }; const handleUploadReceipt = async () => { if (!selectedFile) return; setUploading(true); setError(null); try { const result = await uploadReceipt(selectedFile); if (!result.success) { setError(result.error || "Failed to upload receipt"); return; } setReceiptUrl(result.url || ""); } catch (e: any) { setError(e.message || "An unexpected error occurred"); } finally { setUploading(false); } }; const handleRetake = () => { setSelectedFile(null); if (previewUrl && previewUrl.startsWith("blob:")) URL.revokeObjectURL(previewUrl); setPreviewUrl(null); setReceiptUrl(""); setExtracted(false); if (fileInputRef.current) fileInputRef.current.value = ""; }; const handleExtractReceipt = async () => { if (!previewUrl) return; setExtracting(true); setError(null); try { // Convert blob URL or data URL to base64 let base64: string; if (previewUrl.startsWith("blob:")) { const response = await fetch(previewUrl); const blob = await response.blob(); base64 = await new Promise((resolve) => { const reader = new FileReader(); reader.onloadend = () => resolve((reader.result as string).split(",")[1]); reader.readAsDataURL(blob); }); } else { base64 = previewUrl.split(",")[1]; } const result = await extractReceiptData(base64); if (!result.success) { setError(result.error || "Failed to extract receipt data"); return; } if (result.data) { if (result.data.merchant) setVendor(result.data.merchant); if (result.data.amount) setAmount(result.data.amount); if (result.data.currency) setCurrency(result.data.currency.toUpperCase()); if (result.data.date) { // Try to parse the date into YYYY-MM-DD format const d = new Date(result.data.date); if (!isNaN(d.getTime())) { setExpenseDate(d.toISOString().split("T")[0]); } } setExtracted(true); } } catch (e: any) { setError(e.message || "An unexpected error occurred"); } finally { setExtracting(false); } }; const handleSubmit = async () => { setError(null); const amt = parseFloat(amount); if (isNaN(amt) || amt <= 0) { setError("Enter a valid amount"); return; } if (!expenseDate) { setError("Select a date"); return; } setSaving(true); try { const result = await createExpense({ amount: amt, currency, category_id: categoryId || null, employee_id: null, vendor: vendor || null, description: description || null, expense_date: expenseDate, status: "draft", receipt_url: receiptUrl || null, }); if (!result.success) { setError(result.error || "Failed to create expense"); return; } onSuccess(); } catch (e: any) { setError(e.message || "An unexpected error occurred"); } finally { setSaving(false); } }; return (
{/* Header */}

Add Expense

Record a business expense

{error && (
{error}
)} {/* ── Receipt Capture Section ── */}
{!selectedFile ? (
fileInputRef.current?.click()} className="mt-1 border-2 border-dashed border-gray-200 rounded-xl p-8 flex flex-col items-center justify-center cursor-pointer hover:border-[#1D9E75] hover:bg-[#E1F5EE]/30 transition-colors" >

Tap to photograph receipt

or choose from your files

) : (
Receipt preview
{!receiptUrl && ( )}
{receiptUrl && (

Receipt uploaded successfully

)}
{extracted && (

Review and confirm the pre-filled values below.

)}
)}
{/* ── Form Fields ── */}
$ setAmount(e.target.value)} className="w-full border border-gray-200 rounded-lg pl-7 pr-3 py-2.5 text-sm focus:outline-none focus:ring-2 focus:ring-[#1D9E75]/20 focus:border-[#1D9E75]" placeholder="0.00" />
setExpenseDate(e.target.value)} className="mt-1 w-full border border-gray-200 rounded-lg px-3 py-2.5 text-sm focus:outline-none focus:ring-2 focus:ring-[#1D9E75]/20 focus:border-[#1D9E75]" />
setVendor(e.target.value)} className="mt-1 w-full border border-gray-200 rounded-lg px-3 py-2.5 text-sm focus:outline-none focus:ring-2 focus:ring-[#1D9E75]/20 focus:border-[#1D9E75]" placeholder="e.g. Amazon, Uber, Starbucks" />