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
+65 -41
View File
@@ -6,6 +6,7 @@ import { CustomButton } from "@/components/custom-button";
import { RideLayout } from "@/components/ride-layout";
import { SERVICES } from "@/constants/services";
import { ApiError, fetchAPI } from "@/lib/fetch";
import { useT } from "@/lib/i18n";
import { calculateTripFare } from "@/lib/map";
import { formatLBP } from "@/lib/pricing";
import { requestRide } from "@/lib/request-ride";
@@ -38,6 +39,7 @@ const ConfirmRide = () => {
} = useLocationStore();
const { service: storeService, setService } = useServiceStore();
const { user } = useSession();
const t = useT();
const service = params.service ?? storeService;
const selected = SERVICES.find((s) => s.id === service) ?? SERVICES[0];
@@ -154,11 +156,17 @@ const ConfirmRide = () => {
const request = async () => {
if (!userLatitude || !userLongitude || !destinationLatitude || !destinationLongitude) {
Alert.alert("Missing route", "Please set a pickup and destination first.");
Alert.alert(
t("confirmRide.alertMissingRouteTitle"),
t("confirmRide.alertMissingRouteBody"),
);
return;
}
if (!estimate) {
Alert.alert("No estimate", "We couldn't estimate this fare. Please try again.");
Alert.alert(
t("confirmRide.alertNoEstimateTitle"),
t("confirmRide.alertNoEstimateBody"),
);
return;
}
@@ -194,8 +202,8 @@ const ConfirmRide = () => {
const msg =
err instanceof ApiError
? err.message
: "Something went wrong while booking your ride. Please try again.";
Alert.alert("Error", msg);
: t("confirmRide.alertErrorFallback");
Alert.alert(t("confirmRide.alertErrorTitle"), msg);
} finally {
setProcessing(false);
}
@@ -203,11 +211,11 @@ const ConfirmRide = () => {
if (method === "card") {
Alert.alert(
"Pay by card",
`Your card will be charged $${estimate.fare}.`,
t("confirmRide.alertPayCardTitle"),
t("confirmRide.alertPayCardBody", { fare: estimate.fare }),
[
{ text: "Cancel", style: "cancel" },
{ text: "Continue", onPress: () => void doRequest() },
{ text: t("common.cancel"), style: "cancel" },
{ text: t("common.continue"), onPress: () => void doRequest() },
],
);
} else {
@@ -216,39 +224,53 @@ const ConfirmRide = () => {
};
return (
<RideLayout title="Request Ride" snapPoints={["60%", "88%"]}>
<Text className="text-xl font-JakartaSemiBold mb-1">Your trip</Text>
<RideLayout title={t("confirmRide.title")} snapPoints={["60%", "88%"]}>
<Text className="text-xl font-JakartaSemiBold mb-1 text-black dark:text-white">
{t("confirmRide.yourTrip")}
</Text>
<View className="flex-row items-center gap-x-2 mb-1">
<Text className="text-general-200 text-xs">Pickup</Text>
<Text className="text-general-200 dark:text-neutral-400 text-xs">
{t("confirmRide.pickup")}
</Text>
</View>
<Text className="font-JakartaMedium mb-3" numberOfLines={1}>
<Text className="font-JakartaMedium mb-3 text-black dark:text-white" numberOfLines={1}>
{userAddress}
</Text>
<View className="flex-row items-center gap-x-2 mb-1">
<Text className="text-general-200 text-xs">Destination</Text>
<Text className="text-general-200 dark:text-neutral-400 text-xs">
{t("confirmRide.destination")}
</Text>
</View>
<Text className="font-JakartaMedium mb-4" numberOfLines={1}>
<Text className="font-JakartaMedium mb-4 text-black dark:text-white" numberOfLines={1}>
{destinationAddress}
</Text>
<View className="flex-row items-center justify-between bg-general-500 rounded-2xl p-4 mb-4">
<View className="flex-row items-center justify-between bg-general-500 dark:bg-neutral-950 rounded-2xl p-4 mb-4">
<View>
<Text className="text-general-200 text-xs font-JakartaMedium">
{selected.label} · {selected.tagline}
<Text className="text-general-200 dark:text-neutral-400 text-xs font-JakartaMedium">
{t(selected.labelKey)} · {t(selected.taglineKey)}
</Text>
<Text className="text-general-200 text-xs mt-1">
Trip time {estimate ? formatTime(estimate.durationSeconds / 60) : "…"}
<Text className="text-general-200 dark:text-neutral-400 text-xs mt-1">
{t("confirmRide.tripTime", {
time: estimate ? formatTime(estimate.durationSeconds / 60) : "…",
})}
</Text>
</View>
<View className="items-end">
<Text className="text-2xl font-JakartaExtraBold">
{estimating ? "…" : estimate ? `$${estimate.fare}` : "—"}
<Text className="text-2xl font-JakartaExtraBold text-black dark:text-white">
{estimating
? "…"
: estimate
? t("confirmRide.fareDisplay", { fare: estimate.fare })
: "—"}
</Text>
{estimate ? (
<Text className="text-xs text-general-200">
{formatLBP(parseFloat(estimate.fare))}
<Text className="text-xs text-general-200 dark:text-neutral-400">
{t("confirmRide.lbpEstimate", {
lbp: formatLBP(parseFloat(estimate.fare)),
})}
</Text>
) : null}
</View>
@@ -256,50 +278,52 @@ const ConfirmRide = () => {
<Text
className={`text-base font-JakartaMedium mb-2 ${
driversOnline === 0 ? "text-rose-500" : "text-general-200"
driversOnline === 0
? "text-rose-500"
: "text-general-200 dark:text-neutral-400"
}`}
>
{driversOnline === 0
? `No ${selected.label} drivers online right now`
? t("confirmRide.noDrivers", { service: t(selected.labelKey) })
: nearestEta == null
? "Finding drivers nearby…"
: `Nearest driver${nearestEta} min away`}
? t("confirmRide.findingDrivers")
: t("confirmRide.nearestDriver", { eta: nearestEta })}
</Text>
<Text className="text-lg font-JakartaSemiBold mt-2 mb-2">
Payment Method
<Text className="text-lg font-JakartaSemiBold mt-2 mb-2 text-black dark:text-white">
{t("confirmRide.paymentMethod")}
</Text>
<View className="flex-row gap-x-3 mb-2">
<TouchableOpacity
onPress={() => 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"
? "bg-general-600 dark:bg-primary-500/20 border-primary-500"
: "bg-white dark:bg-neutral-900 border-general-700 dark:border-neutral-700"
}`}
>
<Text
className={`font-JakartaMedium ${
method === "cash" ? "text-white" : "text-black"
method === "cash" ? "text-white" : "text-black dark:text-white"
}`}
>
💵 Cash
{t("confirmRide.cash")}
</Text>
</TouchableOpacity>
<TouchableOpacity
onPress={() => 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"
? "bg-general-600 dark:bg-primary-500/20 border-primary-500"
: "bg-white dark:bg-neutral-900 border-general-700 dark:border-neutral-700"
}`}
>
<Text
className={`font-JakartaMedium ${
method === "card" ? "text-white" : "text-black"
method === "card" ? "text-white" : "text-black dark:text-white"
}`}
>
💳 Card
{t("confirmRide.card")}
</Text>
</TouchableOpacity>
</View>
@@ -307,12 +331,12 @@ const ConfirmRide = () => {
<CustomButton
title={
processing
? "Requesting"
? t("confirmRide.requesting")
: driversOnline === 0
? "No drivers online"
? t("confirmRide.noDriversOnline")
: method === "cash"
? "Request Ride · Pay cash to driver"
: "Request Ride · Pay by card"
? t("confirmRide.requestCash")
: t("confirmRide.requestCard")
}
className="mt-4"
onPress={request}