Waseel: driver app, dispatch, POI suggestions, map fixes

This commit is contained in:
Krikorios
2026-08-25 02:57:49 +03:00
parent 899ca93cd5
commit 1d84003e0a
50 changed files with 3625 additions and 625 deletions
+45 -43
View File
@@ -14,18 +14,19 @@ import { CustomButton } from "@/components/custom-button";
import { Map } from "@/components/map";
import { icons, images } from "@/constants";
import { ApiError, fetchAPI } from "@/lib/fetch";
import { useT } from "@/lib/i18n";
import { formatTime } from "@/lib/utils";
import { useLocationStore } from "@/store";
import type { Ride } from "@/types/type";
const POLL_MS = 3000;
const statusLabel: Record<string, string> = {
requested: "Finding your driver…",
accepted: "Driver assigned — heading to you",
en_route: "On your trip",
completed: "You've arrived!",
cancelled: "Ride cancelled",
const STATUS_KEY: Record<string, string> = {
requested: "bookRide.status.requested",
accepted: "bookRide.status.accepted",
en_route: "bookRide.status.enRoute",
completed: "bookRide.status.completed",
cancelled: "bookRide.status.cancelled",
};
// book-ride is now the live ride-status screen. The rider lands here after
@@ -33,6 +34,7 @@ const statusLabel: Record<string, string> = {
const BookRide = () => {
const { id } = useLocalSearchParams<{ id: string }>();
const rideId = Number(id);
const t = useT();
const setUserLocation = useLocationStore((s) => s.setUserLocation);
const setDestinationLocation = useLocationStore((s) => s.setDestinationLocation);
@@ -62,12 +64,12 @@ const BookRide = () => {
} catch (err) {
console.log("[BOOK_RIDE_LOAD]: ", err);
if (err instanceof ApiError && err.status === 404) {
setError("Ride not found.");
setError(t("bookRide.rideNotFound"));
}
} finally {
setLoading(false);
}
}, [rideId, setUserLocation, setDestinationLocation]);
}, [rideId, setUserLocation, setDestinationLocation, t]);
useEffect(() => {
void load();
@@ -92,7 +94,7 @@ const BookRide = () => {
await load();
} catch (err) {
console.log("[BOOK_RIDE_CANCEL]: ", err);
Alert.alert("Error", "Could not cancel this ride. Please try again.");
Alert.alert(t("bookRide.alertErrorTitle"), t("bookRide.alertErrorBody"));
} finally {
setCancelling(false);
}
@@ -100,7 +102,7 @@ const BookRide = () => {
if (loading) {
return (
<SafeAreaView className="flex-1 bg-white items-center justify-center">
<SafeAreaView className="flex-1 bg-white dark:bg-neutral-950 items-center justify-center">
<ActivityIndicator size="large" color="#0286ff" />
</SafeAreaView>
);
@@ -108,12 +110,12 @@ const BookRide = () => {
if (error || !ride) {
return (
<SafeAreaView className="flex-1 bg-white items-center justify-center px-7">
<Text className="text-base text-general-200 text-center">
{error ?? "Could not load this ride."}
<SafeAreaView className="flex-1 bg-white dark:bg-neutral-950 items-center justify-center px-7">
<Text className="text-base text-general-200 dark:text-neutral-400 text-center">
{error ?? t("bookRide.couldNotLoad")}
</Text>
<CustomButton
title="Back Home"
title={t("bookRide.backHome")}
onPress={() => router.replace("/(root)/(tabs)/home")}
className="mt-6"
/>
@@ -125,75 +127,75 @@ const BookRide = () => {
const terminal = ride.status === "completed" || ride.status === "cancelled";
return (
<SafeAreaView className="flex-1 bg-general-500">
<SafeAreaView className="flex-1 bg-general-500 dark:bg-neutral-950">
<View className="h-[45%] bg-blue-500">
<Map />
</View>
<View className="flex-1 px-5 pt-4">
<Text className="text-2xl font-JakartaExtraBold mb-2">
{statusLabel[ride.status] ?? ride.status}
<Text className="text-2xl font-JakartaExtraBold mb-2 text-black dark:text-white">
{STATUS_KEY[ride.status] ? t(STATUS_KEY[ride.status]) : ride.status}
</Text>
{/* Searching state */}
{ride.status === "requested" ? (
<View className="items-center mt-6">
<ActivityIndicator size="large" color="#0286ff" />
<Text className="text-general-200 mt-3 text-center">
We&apos;re matching you with the nearest {ride.service} driver.
<Text className="text-general-200 dark:text-neutral-400 mt-3 text-center">
{t("bookRide.matchingDriver", { service: ride.service })}
</Text>
</View>
) : null}
{/* Driver card — shown once a driver is assigned. */}
{driver?.id ? (
<View className="bg-white rounded-2xl p-4 mt-2">
<View className="bg-white dark:bg-neutral-900 rounded-2xl p-4 mt-2">
<View className="flex-row items-center">
<Image
source={{ uri: driver.profile_image_url ?? undefined }}
className="w-16 h-16 rounded-full"
/>
<View className="ml-4 flex-1">
<Text className="text-lg font-JakartaSemiBold">
<Text className="text-lg font-JakartaSemiBold text-black dark:text-white">
{driver.first_name} {driver.last_name}
</Text>
<View className="flex-row items-center mt-1">
<Image source={icons.star} className="w-4 h-4" />
<Text className="ml-1 text-general-200">
{driver.rating?.toFixed(1) ?? "—"}
<Text className="ml-1 text-general-200 dark:text-neutral-400">
{driver.rating?.toFixed(1) ?? t("bookRide.ratingFallback")}
</Text>
{driver.car_model ? (
<Text className="ml-3 text-general-200">
<Text className="ml-3 text-general-200 dark:text-neutral-400">
{driver.car_model}
</Text>
) : null}
</View>
</View>
<Text className="text-xs text-general-200 capitalize">
<Text className="text-xs text-general-200 dark:text-neutral-400 capitalize">
{driver.service ?? ride.service}
</Text>
</View>
<View className="flex-row items-center gap-x-2 mt-4">
<Image source={icons.to} className="w-4 h-4" />
<Text className="font-JakartaMedium text-sm" numberOfLines={1}>
<Text className="font-JakartaMedium text-sm text-black dark:text-white" numberOfLines={1}>
{ride.origin_address}
</Text>
</View>
<View className="flex-row items-center gap-x-2 mt-2">
<Image source={icons.point} className="w-4 h-4" />
<Text className="font-JakartaMedium text-sm" numberOfLines={1}>
<Text className="font-JakartaMedium text-sm text-black dark:text-white" numberOfLines={1}>
{ride.destination_address}
</Text>
</View>
<View className="flex-row justify-between mt-4 pt-3 border-t border-neutral-100">
<Text className="text-general-200 text-xs">
<View className="flex-row justify-between mt-4 pt-3 border-t border-neutral-100 dark:border-neutral-800">
<Text className="text-general-200 dark:text-neutral-400 text-xs">
{ride.payment_status === "cash"
? "💵 Cash to driver"
: "💳 Paid by card"}
? t("bookRide.paymentCash")
: t("bookRide.paymentCard")}
</Text>
<Text className="font-JakartaBold text-emerald-600">
<Text className="font-JakartaBold text-emerald-600 dark:text-emerald-400">
${(ride.fare_price / 100).toFixed(2)}
</Text>
</View>
@@ -202,22 +204,22 @@ const BookRide = () => {
{/* Completed summary */}
{ride.status === "completed" ? (
<View className="bg-white rounded-2xl p-4 mt-4 items-center">
<View className="bg-white dark:bg-neutral-900 rounded-2xl p-4 mt-4 items-center">
<Image source={images.check} className="w-12 h-12" />
<Text className="text-lg font-JakartaBold mt-3">
Fare: ${(ride.fare_price / 100).toFixed(2)}
<Text className="text-lg font-JakartaBold mt-3 text-black dark:text-white">
{t("bookRide.fare", { fare: (ride.fare_price / 100).toFixed(2) })}
</Text>
<Text className="text-general-200 text-sm mt-1">
Trip time {formatTime(ride.ride_time)}
<Text className="text-general-200 dark:text-neutral-400 text-sm mt-1">
{t("bookRide.tripTime", { time: formatTime(ride.ride_time) })}
</Text>
</View>
) : null}
{/* Cancelled */}
{ride.status === "cancelled" ? (
<View className="bg-white rounded-2xl p-4 mt-4 items-center">
<Text className="text-general-200">
This ride was cancelled.
<View className="bg-white dark:bg-neutral-900 rounded-2xl p-4 mt-4 items-center">
<Text className="text-general-200 dark:text-neutral-400">
{t("bookRide.rideCancelled")}
</Text>
</View>
) : null}
@@ -225,17 +227,17 @@ const BookRide = () => {
<View className="mt-auto pt-6">
{terminal ? (
<CustomButton
title="Back Home"
title={t("bookRide.backHome")}
onPress={() => router.replace("/(root)/(tabs)/home")}
/>
) : (
<TouchableOpacity
onPress={cancel}
disabled={cancelling}
className="rounded-full py-3 bg-white items-center border border-rose-300"
className="rounded-full py-3 bg-white dark:bg-neutral-900 items-center border border-rose-300 dark:border-rose-900"
>
<Text className="font-JakartaBold text-rose-500">
{cancelling ? "Cancelling" : "Cancel Ride"}
{cancelling ? t("bookRide.cancelling") : t("bookRide.cancelRide")}
</Text>
</TouchableOpacity>
)}