import React, { useState, useEffect, useRef } from "react"; import { View, Pressable, Platform, ActivityIndicator, Alert, } from "react-native"; import { Text } from "@/components/ui/text"; import { Button } from "@/components/ui/button"; import { X, Zap, Camera as CameraIcon, ScanLine } from "@/lib/icons"; import { ScreenWrapper } from "@/components/ScreenWrapper"; import { CameraView, useCameraPermissions } from "expo-camera"; import { useSirouRouter } from "@sirou/react-native"; import { AppRoutes } from "@/lib/routes"; import { useNavigation } from "expo-router"; import { BASE_URL } from "@/lib/api"; import { useAuthStore } from "@/lib/auth-store"; import { toast } from "@/lib/toast-store"; const NAV_BG = "#ffffff"; export default function ScanScreen() { const nav = useSirouRouter(); const [permission, requestPermission] = useCameraPermissions(); const [torch, setTorch] = useState(false); const [scanning, setScanning] = useState(false); const cameraRef = useRef(null); const navigation = useNavigation(); const token = useAuthStore((s) => s.token); useEffect(() => { navigation.setOptions({ tabBarStyle: { display: "none" } }); return () => { navigation.setOptions({ tabBarStyle: { display: "flex", backgroundColor: NAV_BG, borderTopWidth: 0, elevation: 10, height: 75, paddingBottom: Platform.OS === "ios" ? 30 : 10, paddingTop: 10, marginHorizontal: 20, position: "absolute", bottom: 25, left: 20, right: 20, borderRadius: 32, shadowColor: "#000", shadowOffset: { width: 0, height: 10 }, shadowOpacity: 0.12, shadowRadius: 20, }, }); }; }, [navigation]); const handleScan = async () => { if (!cameraRef.current || scanning) return; setScanning(true); try { // 1. Capture the photo const photo = await cameraRef.current.takePictureAsync({ quality: 0.85, base64: false, }); if (!photo?.uri) throw new Error("Failed to capture photo."); toast.info("Scanning...", "Uploading invoice image for AI extraction."); // 2. Build multipart form data with the image file const formData = new FormData(); formData.append("file", { uri: photo.uri, name: "invoice.jpg", type: "image/jpeg", } as any); // 3. POST to /api/v1/scan/invoice const response = await fetch(`${BASE_URL}scan/invoice`, { method: "POST", headers: { Authorization: `Bearer ${token}`, // Do NOT set Content-Type here — fetch sets it automatically with the boundary for multipart }, body: formData, }); if (!response.ok) { const err = await response.json(); throw new Error(err.message || "Scan failed."); } const data = await response.json(); console.log("[Scan] Extracted invoice data:", data); toast.success("Scan Complete!", "Invoice data extracted successfully."); // Navigate to create invoice screen nav.go("proforma/create"); } catch (err: any) { console.error("[Scan] Error:", err); toast.error( "Scan Failed", err.message || "Could not process the invoice.", ); } finally { setScanning(false); } }; if (!permission) { return ; } if (!permission.granted) { return ( Camera Access We need your permission to use the camera to scan invoices and receipts automatically. nav.back()} className="mt-6"> Go Back ); } return ( {/* Top bar */} setTorch(!torch)} className={`h-12 w-12 rounded-full items-center justify-center border border-white/20 ${torch ? "bg-primary" : "bg-black/40"}`} > nav.back()} className="h-12 w-12 rounded-full bg-black/40 items-center justify-center border border-white/20" > {/* Scan Frame */} Align Invoice Within Frame {/* Capture Button */} {scanning ? ( ) : ( )} {scanning ? "Extracting Data..." : "Tap to Scan"} ); }