diff --git a/apps/expo/src/app/driver/become-driver.tsx b/apps/expo/src/app/driver/become-driver.tsx index 06fd42d..9a903df 100644 --- a/apps/expo/src/app/driver/become-driver.tsx +++ b/apps/expo/src/app/driver/become-driver.tsx @@ -1,20 +1,32 @@ import React, { useState } from "react"; -import { Image, Modal, ScrollView, Text, TouchableOpacity, View } from "react-native"; -import { useRouter } from "expo-router"; import { - AntDesign, -} from "@expo/vector-icons"; + Image, + LogBox, + Modal, + Text, + TouchableOpacity, + View, +} from "react-native"; +import { SafeAreaView } from "react-native-safe-area-context"; +import { useRouter } from "expo-router"; +import { AntDesign, Ionicons } from "@expo/vector-icons"; + import { api } from "~/utils/api"; -import { RiderNavbar } from "~/components/Navbar/RiderNavbar"; -const DriverProfile = () => { +LogBox.ignoreLogs([ + "TRPCClientError: You are not a driver yet", + "Modal with 'formSheet' presentation style and 'transparent' value is not supported.", +]); + +const BecomeDriver = () => { const router = useRouter(); const [visible, setVisible] = useState(false); const [capacity, setCapacity] = useState(1); + const utils = api.useContext(); const riderQuery = api.rider.profile.useQuery(); const becomeDriver = api.rider.becomeDriver.useMutation({ onSuccess: () => { - router.push('/driver/profile'); + void utils.driver.profile.refetch(); }, }); @@ -22,73 +34,91 @@ const DriverProfile = () => { <> { setVisible(!visible); }} + presentationStyle="formSheet" + transparent={true} > - - Passenger Capacity - - + + + Passenger Capacity + + + {capacity} - - setCapacity(capacity<10 ? capacity+1 : capacity)}> + + + setCapacity(capacity < 10 ? capacity + 1 : capacity) + } + > - setCapacity(capacity>1 ? capacity-1 : capacity)}> + + setCapacity(capacity > 1 ? capacity - 1 : capacity) + } + > - - { setVisible(!visible); - becomeDriver.mutate({capacity: capacity}); + becomeDriver.mutate({ capacity: capacity }); }} > Confirm - - - Profile - - - - + + + void router.back()} + > + + + + - - - {riderQuery.data?.name} - - - - You are not a driver yet.{"\n"}Become driver to earn extra money! - setVisible(true)}> - - Become Driver + + + You are not a driver yet.{"\n"}Become driver to earn extra + money! + + setVisible(true)} + > + + + Become Driver + + + - + - - - + ); }; -export default DriverProfile; +export default BecomeDriver; diff --git a/apps/expo/src/app/driver/home.tsx b/apps/expo/src/app/driver/home.tsx index 3b28203..9163b31 100644 --- a/apps/expo/src/app/driver/home.tsx +++ b/apps/expo/src/app/driver/home.tsx @@ -1,14 +1,166 @@ -import { Text } from "react-native"; +import { + ScrollView, + Text, + TextInput, + TouchableOpacity, + View, +} from "react-native"; +import { type Region } from "react-native-maps"; import { SafeAreaView } from "react-native-safe-area-context"; +import { Link } from "expo-router"; +import { Feather } from "@expo/vector-icons"; +import DateTimePicker from "@react-native-community/datetimepicker"; +import { atom, useAtom } from "jotai"; +import { api } from "~/utils/api"; import { DriverNavbar } from "~/components/Navbar/DriverNavbar"; +export type Location = { + name: string; + longitude: number; + latitude: number; +}; + +export const createLocationsAtom = atom([]); +export const addLocationAtom = atom(null); +export const addLocationNameAtom = atom(""); + +const MILLIS_PER_DAY = 1000 * 86400; +export const dateAtom = atom(new Date(Date.now() + MILLIS_PER_DAY)); + const HomePage = () => { + const [locations, setLocations] = useAtom(createLocationsAtom); + const [addLocation, setAddLocation] = useAtom(addLocationAtom); + const [locationName, setLocationName] = useAtom(addLocationNameAtom); + const [date, setDate] = useAtom(dateAtom); + const addRideMutation = api.driver.create.useMutation(); + + const handleAddLocation = () => { + setLocations((prev) => [ + ...prev, + { + name: locationName, + longitude: addLocation?.longitude ?? 0, + latitude: addLocation?.latitude ?? 0, + }, + ]); + setLocationName(""); + setAddLocation(null); + }; + + const handleAddRide = () => { + addRideMutation.mutate({ + departAt: date, + locations, + }); + }; + return ( - - Home Page + <> + + + Trip + + + + + + Create a trip ! + + setLocationName(text)} + /> + + + {addLocation ? ( + + ({addLocation?.latitude.toFixed(4)},{" "} + {addLocation?.longitude.toFixed(4)}) + + ) : ( + + Location + + )} + + + handleAddLocation()} + > + Add a location + + + + + + + + Depart Time + { + const currentDate = selectedDate || date; + setDate(currentDate); + }} + display="default" + /> + + + + + {locations.map((location, id) => ( + + + + {id + 1}. + + + + {location.name} + + + ({location.latitude.toFixed(4)},{" "} + {location.longitude.toFixed(4)}) + + + + + setLocations((prev) => { + const newLocations = [...prev]; + newLocations.splice(id, 1); + return newLocations; + }) + } + > + + + + ))} + + + + handleAddRide()} + > + + Add ride + + + - + ); }; diff --git a/apps/expo/src/app/driver/profile.tsx b/apps/expo/src/app/driver/profile.tsx index 5ae61e6..4c3dd8d 100644 --- a/apps/expo/src/app/driver/profile.tsx +++ b/apps/expo/src/app/driver/profile.tsx @@ -1,6 +1,7 @@ import React, { useState } from "react"; import { Image, ScrollView, Text, TouchableOpacity, View } from "react-native"; -import { Link, useRouter } from "expo-router"; +import { SafeAreaView } from "react-native-safe-area-context"; +import { Link } from "expo-router"; import { Feather, FontAwesome5, @@ -15,26 +16,44 @@ import { DriverNavbar } from "~/components/Navbar/DriverNavbar"; import { RatingStars } from "~/components/RatingStars"; import { useObjectState } from "~/hooks/useObjectState"; import SignOut from "~/screens/auth/SignOut"; +import BecomeDriver from "./become-driver"; type ModalTypes = "Bio" | "Rules" | "Capacity" | "none"; -const DriverProfile = () => { +const ProfilePage = () => { const [visibleModal, setVisibleModal] = useState("none"); const [profile, updateProfile] = useObjectState({ bio: "", rules: "", capacity: "", }); - const router = useRouter(); const profileQuery = api.driver.profile.useQuery(undefined, { onSuccess: (data) => { updateProfile({ ...data, capacity: data.capacity.toString() }); }, + onError: (err) => { + console.log("here is an error", err); + }, + retry(failureCount, error) { + if (error.data?.code === "UNAUTHORIZED") return false; + return failureCount < 2; + }, }); + if (profileQuery.isLoading) { + return ( + + + + ); + } + if (profileQuery.data == null) { - router.push("/driver/become-driver"); + return ; } return ( @@ -158,4 +177,4 @@ const DriverProfile = () => { ); }; -export default DriverProfile; +export default ProfilePage; diff --git a/apps/expo/src/app/driver/set-location.tsx b/apps/expo/src/app/driver/set-location.tsx new file mode 100644 index 0000000..f0e6a79 --- /dev/null +++ b/apps/expo/src/app/driver/set-location.tsx @@ -0,0 +1,21 @@ +import { useAtomValue, useSetAtom } from "jotai"; + +import { LocationPicker } from "~/components/LocationPicker"; +import { addLocationAtom, createLocationsAtom, type Location } from "./home"; + +const locationToRegion = (location: Location) => ({ + latitude: location.latitude, + longitude: location.longitude, + latitudeDelta: 0.09, + longitudeDelta: 0.09, +}); + +const SetLocationPage = () => { + const setAddLocation = useSetAtom(addLocationAtom); + const locations = useAtomValue(createLocationsAtom); + const regions = locations.map(locationToRegion); + + return ; +}; + +export default SetLocationPage; diff --git a/apps/expo/src/app/index.tsx b/apps/expo/src/app/index.tsx index f55e12d..7c865ca 100644 --- a/apps/expo/src/app/index.tsx +++ b/apps/expo/src/app/index.tsx @@ -1,9 +1,10 @@ import React from "react"; import { Pressable, Text, View } from "react-native"; import { SafeAreaView } from "react-native-safe-area-context"; -import { Link } from "expo-router"; +import { Link, Redirect } from "expo-router"; const IndexPage = () => { + return ; return ( diff --git a/apps/expo/src/app/rider/history.tsx b/apps/expo/src/app/rider/history.tsx index 8d18bf5..f4c4581 100644 --- a/apps/expo/src/app/rider/history.tsx +++ b/apps/expo/src/app/rider/history.tsx @@ -8,7 +8,7 @@ import { api } from "~/utils/api"; import { HistoryItem } from "~/components/HistoryItem"; const HistoryPage = () => { - const historyQuery = api.rider.rideHistory.useQuery(); + const historyQuery = api.rider.rideHistory.useQuery({ limit: 10 }); const router = useRouter(); return ( diff --git a/apps/expo/src/app/rider/home.tsx b/apps/expo/src/app/rider/home.tsx index f0ae1a8..4748cf1 100644 --- a/apps/expo/src/app/rider/home.tsx +++ b/apps/expo/src/app/rider/home.tsx @@ -49,6 +49,7 @@ const HomePage = () => { Recent {recentQuery.data?.map(({ id, source, destination, departAt }) => ( { router.push("/rider/search"); setDataAtom(departAt); @@ -72,6 +73,7 @@ const HomePage = () => { Favorite {favoriteQuery.data?.map(({ id, source, destination, departAt }) => ( { router.push("/rider/search"); setDataAtom(departAt); diff --git a/apps/expo/src/components/HistoryModal.tsx b/apps/expo/src/components/HistoryModal.tsx index 0f6f4b3..015dc07 100644 --- a/apps/expo/src/components/HistoryModal.tsx +++ b/apps/expo/src/components/HistoryModal.tsx @@ -58,6 +58,7 @@ export const HistoryModal = (props: HistoryModalProps) => { {new Array(5).fill(null).map((_, i) => ( { numberOfLines={10} onChangeText={setCommentInput} value={commentInput} - className="text-md mx-2 rounded-lg bg-white px-5" + className="text-md mx-2 w-full rounded-lg bg-gray-200 px-5" placeholder={commentInput} /> diff --git a/apps/expo/src/components/LocationPicker.tsx b/apps/expo/src/components/LocationPicker.tsx new file mode 100644 index 0000000..20e1460 --- /dev/null +++ b/apps/expo/src/components/LocationPicker.tsx @@ -0,0 +1,109 @@ +import { useEffect, useRef, useState } from "react"; +import { Image, TouchableOpacity, View } from "react-native"; +import MapView, { + Marker, + PROVIDER_GOOGLE, + type Region, +} from "react-native-maps"; +import { SafeAreaView } from "react-native-safe-area-context"; +import { + getCurrentPositionAsync, + requestForegroundPermissionsAsync, +} from "expo-location"; +import { useRouter } from "expo-router"; +import { FontAwesome, Ionicons } from "@expo/vector-icons"; + +type LocationPickerProps = { + locations: Region[]; + setLocation: (location: Region) => void; +}; + +export const LocationPicker = ({ + setLocation, + locations, +}: LocationPickerProps) => { + const [region, setRegion] = useState(null); + const mapRef = useRef(null); + + useEffect(() => { + const setCurrentLocation = async () => { + if (locations[0] != null) { + setRegion(locations[0]); + return; + } + + const { status } = await requestForegroundPermissionsAsync(); + if (status !== "granted") { + return; + } + + const currentLocation = await getCurrentPositionAsync({}); + setRegion({ + latitude: currentLocation.coords.latitude, + longitude: currentLocation.coords.longitude, + latitudeDelta: 0.09, + longitudeDelta: 0.09, + }); + }; + void setCurrentLocation(); + }, []); + + const router = useRouter(); + const handleClick = ({ region }: { region: Region | null }) => { + if (region == null) return; + setLocation(region); + router.back(); + }; + + if (!region) { + return ( + + + + ); + } + + return ( + + + router.back()} + > + + + + + setRegion(newRegion)} + > + + {locations.map((location, index) => ( + + ))} + + + handleClick({ region })} + /> + + + ); +}; diff --git a/apps/expo/src/components/RatingStars.tsx b/apps/expo/src/components/RatingStars.tsx index 814fb40..fdf35e2 100644 --- a/apps/expo/src/components/RatingStars.tsx +++ b/apps/expo/src/components/RatingStars.tsx @@ -10,6 +10,7 @@ export const RatingStars = ({ stars }: RatingStarsProps) => { {new Array(5).fill(null).map((_, i) => ( { const { registerItemProps, setModalVisible } = props; return ( setModalVisible(true)}> - - - - - + + + {(registerItemProps.type === "driver-passengers" || registerItemProps.type === "rider-approved") && ( - + {registerItemProps.person.name} )} {(registerItemProps.type === "driver-pending" || registerItemProps.type === "rider-pending") && ( - + {registerItemProps.person.name} )} @@ -59,13 +57,9 @@ const RegisterSubItem = (props: RegisterSubItemProps) => { {registerItemProps.departAt.toDateString()} + {registerItemProps.source.name} - ({registerItemProps.source.latitude},{" "} - {registerItemProps.source.longitude}) - - - ({registerItemProps.desiredDestination.latitude},{" "} - {registerItemProps.desiredDestination.longitude}) + {registerItemProps.desiredDestination.name} $ {registerItemProps.price} diff --git a/apps/expo/src/components/RideModal.tsx b/apps/expo/src/components/RideModal.tsx index 7c6d8d4..473eaee 100644 --- a/apps/expo/src/components/RideModal.tsx +++ b/apps/expo/src/components/RideModal.tsx @@ -1,5 +1,12 @@ import React, { type Dispatch, type SetStateAction } from "react"; -import { Image, Modal, Text, TouchableOpacity, View } from "react-native"; +import { + Image, + LogBox, + Modal, + Text, + TouchableOpacity, + View, +} from "react-native"; import MapView, { PROVIDER_GOOGLE } from "react-native-maps"; import { AntDesign, @@ -12,6 +19,10 @@ import { import { api } from "~/utils/api"; import type { RegisterItemProps } from "./RegisterItem"; +LogBox.ignoreLogs([ + "Modal with 'formSheet' presentation style and 'transparent' value is not supported.", +]); + export type RideModalProps = { id: string; type: string; @@ -23,8 +34,18 @@ export type RideModalProps = { export const RideModal = (props: RideModalProps) => { const utils = api.useContext(); const { id, type, modalVisible, setModalVisible, registerItemProps } = props; - const cancelRideMutation = api.rider.leaveRide.useMutation(); - const driverMutation = api.driver.manageRider.useMutation(); + const cancelRideMutation = api.rider.leaveRide.useMutation({ + onSuccess: () => { + void utils.driver.approvedRider.invalidate(); + void utils.driver.pendingRider.invalidate(); + }, + }); + const driverMutation = api.driver.manageRider.useMutation({ + onSuccess: () => { + void utils.driver.approvedRider.invalidate(); + void utils.driver.pendingRider.invalidate(); + }, + }); const finishRide = api.driver.finishRide.useMutation({ onSuccess: () => { void utils.driver.approvedRider.invalidate(); @@ -37,12 +58,13 @@ export const RideModal = (props: RideModalProps) => { { setModalVisible(!modalVisible); }} > - + @@ -74,12 +96,10 @@ export const RideModal = (props: RideModalProps) => { {registerItemProps.departAt.toDateString()} - ({registerItemProps.source.latitude},{" "} - {registerItemProps.source.longitude}) + {registerItemProps.source.name} - ({registerItemProps.desiredDestination.latitude},{" "} - {registerItemProps.desiredDestination.longitude}) + {registerItemProps.desiredDestination.name} $ {registerItemProps.price} @@ -112,26 +132,6 @@ export const RideModal = (props: RideModalProps) => { }); setModalVisible(!modalVisible); }} - > - - - - - Cancel - - - - { - finishRide.mutate({ - rideId: id, - }); - setModalVisible(!modalVisible); - }} > diff --git a/apps/expo/src/components/SearchModal.tsx b/apps/expo/src/components/SearchModal.tsx index 1cc9b62..0bf5962 100644 --- a/apps/expo/src/components/SearchModal.tsx +++ b/apps/expo/src/components/SearchModal.tsx @@ -1,6 +1,6 @@ import React, { type Dispatch, type SetStateAction } from "react"; import { Image, Modal, Text, TouchableOpacity, View } from "react-native"; -import MapView, { PROVIDER_GOOGLE } from "react-native-maps"; +import MapView, { Marker, PROVIDER_GOOGLE } from "react-native-maps"; import { AntDesign, EvilIcons, @@ -82,12 +82,15 @@ export const SearchModal = (props: SearchModalProps) => { className="h-96 w-96" showsUserLocation initialRegion={{ - latitude: 24.8148, - longitude: 120.9675, + latitude: props.travel.source.latitude, + longitude: props.travel.source.longitude, latitudeDelta: 0.09, longitudeDelta: 0.09, }} - > + > + + + @@ -96,8 +99,8 @@ export const SearchModal = (props: SearchModalProps) => { onPress={() => { applyRideMutation.mutate({ rideId: travel.id, - source, - destination, + sourceId: travel.source.id, + destinationId: travel.desiredDestination.id, }); setModalVisible(!modalVisible); }} diff --git a/packages/api/src/router/driver.ts b/packages/api/src/router/driver.ts index 8f69ec7..27e5eed 100644 --- a/packages/api/src/router/driver.ts +++ b/packages/api/src/router/driver.ts @@ -4,7 +4,7 @@ import { z } from "zod"; import { DriverServiceErrors } from "@rideplus/internal"; -import { location } from "../schema/location"; +import { namedLocation } from "../schema/location"; import { createTRPCRouter, protectedProcedure } from "../trpc"; export const driverRouter = createTRPCRouter({ @@ -145,7 +145,7 @@ export const driverRouter = createTRPCRouter({ create: protectedProcedure .input( z.object({ - locations: z.array(location()), + locations: z.array(namedLocation()), departAt: z.date(), }), ) @@ -180,7 +180,7 @@ export const driverRouter = createTRPCRouter({ const status = input.action === "approve" ? "APPROVED" : "CANCELLED"; const passengerRide = await ctx.driverService.manageRider({ driverId: ctx.auth.userId, - driverRideId: input.rideId, + passengerRideId: input.rideId, passengerId: input.riderId, status, }); @@ -193,10 +193,10 @@ export const driverRouter = createTRPCRouter({ message: "You are not a driver yet", }); }) - .with({ error: DriverServiceErrors.DRIVER_RIDE_NOT_FOUND }, () => { + .with({ error: DriverServiceErrors.PASSANGER_RIDE_NOT_FOUND }, () => { throw new TRPCError({ code: "NOT_FOUND", - message: "Driver ride not found", + message: "Passenger ride not found", }); }) .exhaustive(); @@ -223,12 +223,24 @@ export const driverRouter = createTRPCRouter({ message: "You are not a driver yet", }); }) + .with({ error: DriverServiceErrors.PASSANGER_RIDE_NOT_FOUND }, () => { + throw new TRPCError({ + code: "NOT_FOUND", + message: "Passenger ride not found", + }); + }) .with({ error: DriverServiceErrors.DRIVER_RIDE_NOT_FOUND }, () => { throw new TRPCError({ code: "NOT_FOUND", message: "Driver ride not found", }); }) + .with({ error: DriverServiceErrors.NOT_OWNED_RIDE }, () => { + throw new TRPCError({ + code: "FORBIDDEN", + message: "You are not the owner of this ride", + }); + }) .exhaustive(); return result; diff --git a/packages/api/src/router/rider.ts b/packages/api/src/router/rider.ts index 6a63570..aba2e7f 100644 --- a/packages/api/src/router/rider.ts +++ b/packages/api/src/router/rider.ts @@ -258,16 +258,16 @@ export const riderRouter = createTRPCRouter({ .input( z.object({ rideId: z.string(), - source: location(), - destination: location(), + sourceId: z.string(), + destinationId: z.string(), }), ) .mutation(async ({ ctx, input }) => { const passengerRide = await ctx.passengerService.applyRide({ passengerId: ctx.auth.userId, driverRideId: input.rideId, - source: input.source, - destination: input.destination, + sourceId: input.sourceId, + destinationId: input.destinationId, }); const result = match(passengerRide) diff --git a/packages/api/src/schema/location.ts b/packages/api/src/schema/location.ts index e9470ea..97f4797 100644 --- a/packages/api/src/schema/location.ts +++ b/packages/api/src/schema/location.ts @@ -6,3 +6,10 @@ export const location = () => { longitude: z.number(), }); }; +export const namedLocation = () => { + return z.object({ + latitude: z.number(), + longitude: z.number(), + name: z.string().min(1), + }); +}; diff --git a/packages/internal/src/core/domain/driverRide.ts b/packages/internal/src/core/domain/driverRide.ts index d013caa..b925cc8 100644 --- a/packages/internal/src/core/domain/driverRide.ts +++ b/packages/internal/src/core/domain/driverRide.ts @@ -1,11 +1,11 @@ -import { type NamedLocation } from "./location"; +import { type NamedLocationWithId } from "./location"; export type DriverRideStatus = "OPEN" | "CLOSED"; export type DriverRide = { id: string; driverId: string; - locations: NamedLocation[]; + locations: NamedLocationWithId[]; status: DriverRideStatus; departAt: Date; }; diff --git a/packages/internal/src/core/domain/location.ts b/packages/internal/src/core/domain/location.ts index cdccd80..3f0a3b8 100644 --- a/packages/internal/src/core/domain/location.ts +++ b/packages/internal/src/core/domain/location.ts @@ -5,4 +5,12 @@ export type Location = { longitude: number; }; +export type LocationWithId = Expand< + { + id: string; + } & Location +>; + export type NamedLocation = Expand; + +export type NamedLocationWithId = Expand; diff --git a/packages/internal/src/core/ports/driverRideRepository.ts b/packages/internal/src/core/ports/driverRideRepository.ts index 85366dd..e216291 100644 --- a/packages/internal/src/core/ports/driverRideRepository.ts +++ b/packages/internal/src/core/ports/driverRideRepository.ts @@ -1,9 +1,17 @@ -import { type DriverRide } from "../../core/domain/driverRide"; -import { type MakeOptional } from "../../types/magic"; -import { type Location } from "../domain/location"; +import { + type DriverRide, + type DriverRideStatus, +} from "../../core/domain/driverRide"; +import { type Location, type NamedLocation } from "../domain/location"; import { type RideReview } from "../domain/rideReview"; -export type SaveDriverRideInput = MakeOptional; +export type SaveDriverRideInput = { + id?: string; + driverId: string; + locations: NamedLocation[]; + departAt: Date; + status?: DriverRideStatus; +}; export type FindByNearbyLocationsInput = { source: Location; diff --git a/packages/internal/src/core/ports/locationRepository.ts b/packages/internal/src/core/ports/locationRepository.ts index 16ea558..90a8b26 100644 --- a/packages/internal/src/core/ports/locationRepository.ts +++ b/packages/internal/src/core/ports/locationRepository.ts @@ -3,7 +3,7 @@ import { type Location, type NamedLocation } from "../../core/domain/location"; export type LocationWithDistance = NamedLocation & { distance: number }; export type LocationRepository = { - findName: (location: Location[]) => Promise; + findName: (locationIds: string[]) => Promise; findNearby( location: Location, maxDistance: number, diff --git a/packages/internal/src/core/ports/passengerRideRepository.ts b/packages/internal/src/core/ports/passengerRideRepository.ts index 443b5b7..742b2bb 100644 --- a/packages/internal/src/core/ports/passengerRideRepository.ts +++ b/packages/internal/src/core/ports/passengerRideRepository.ts @@ -34,4 +34,5 @@ export type PassengerRideRepository = { driverId: string, status: PassengerRideStatus, ): Promise; + findById(id: string): Promise; }; diff --git a/packages/internal/src/core/service/driverService.ts b/packages/internal/src/core/service/driverService.ts index 396d01b..3ab810f 100644 --- a/packages/internal/src/core/service/driverService.ts +++ b/packages/internal/src/core/service/driverService.ts @@ -1,6 +1,6 @@ import { type PassengerRideStatus } from "@prisma/client"; -import { type Location } from "../../core/domain/location"; +import { type NamedLocation } from "../../core/domain/location"; import { error, success } from "../../types/result"; import { type DriverRepository, @@ -30,25 +30,26 @@ type EditProfileInput = { type CreateDriverRideInput = { driverId: string; departAt: Date; - locations: Location[]; + locations: NamedLocation[]; }; type ManagePassengerInput = { driverId: string; passengerId: string; - driverRideId: string; + passengerRideId: string; status: PassengerRideStatus; }; export const DriverServiceErrors = { PROVIDER_USER_NOT_FOUND: "provider user not found", NOT_A_DRIVER: "not a driver", + NOT_OWNED_RIDE: "not owned ride", DRIVER_RIDE_NOT_FOUND: "driver ride not found", + PASSANGER_RIDE_NOT_FOUND: "passenger ride not found", } as const; export const createDriverService = (deps: DriverServiceDeps) => { - const { driverRides, drivers, passengerRides, locations, users, reviews } = - deps; + const { driverRides, drivers, passengerRides, users, reviews } = deps; return { getProfile: async (driverId: string) => { @@ -97,12 +98,10 @@ export const createDriverService = (deps: DriverServiceDeps) => { if (driver == null) { return error(DriverServiceErrors.NOT_A_DRIVER); } - - const namedLocation = await locations.findName(input.locations); const newDriverRide = await driverRides.save({ driverId: input.driverId, departAt: input.departAt, - locations: namedLocation, + locations: input.locations, }); return success(newDriverRide); }, @@ -113,21 +112,20 @@ export const createDriverService = (deps: DriverServiceDeps) => { return error(DriverServiceErrors.NOT_A_DRIVER); } - const driverRide = await passengerRides.findByDriverRideId( - input.driverRideId, - input.passengerId, + const passengerRide = await passengerRides.findById( + input.passengerRideId, ); - if (driverRide == null) { - return error(DriverServiceErrors.DRIVER_RIDE_NOT_FOUND); + if (passengerRide == null) { + return error(DriverServiceErrors.PASSANGER_RIDE_NOT_FOUND); } const newPassengerRide = await passengerRides.save({ - id: driverRide.id, + id: passengerRide.id, status: input.status, - locations: driverRide.locations, - driverId: driverRide.driverId, - passengerId: driverRide.passengerId, - driverRideId: driverRide.driverRideId, + locations: passengerRide.locations, + driverId: passengerRide.driverId, + passengerId: passengerRide.passengerId, + driverRideId: passengerRide.driverRideId, }); return success(newPassengerRide); @@ -231,17 +229,26 @@ export const createDriverService = (deps: DriverServiceDeps) => { return success(pendingPassengersInfo); }, - finishRide: async (driverId: string, driverRideId: string) => { + finishRide: async (driverId: string, passengerRideId: string) => { const driver = await drivers.findById(driverId); if (driver == null) { return error(DriverServiceErrors.NOT_A_DRIVER); } - const driverRide = await driverRides.findById(driverRideId); + const passengerRide = await passengerRides.findById(passengerRideId); + if (passengerRide == null) { + return error(DriverServiceErrors.PASSANGER_RIDE_NOT_FOUND); + } + + const driverRide = await driverRides.findById(passengerRide.driverRideId); if (driverRide == null) { return error(DriverServiceErrors.DRIVER_RIDE_NOT_FOUND); } + if (driverRide.driverId !== driverId) { + return error(DriverServiceErrors.NOT_OWNED_RIDE); + } + const finishedDriverRide = await driverRides.save({ id: driverRide.id, departAt: driverRide.departAt, diff --git a/packages/internal/src/core/service/passengerService.ts b/packages/internal/src/core/service/passengerService.ts index 59e8cac..1735378 100644 --- a/packages/internal/src/core/service/passengerService.ts +++ b/packages/internal/src/core/service/passengerService.ts @@ -28,8 +28,8 @@ type EditProfileInput = { type ApplyRideInput = { driverRideId: string; passengerId: string; - source: Location; - destination: Location; + sourceId: string; + destinationId: string; }; type LeaveRideInput = { @@ -121,8 +121,8 @@ export const createPassengerService = (deps: PassengerServiceDeps) => { } const namedLocations = await locations.findName([ - input.source, - input.destination, + input.sourceId, + input.destinationId, ]); try { @@ -290,6 +290,8 @@ export const createPassengerService = (deps: PassengerServiceDeps) => { searchNearbyRides: async (input: SearchNearbyRidesInput) => { const rides = await driverRides.findByNearbyLocations(input); + console.log(rides); + const driverIds = rides.map((ride) => ride.driverId); const passengerIds = rides.flatMap((ride) => ride.passengers.map((passenger) => passenger.id), diff --git a/packages/internal/src/repositories/prismaDriverRideRepo.ts b/packages/internal/src/repositories/prismaDriverRideRepo.ts index 7afb2a3..235858d 100644 --- a/packages/internal/src/repositories/prismaDriverRideRepo.ts +++ b/packages/internal/src/repositories/prismaDriverRideRepo.ts @@ -16,7 +16,7 @@ const locationToRangeFilter = (location: Location, range: number) => { }; }; -const LONG_LAT_OFFSET = 0.005; +const LONG_LAT_OFFSET = 0.01; export const createPrismaDriverRideRepo = ( prisma: PrismaClient, @@ -77,7 +77,10 @@ export const createPrismaDriverRideRepo = ( locations: { createMany: { data: input.locations.map((location, index) => ({ - ...location, + driverRideId: input.id!, + latitude: location.latitude, + longitude: location.longitude, + name: location.name, serialNumber: index, })), }, diff --git a/packages/internal/src/repositories/prismaLocationRepo.ts b/packages/internal/src/repositories/prismaLocationRepo.ts index 9bf6fe2..19a6182 100644 --- a/packages/internal/src/repositories/prismaLocationRepo.ts +++ b/packages/internal/src/repositories/prismaLocationRepo.ts @@ -11,25 +11,20 @@ export const createPrismaLocationRepo = ( const uniqueLocations = [...new Set(locations)]; const namedLocations = await prisma.location.findMany({ where: { - AND: { - latitude: { - in: uniqueLocations.map((location) => location.latitude), - }, - longitude: { - in: uniqueLocations.map((location) => location.longitude), - }, + id: { + in: uniqueLocations, }, }, }); const locationMap = namedLocations.reduce((acc, location) => { - acc[`${location.latitude},${location.longitude}`] = location; + acc[location.id] = location; return acc; }, {} as Record); - return locations.map((location) => { - const key = `${location.latitude},${location.longitude}`; - return locationMap[key] ?? { ...location, name: "unknown" }; + return locations.map((locationId) => { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + return locationMap[locationId]!; }); }, diff --git a/packages/internal/src/repositories/prismaPassengerRideRepo.ts b/packages/internal/src/repositories/prismaPassengerRideRepo.ts index d1278b0..a7ea195 100644 --- a/packages/internal/src/repositories/prismaPassengerRideRepo.ts +++ b/packages/internal/src/repositories/prismaPassengerRideRepo.ts @@ -158,5 +158,23 @@ export const createPrismaPassengerRideRepo = ( return rides; }, + + findById: async (id) => { + const ride = await prisma.passengerRide.findUnique({ + where: { + id, + }, + include: { + driverRide: { + select: { + departAt: true, + }, + }, + locations: true, + }, + }); + + return ride; + }, }; };