import { router, useLocalSearchParams } from "expo-router"; import { useEffect, useState } from "react"; import { ActivityIndicator, Alert, Text, TouchableOpacity, View } from "react-native"; import { CustomButton } from "@/components/custom-button"; import { RideLayout } from "@/components/ride-layout"; import { SERVICES } from "@/constants/services"; import { ApiError, fetchAPI } from "@/lib/fetch"; import { calculateTripFare } from "@/lib/map"; import { formatLBP } from "@/lib/pricing"; import { requestRide } from "@/lib/request-ride"; import { useSession } from "@/lib/session"; import { formatTime, haversine } from "@/lib/utils"; import { useLocationStore, useServiceStore } from "@/store"; type PaymentMethod = "cash" | "card"; type NearbyDriver = { id: number; first_name: string; latitude: number; longitude: number; }; // Confirm-ride is now the request screen: the rider no longer browses and // picks a driver. They see a single fare estimate + nearest-driver ETA, pick a // payment method, and tap Request — auto-match assigns the driver and they're // routed to the live status screen. const ConfirmRide = () => { const params = useLocalSearchParams<{ service?: string }>(); const { userAddress, userLatitude, userLongitude, destinationAddress, destinationLatitude, destinationLongitude, } = useLocationStore(); const { service: storeService, setService } = useServiceStore(); const { user } = useSession(); const service = params.service ?? storeService; const selected = SERVICES.find((s) => s.id === service) ?? SERVICES[0]; const [method, setMethod] = useState("cash"); const [estimate, setEstimate] = useState<{ fare: string; durationSeconds: number; } | null>(null); const [nearestEta, setNearestEta] = useState(null); const [driversOnline, setDriversOnline] = useState(null); const [estimating, setEstimating] = useState(true); const [processing, setProcessing] = useState(false); // Trip fare estimate — one Directions call for the trip leg, recomputed when // the route or service changes. Independent of driver availability. useEffect(() => { if ( !userLatitude || !userLongitude || !destinationLatitude || !destinationLongitude ) return; let cancelled = false; setEstimating(true); const run = async () => { const trip = await calculateTripFare({ userLatitude, userLongitude, destinationLatitude, destinationLongitude, service: selected.id, }); if (cancelled) return; setEstimate( trip ? { fare: trip.fare, durationSeconds: trip.durationSeconds } : null, ); }; void run().finally(() => { if (!cancelled) setEstimating(false); }); return () => { cancelled = true; }; }, [ userLatitude, userLongitude, destinationLatitude, destinationLongitude, selected.id, ]); // Online-driver availability for the selected service, polled so the "no // drivers" state self-heals the moment a driver of this service comes // online. The nearest driver's pickup ETA is resolved alongside the count. useEffect(() => { if (!userLatitude || !userLongitude) return; let cancelled = false; const check = async () => { try { const res = await fetchAPI( `/(api)/driver/nearby?service=${selected.id}&lat=${userLatitude}&lng=${userLongitude}`, ); const drivers = (res.data ?? []) as NearbyDriver[]; if (cancelled) return; setDriversOnline(drivers.length); if (drivers.length === 0) { setNearestEta(null); return; } const nearest = drivers .map((d) => ({ d, dist: haversine( userLatitude, userLongitude, d.latitude, d.longitude, ), })) .sort((a, b) => a.dist - b.dist)[0].d; const directionsRes = await fetch( `https://maps.googleapis.com/maps/api/directions/json?origin=${nearest.latitude},${nearest.longitude}&destination=${userLatitude},${userLongitude}&key=${process.env.EXPO_PUBLIC_GOOGLE_API_KEY}`, ); const data = await directionsRes.json(); const leg = data.routes?.[0]?.legs?.[0]; if (!cancelled) setNearestEta(leg ? Math.round(leg.duration.value / 60) : null); } catch { if (!cancelled) { setDriversOnline(null); setNearestEta(null); } } }; void check(); const timer = setInterval(() => void check(), 10000); return () => { cancelled = true; clearInterval(timer); }; }, [userLatitude, userLongitude, selected.id]); const request = async () => { if (!userLatitude || !userLongitude || !destinationLatitude || !destinationLongitude) { Alert.alert("Missing route", "Please set a pickup and destination first."); return; } if (!estimate) { Alert.alert("No estimate", "We couldn't estimate this fare. Please try again."); return; } // Nested so the guards above narrow userLatitude/estimate to non-null for // the card-confirm callback as well as the direct cash path. const doRequest = async () => { setProcessing(true); try { // Keep the store in sync with whatever service we resolved for this ride. setService(selected.id); const { ride } = await requestRide({ method, service: selected.id, user: { name: user?.name ?? "", email: user?.email ?? "" }, origin: { address: userAddress ?? "", latitude: userLatitude, longitude: userLongitude, }, destination: { address: destinationAddress ?? "", latitude: destinationLatitude, longitude: destinationLongitude, }, rideTimeSeconds: estimate.durationSeconds, fareCents: Math.round(parseFloat(estimate.fare) * 100), }); router.replace(`/(root)/book-ride?id=${ride.ride_id}`); } catch (err) { console.log("[REQUEST_RIDE]: ", err); const msg = err instanceof ApiError ? err.message : "Something went wrong while booking your ride. Please try again."; Alert.alert("Error", msg); } finally { setProcessing(false); } }; if (method === "card") { Alert.alert( "Pay by card", `Your card will be charged $${estimate.fare}.`, [ { text: "Cancel", style: "cancel" }, { text: "Continue", onPress: () => void doRequest() }, ], ); } else { void doRequest(); } }; return ( Your trip Pickup {userAddress} Destination {destinationAddress} {selected.label} · {selected.tagline} Trip time {estimate ? formatTime(estimate.durationSeconds / 60) : "…"} {estimating ? "…" : estimate ? `$${estimate.fare}` : "—"} {estimate ? ( ≈ {formatLBP(parseFloat(estimate.fare))} ) : null} {driversOnline === 0 ? `No ${selected.label} drivers online right now` : nearestEta == null ? "Finding drivers nearby…" : `Nearest driver ≈ ${nearestEta} min away`} Payment Method setMethod("cash")} className={`flex-1 items-center py-3 rounded-xl border ${ method === "cash" ? "bg-general-600 border-primary-500" : "bg-white border-general-700" }`} > 💵 Cash setMethod("card")} className={`flex-1 items-center py-3 rounded-xl border ${ method === "card" ? "bg-general-600 border-primary-500" : "bg-white border-general-700" }`} > 💳 Card ); }; export default ConfirmRide;