import { MaterialCommunityIcons } from "@expo/vector-icons"; import { router } from "expo-router"; import { useCallback, useEffect, useState } from "react"; import { ActivityIndicator, Alert, Image, ScrollView, Text, TextInput, TouchableOpacity, View, } from "react-native"; import { SafeAreaView } from "react-native-safe-area-context"; import { CustomButton } from "@/components/custom-button"; import { icons, images } from "@/constants"; import { SERVICES, type ServiceId } from "@/constants/services"; import { ApiError, fetchAPI } from "@/lib/fetch"; import { useT } from "@/lib/i18n"; import { useSession } from "@/lib/session"; import { useTheme } from "@/lib/theme"; import { useDriverLocation } from "@/lib/use-driver-location"; import { formatTime } from "@/lib/utils"; // Poll cadence for the driver dashboard (offers / active ride / earnings). const POLL_MS = 4000; type Profile = { id: number; first_name: string; last_name: string; profile_image_url: string | null; car_image_url: string | null; car_seats: number; rating: number; service: ServiceId; online: boolean; car_model: string | null; }; type Offer = { offer_id: number; offered_at: string; ride_id: number; origin_address: string; destination_address: string; ride_time: number; fare_price: number; payment_status: string; service: string; }; type ActiveRide = { ride_id: number; status: string; service: string; payment_status: string; origin_address: string; destination_address: string; ride_time: number; fare_price: number; rider_name: string | null; rider_phone: string | null; }; type Dashboard = { offers: Offer[]; active: ActiveRide | null; recent: { ride_id: number; fare_price: number; service: string }[]; earnings: number; }; const DriverHome = () => { const { signOut, user } = useSession(); const { isDark } = useTheme(); const t = useT(); const [loading, setLoading] = useState(true); const [profile, setProfile] = useState(null); const [online, setOnline] = useState(false); const [dashboard, setDashboard] = useState(null); const [busy, setBusy] = useState(false); const loadProfile = useCallback(async () => { try { const res = await fetchAPI("/(api)/driver/profile"); const p = res.data as Profile; setProfile(p); setOnline(p.online); } catch (err) { // 403 with code ONBOARD means no profile yet — show the onboarding form. if (err instanceof ApiError && err.status === 403) { setProfile(null); } else { console.log("[DRIVER_PROFILE_LOAD]: ", err); } } finally { setLoading(false); } }, []); useEffect(() => { void loadProfile(); }, [loadProfile]); // Keep the location heartbeat running only while the driver is online and // has completed onboarding. useDriverLocation(online && profile !== null); // Poll the dashboard while online. useCallback keeps the fetcher stable so the // interval effect doesn't re-subscribe on every render. const fetchDashboard = useCallback(async () => { try { const res = await fetchAPI("/(api)/driver/rides"); setDashboard(res.data as Dashboard); } catch (err) { console.log("[DRIVER_DASHBOARD_POLL]: ", err); } }, []); useEffect(() => { if (!online || !profile) return; void fetchDashboard(); const timer = setInterval(() => void fetchDashboard(), POLL_MS); return () => clearInterval(timer); }, [online, profile, fetchDashboard]); const toggleOnline = async () => { if (!profile) return; const next = !online; setBusy(true); try { await fetchAPI("/(api)/driver/profile", { method: "PATCH", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ online: next }), }); setOnline(next); setProfile({ ...profile, online: next }); if (!next) setDashboard(null); } catch (err) { console.log("[DRIVER_TOGGLE]: ", err); Alert.alert(t("driver.home.alertErrorTitle"), t("driver.home.alertToggleBody")); } finally { setBusy(false); } }; const respond = async (offer: Offer, action: "accept" | "decline") => { setBusy(true); try { await fetchAPI(`/(api)/ride/${offer.ride_id}/respond`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ action }), }); await fetchDashboard(); } catch (err) { console.log("[DRIVER_RESPOND]: ", err); Alert.alert( t("driver.activeRide.alertErrorTitle"), action === "accept" ? t("driver.activeRide.alertAcceptBody") : t("driver.activeRide.alertDeclineBody"), ); } finally { setBusy(false); } }; const advance = async (rideId: number, status: "en_route" | "completed") => { setBusy(true); try { await fetchAPI(`/(api)/ride/${rideId}`, { method: "PATCH", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ status }), }); await fetchDashboard(); } catch (err) { console.log("[DRIVER_ADVANCE]: ", err); Alert.alert(t("driver.activeRide.alertErrorTitle"), t("driver.activeRide.alertUpdateBody")); } finally { setBusy(false); } }; if (loading) { return ( ); } if (!profile) { return ( ); } const earnings = dashboard?.earnings ?? 0; const rideCount = dashboard?.recent.length ?? 0; return ( {t("driver.home.driverMode")} {t("driver.home.signOutAlt")} {/* Online / offline toggle */} {online ? t("driver.home.online") : t("driver.home.goOnline")} {/* Earnings summary */} {t("driver.home.todaysEarnings")} ${(earnings / 100).toFixed(2)} {t("driver.home.completedToday")} {rideCount} {/* Active ride */} {dashboard?.active ? ( ) : null} {/* Incoming offers */} {online ? t("driver.home.incomingRequests") : t("driver.home.incomingRequestsOffline")} {!online ? null : dashboard?.offers.length ? ( dashboard.offers.map((offer) => ( respond(offer, "accept")} onDecline={() => respond(offer, "decline")} /> )) ) : ( {online ? t("driver.home.waitingRequests") : t("driver.home.goOnlineStart")} )} ); }; // --- Onboarding form ------------------------------------------------------ const Onboarding = ({ onCreated, signOut, userName, }: { onCreated: () => Promise; signOut: () => Promise; userName?: string | null; }) => { const t = useT(); const { isDark } = useTheme(); const [service, setService] = useState("car"); const [carModel, setCarModel] = useState(""); const [carSeats, setCarSeats] = useState("4"); const [submitting, setSubmitting] = useState(false); const submit = async () => { const seats = Number(carSeats); if (!Number.isInteger(seats) || seats < 1 || seats > 8) { Alert.alert(t("driver.home.alertSeatsTitle"), t("driver.home.alertSeatsBody")); return; } setSubmitting(true); try { await fetchAPI("/(api)/driver/profile", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ service, car_model: carModel.trim() || null, car_seats: seats, }), }); await onCreated(); } catch (err) { console.log("[DRIVER_ONBOARD]: ", err); Alert.alert(t("driver.home.alertErrorTitle"), t("driver.home.alertCreateBody")); } finally { setSubmitting(false); } }; return ( {t("driver.home.welcome", { name: userName?.split(" ")[0] || t("driver.home.welcomeFallback"), })} {t("driver.home.signOutAlt")} {t("driver.home.setupIntro")} {t("driver.home.whatDrive")} {SERVICES.map((item) => { const active = item.id === service; return ( setService(item.id)} className={`flex-1 items-center rounded-2xl border py-3 ${ active ? "border-primary-500 bg-primary-500/10" : "border-neutral-100 dark:border-neutral-800 bg-neutral-100 dark:bg-neutral-900" }`} > {t(item.labelKey)} ); })} {t("driver.home.carModel")} {t("driver.home.carSeats")} ); }; // --- Offer card ----------------------------------------------------------- const OfferCard = ({ offer, busy, onAccept, onDecline, }: { offer: Offer; busy: boolean; onAccept: () => void; onDecline: () => void; }) => { const t = useT(); return ( {t("driver.offerCard.newRequest", { service: offer.service })} {offer.payment_status === "cash" ? t("driver.offerCard.cash") : t("driver.offerCard.card")} {t("driver.offerCard.fromAlt")} {offer.origin_address} {t("driver.offerCard.toAlt")} {offer.destination_address} {t("driver.offerCard.tripTime")} {formatTime(offer.ride_time)} {t("driver.offerCard.fare")} ${(offer.fare_price / 100).toFixed(2)} {t("driver.offerCard.decline")} {busy ? "…" : t("driver.offerCard.accept")} ); }; // --- Active ride card ----------------------------------------------------- const ActiveRideCard = ({ ride, busy, onAdvance, }: { ride: ActiveRide; busy: boolean; onAdvance: (rideId: number, status: "en_route" | "completed") => void; }) => { const t = useT(); const statusLabel = ride.status === "accepted" ? t("driver.activeRide.headToPickup") : ride.status === "en_route" ? t("driver.activeRide.tripInProgress") : ride.status; return ( ● {statusLabel} {ride.service} {ride.rider_name ? ( {t("driver.activeRide.rider", { name: ride.rider_name })} ) : null} {t("driver.activeRide.fromAlt")} {ride.origin_address} {t("driver.activeRide.toAlt")} {ride.destination_address} {t("driver.activeRide.fare")} ${(ride.fare_price / 100).toFixed(2)} {ride.status === "accepted" ? ( onAdvance(ride.ride_id, "en_route")} className="mb-2" /> ) : null} {ride.status === "en_route" ? ( onAdvance(ride.ride_id, "completed")} /> ) : null} ); }; export default DriverHome;