From 2548273d89e55efc24a2df101b621b06a6a83313 Mon Sep 17 00:00:00 2001 From: omagdy7 Date: Fri, 22 Dec 2023 20:32:40 +0200 Subject: Added rendering passenger requests --- driver/package.json | 1 + driver/pnpm-lock.yaml | 80 ++++++++++++++ driver/src/components/RideDialog.tsx | 48 ++++++++- driver/src/components/ui/toast.tsx | 127 ++++++++++++++++++++++ driver/src/components/ui/toaster.tsx | 33 ++++++ driver/src/components/ui/use-toast.ts | 192 ++++++++++++++++++++++++++++++++++ driver/src/pages/Home.tsx | 44 ++++++-- driver/src/utils/fetchRideRequests.ts | 38 +++++++ driver/src/utils/fetchUserDetails.ts | 8 +- 9 files changed, 555 insertions(+), 16 deletions(-) create mode 100644 driver/src/components/ui/toast.tsx create mode 100644 driver/src/components/ui/toaster.tsx create mode 100644 driver/src/components/ui/use-toast.ts create mode 100644 driver/src/utils/fetchRideRequests.ts diff --git a/driver/package.json b/driver/package.json index 9853e7c..5ec615d 100644 --- a/driver/package.json +++ b/driver/package.json @@ -14,6 +14,7 @@ "@radix-ui/react-dialog": "^1.0.5", "@radix-ui/react-label": "^2.0.2", "@radix-ui/react-slot": "^1.0.2", + "@radix-ui/react-toast": "^1.1.5", "class-variance-authority": "^0.7.0", "clsx": "^2.0.0", "firebase": "^10.7.1", diff --git a/driver/pnpm-lock.yaml b/driver/pnpm-lock.yaml index 21624a2..cb5722b 100644 --- a/driver/pnpm-lock.yaml +++ b/driver/pnpm-lock.yaml @@ -17,6 +17,9 @@ dependencies: '@radix-ui/react-slot': specifier: ^1.0.2 version: 1.0.2(@types/react@18.2.45)(react@18.2.0) + '@radix-ui/react-toast': + specifier: ^1.1.5 + version: 1.1.5(@types/react-dom@18.2.18)(@types/react@18.2.45)(react-dom@18.2.0)(react@18.2.0) class-variance-authority: specifier: ^0.7.0 version: 0.7.0 @@ -1181,6 +1184,30 @@ packages: react-dom: 18.2.0(react@18.2.0) dev: false + /@radix-ui/react-collection@1.0.3(@types/react-dom@18.2.18)(@types/react@18.2.45)(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-3SzW+0PW7yBBoQlT8wNcGtaxaD0XSu0uLUFgrtHY08Acx05TaHaOmVLR73c0j/cqpDy53KBMO7s0dx2wmOIDIA==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 + react-dom: ^16.8 || ^17.0 || ^18.0 + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + dependencies: + '@babel/runtime': 7.23.6 + '@radix-ui/react-compose-refs': 1.0.1(@types/react@18.2.45)(react@18.2.0) + '@radix-ui/react-context': 1.0.1(@types/react@18.2.45)(react@18.2.0) + '@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.18)(@types/react@18.2.45)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-slot': 1.0.2(@types/react@18.2.45)(react@18.2.0) + '@types/react': 18.2.45 + '@types/react-dom': 18.2.18 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + dev: false + /@radix-ui/react-compose-refs@1.0.1(@types/react@18.2.45)(react@18.2.0): resolution: {integrity: sha512-fDSBgd44FKHa1FRMU59qBMPFcl2PZE+2nmqunj+BWFyYYjnhIDWL2ItDs3rrbJDQOtzt5nIebLCQc4QRfz6LJw==} peerDependencies: @@ -1420,6 +1447,38 @@ packages: react: 18.2.0 dev: false + /@radix-ui/react-toast@1.1.5(@types/react-dom@18.2.18)(@types/react@18.2.45)(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-fRLn227WHIBRSzuRzGJ8W+5YALxofH23y0MlPLddaIpLpCDqdE0NZlS2NRQDRiptfxDeeCjgFIpexB1/zkxDlw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 + react-dom: ^16.8 || ^17.0 || ^18.0 + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + dependencies: + '@babel/runtime': 7.23.6 + '@radix-ui/primitive': 1.0.1 + '@radix-ui/react-collection': 1.0.3(@types/react-dom@18.2.18)(@types/react@18.2.45)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-compose-refs': 1.0.1(@types/react@18.2.45)(react@18.2.0) + '@radix-ui/react-context': 1.0.1(@types/react@18.2.45)(react@18.2.0) + '@radix-ui/react-dismissable-layer': 1.0.5(@types/react-dom@18.2.18)(@types/react@18.2.45)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-portal': 1.0.4(@types/react-dom@18.2.18)(@types/react@18.2.45)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-presence': 1.0.1(@types/react-dom@18.2.18)(@types/react@18.2.45)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.18)(@types/react@18.2.45)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-use-callback-ref': 1.0.1(@types/react@18.2.45)(react@18.2.0) + '@radix-ui/react-use-controllable-state': 1.0.1(@types/react@18.2.45)(react@18.2.0) + '@radix-ui/react-use-layout-effect': 1.0.1(@types/react@18.2.45)(react@18.2.0) + '@radix-ui/react-visually-hidden': 1.0.3(@types/react-dom@18.2.18)(@types/react@18.2.45)(react-dom@18.2.0)(react@18.2.0) + '@types/react': 18.2.45 + '@types/react-dom': 18.2.18 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + dev: false + /@radix-ui/react-use-callback-ref@1.0.1(@types/react@18.2.45)(react@18.2.0): resolution: {integrity: sha512-D94LjX4Sp0xJFVaoQOd3OO9k7tpBYNOXdVhkltUbGv2Qb9OXdrg/CpsjlZv7ia14Sylv398LswWBVVu5nqKzAQ==} peerDependencies: @@ -1478,6 +1537,27 @@ packages: react: 18.2.0 dev: false + /@radix-ui/react-visually-hidden@1.0.3(@types/react-dom@18.2.18)(@types/react@18.2.45)(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-D4w41yN5YRKtu464TLnByKzMDG/JlMPHtfZgQAu9v6mNakUqGUI9vUrfQKz8NK41VMm/xbZbh76NUTVtIYqOMA==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 + react-dom: ^16.8 || ^17.0 || ^18.0 + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + dependencies: + '@babel/runtime': 7.23.6 + '@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.18)(@types/react@18.2.45)(react-dom@18.2.0)(react@18.2.0) + '@types/react': 18.2.45 + '@types/react-dom': 18.2.18 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + dev: false + /@remix-run/router@1.14.0: resolution: {integrity: sha512-WOHih+ClN7N8oHk9N4JUiMxQJmRVaOxcg8w7F/oHUXzJt920ekASLI/7cYX8XkntDWRhLZtsk6LbGrkgOAvi5A==} engines: {node: '>=14.0.0'} diff --git a/driver/src/components/RideDialog.tsx b/driver/src/components/RideDialog.tsx index da7767b..ff3aa98 100644 --- a/driver/src/components/RideDialog.tsx +++ b/driver/src/components/RideDialog.tsx @@ -1,6 +1,5 @@ -import { addDoc, collection, doc, getDoc } from "firebase/firestore" +import { addDoc, collection } from "firebase/firestore" import { db } from "../firebase/firebase_config" -import { auth } from "../firebase/firebase_config" import { Button } from "@/components/ui/button" import { Dialog, @@ -14,6 +13,8 @@ import { import { Input } from "@/components/ui/input" import { Label } from "@/components/ui/label" import { useState } from "react" +import { useToast } from "./ui/use-toast" +import { ToastAction } from "./ui/toast" interface RideOrder { driverName: string @@ -28,15 +29,21 @@ interface RideOrder { } -function RideDialog({ name, brand, model, color, plateNumber }: any) { +function RideDialog({ name, brand, model, color, plateNumber, phoneNumber }: any) { + const { toast } = useToast() - const [status, _setStatus] = useState('Pending') + const [status, _setStatus] = useState('Unreserved') const [orderTime, _setOrderTime] = useState(new Date()) const [pickUpLocation, setPickUpLocation] = useState() const [dropOffLocation, setDropOffLocation] = useState() + const [isRideAdded, setIsRideAdded] = useState(true) + const [cost, setCost] = useState('') const addRideOrderToFirestore = async () => { + if (isRideAdded) { + return; + } try { // Get a reference to the 'rideOrders' collection const rideOrdersCollection = collection(db, 'Rides'); @@ -44,16 +51,19 @@ function RideDialog({ name, brand, model, color, plateNumber }: any) { // Add a new document to the 'rideOrders' collection with the data from the RideOrder object const newRideOrderRef = await addDoc(rideOrdersCollection, { driverName: name, + phoneNumber: phoneNumber, carModel: model, carBrand: brand, carColor: color, plateNumber: plateNumber, + cost: parseInt(cost), status: status, orderTime: orderTime, fromLocation: pickUpLocation, toLocation: dropOffLocation, }); + setIsRideAdded(true) console.log('Ride order added with ID:', newRideOrderRef.id); // 'newRideOrderRef.id' will give you the document ID of the added ride order } catch (error) { @@ -61,6 +71,10 @@ function RideDialog({ name, brand, model, color, plateNumber }: any) { } }; + const cancelRide = () => { + setIsRideAdded(false) + } + return (
@@ -97,9 +111,33 @@ function RideDialog({ name, brand, model, color, plateNumber }: any) { onChange={(e) => setPickUpLocation(e.target.value)} />
+
+ + setCost(e.target.value)} + /> +
- + diff --git a/driver/src/components/ui/toast.tsx b/driver/src/components/ui/toast.tsx new file mode 100644 index 0000000..a822477 --- /dev/null +++ b/driver/src/components/ui/toast.tsx @@ -0,0 +1,127 @@ +import * as React from "react" +import * as ToastPrimitives from "@radix-ui/react-toast" +import { cva, type VariantProps } from "class-variance-authority" +import { X } from "lucide-react" + +import { cn } from "@/lib/utils" + +const ToastProvider = ToastPrimitives.Provider + +const ToastViewport = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +ToastViewport.displayName = ToastPrimitives.Viewport.displayName + +const toastVariants = cva( + "group pointer-events-auto relative flex w-full items-center justify-between space-x-4 overflow-hidden rounded-md border p-6 pr-8 shadow-lg transition-all data-[swipe=cancel]:translate-x-0 data-[swipe=end]:translate-x-[var(--radix-toast-swipe-end-x)] data-[swipe=move]:translate-x-[var(--radix-toast-swipe-move-x)] data-[swipe=move]:transition-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[swipe=end]:animate-out data-[state=closed]:fade-out-80 data-[state=closed]:slide-out-to-right-full data-[state=open]:slide-in-from-top-full data-[state=open]:sm:slide-in-from-bottom-full", + { + variants: { + variant: { + default: "border bg-background text-foreground", + destructive: + "destructive group border-destructive bg-destructive text-destructive-foreground", + }, + }, + defaultVariants: { + variant: "default", + }, + } +) + +const Toast = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef & + VariantProps +>(({ className, variant, ...props }, ref) => { + return ( + + ) +}) +Toast.displayName = ToastPrimitives.Root.displayName + +const ToastAction = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +ToastAction.displayName = ToastPrimitives.Action.displayName + +const ToastClose = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + + + +)) +ToastClose.displayName = ToastPrimitives.Close.displayName + +const ToastTitle = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +ToastTitle.displayName = ToastPrimitives.Title.displayName + +const ToastDescription = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +ToastDescription.displayName = ToastPrimitives.Description.displayName + +type ToastProps = React.ComponentPropsWithoutRef + +type ToastActionElement = React.ReactElement + +export { + type ToastProps, + type ToastActionElement, + ToastProvider, + ToastViewport, + Toast, + ToastTitle, + ToastDescription, + ToastClose, + ToastAction, +} diff --git a/driver/src/components/ui/toaster.tsx b/driver/src/components/ui/toaster.tsx new file mode 100644 index 0000000..a2209ba --- /dev/null +++ b/driver/src/components/ui/toaster.tsx @@ -0,0 +1,33 @@ +import { + Toast, + ToastClose, + ToastDescription, + ToastProvider, + ToastTitle, + ToastViewport, +} from "@/components/ui/toast" +import { useToast } from "@/components/ui/use-toast" + +export function Toaster() { + const { toasts } = useToast() + + return ( + + {toasts.map(function ({ id, title, description, action, ...props }) { + return ( + +
+ {title && {title}} + {description && ( + {description} + )} +
+ {action} + +
+ ) + })} + +
+ ) +} diff --git a/driver/src/components/ui/use-toast.ts b/driver/src/components/ui/use-toast.ts new file mode 100644 index 0000000..1671307 --- /dev/null +++ b/driver/src/components/ui/use-toast.ts @@ -0,0 +1,192 @@ +// Inspired by react-hot-toast library +import * as React from "react" + +import type { + ToastActionElement, + ToastProps, +} from "@/components/ui/toast" + +const TOAST_LIMIT = 1 +const TOAST_REMOVE_DELAY = 1000000 + +type ToasterToast = ToastProps & { + id: string + title?: React.ReactNode + description?: React.ReactNode + action?: ToastActionElement +} + +const actionTypes = { + ADD_TOAST: "ADD_TOAST", + UPDATE_TOAST: "UPDATE_TOAST", + DISMISS_TOAST: "DISMISS_TOAST", + REMOVE_TOAST: "REMOVE_TOAST", +} as const + +let count = 0 + +function genId() { + count = (count + 1) % Number.MAX_SAFE_INTEGER + return count.toString() +} + +type ActionType = typeof actionTypes + +type Action = + | { + type: ActionType["ADD_TOAST"] + toast: ToasterToast + } + | { + type: ActionType["UPDATE_TOAST"] + toast: Partial + } + | { + type: ActionType["DISMISS_TOAST"] + toastId?: ToasterToast["id"] + } + | { + type: ActionType["REMOVE_TOAST"] + toastId?: ToasterToast["id"] + } + +interface State { + toasts: ToasterToast[] +} + +const toastTimeouts = new Map>() + +const addToRemoveQueue = (toastId: string) => { + if (toastTimeouts.has(toastId)) { + return + } + + const timeout = setTimeout(() => { + toastTimeouts.delete(toastId) + dispatch({ + type: "REMOVE_TOAST", + toastId: toastId, + }) + }, TOAST_REMOVE_DELAY) + + toastTimeouts.set(toastId, timeout) +} + +export const reducer = (state: State, action: Action): State => { + switch (action.type) { + case "ADD_TOAST": + return { + ...state, + toasts: [action.toast, ...state.toasts].slice(0, TOAST_LIMIT), + } + + case "UPDATE_TOAST": + return { + ...state, + toasts: state.toasts.map((t) => + t.id === action.toast.id ? { ...t, ...action.toast } : t + ), + } + + case "DISMISS_TOAST": { + const { toastId } = action + + // ! Side effects ! - This could be extracted into a dismissToast() action, + // but I'll keep it here for simplicity + if (toastId) { + addToRemoveQueue(toastId) + } else { + state.toasts.forEach((toast) => { + addToRemoveQueue(toast.id) + }) + } + + return { + ...state, + toasts: state.toasts.map((t) => + t.id === toastId || toastId === undefined + ? { + ...t, + open: false, + } + : t + ), + } + } + case "REMOVE_TOAST": + if (action.toastId === undefined) { + return { + ...state, + toasts: [], + } + } + return { + ...state, + toasts: state.toasts.filter((t) => t.id !== action.toastId), + } + } +} + +const listeners: Array<(state: State) => void> = [] + +let memoryState: State = { toasts: [] } + +function dispatch(action: Action) { + memoryState = reducer(memoryState, action) + listeners.forEach((listener) => { + listener(memoryState) + }) +} + +type Toast = Omit + +function toast({ ...props }: Toast) { + const id = genId() + + const update = (props: ToasterToast) => + dispatch({ + type: "UPDATE_TOAST", + toast: { ...props, id }, + }) + const dismiss = () => dispatch({ type: "DISMISS_TOAST", toastId: id }) + + dispatch({ + type: "ADD_TOAST", + toast: { + ...props, + id, + open: true, + onOpenChange: (open) => { + if (!open) dismiss() + }, + }, + }) + + return { + id: id, + dismiss, + update, + } +} + +function useToast() { + const [state, setState] = React.useState(memoryState) + + React.useEffect(() => { + listeners.push(setState) + return () => { + const index = listeners.indexOf(setState) + if (index > -1) { + listeners.splice(index, 1) + } + } + }, [state]) + + return { + ...state, + toast, + dismiss: (toastId?: string) => dispatch({ type: "DISMISS_TOAST", toastId }), + } +} + +export { useToast, toast } diff --git a/driver/src/pages/Home.tsx b/driver/src/pages/Home.tsx index 81f1fb9..5a2d981 100644 --- a/driver/src/pages/Home.tsx +++ b/driver/src/pages/Home.tsx @@ -5,9 +5,10 @@ import { Button } from "@/components/ui/button" import { CardTitle, CardHeader, CardContent, Card } from "@/components/ui/card" import { auth, db } from "@/firebase/firebase_config" import { fetchUserDetails } from "@/utils/fetchUserDetails" -import { DocumentData, collection, doc, getDoc, getDocs, query, where } from "firebase/firestore" +import { Toaster } from "@/components/ui/toaster" import { useEffect, useState } from "react" import { Navigate } from "react-router-dom" +import { fetchRideRequests } from "@/utils/fetchRideRequests" interface IDriver { uid: string, @@ -21,6 +22,8 @@ interface IDriver { interface IPassengerRequest { passengerName: string, + phoneNumber: string, + status: string, pickUp: string, dropOff: string, } @@ -30,12 +33,18 @@ interface ITrip { dropOff: string, } -function PassengerRequestCard({ passengerName, pickUp, dropOff }: IPassengerRequest) { +function PassengerRequestCard({ passengerName, pickUp, dropOff, status, phoneNumber }: IPassengerRequest) { return (
  • Passenger: {passengerName}

    +

    + Phone number: {phoneNumber} +

    +

    + Status: {status} +

    Pickup: {pickUp}

    @@ -57,13 +66,18 @@ function PassengerRequestCard({ passengerName, pickUp, dropOff }: IPassengerRequ export default function Home() { const [driverData, setDriverData] = useState() + const [rideRequests, setRideRequests] = useState([]) const [currentTrip, setCurrentTrip] = useState() - useEffect(() => { + const user = auth.currentUser; async function fetchData() { - const data: IDriver | null | undefined = await fetchUserDetails(); + const data: IDriver | null | undefined = await fetchUserDetails(user?.uid); + const rideReqs = await fetchRideRequests() + console.log("RideRequests:", rideReqs) setDriverData(data) + setRideRequests(rideReqs) + console.log("Length: ", rideRequests?.length) } fetchData() }, [auth.currentUser, db]); @@ -134,16 +148,34 @@ export default function Home() {
      - + {rideRequests?.map((rideReq) => { + return ( + < PassengerRequestCard + passengerName={rideReq?.name} + pickUp={rideReq?.pickUp} + dropOff={rideReq?.dropOff} + phoneNumber={rideReq?.phoneNumber} + status={rideReq?.status} + /> + ) + })}
    - +
    + ) ) diff --git a/driver/src/utils/fetchRideRequests.ts b/driver/src/utils/fetchRideRequests.ts new file mode 100644 index 0000000..ec28471 --- /dev/null +++ b/driver/src/utils/fetchRideRequests.ts @@ -0,0 +1,38 @@ +import { auth, db } from "@/firebase/firebase_config"; +import { DocumentData, collection, getDocs, query, where } from "firebase/firestore"; +import { fetchUserDetails } from "./fetchUserDetails"; + + +export const fetchRideRequests = async () => { + const user = auth.currentUser; + console.log(user) + try { + let data: any[] = [] + if (user) { + const usersRef = collection(db, "RideRequest") + const q = query(usersRef, where("status", "==", "Pending")) + const querySnapshot = await getDocs(q); + querySnapshot.forEach((doc: DocumentData) => { + data.push(doc.data()) + console.log(doc.id, " => ", doc.data()); + }); + const rideReqs = data?.map(async (rideReq) => { + const passengerData: any = await fetchUserDetails(rideReq.passengerID); + return { + name: passengerData?.name, + phoneNumber: passengerData?.phoneNumber, + status: rideReq.status, + pickUp: rideReq.pickUp, + dropOff: rideReq.dropOff + } + }) + const rides = await Promise.all(rideReqs) + return rides; + } else { + console.log("There is no user"); + return []; + } + } catch (error) { + console.error('Error fetching ride requests', error); + } +}; diff --git a/driver/src/utils/fetchUserDetails.ts b/driver/src/utils/fetchUserDetails.ts index 949a4ab..a2d5cb6 100644 --- a/driver/src/utils/fetchUserDetails.ts +++ b/driver/src/utils/fetchUserDetails.ts @@ -2,18 +2,16 @@ import { auth, db } from "@/firebase/firebase_config"; import { DocumentData, collection, getDocs, query, where } from "firebase/firestore"; -export const fetchUserDetails = async () => { - const user = auth.currentUser; - console.log(user) +export const fetchUserDetails = async (uid) => { try { + const user = auth.currentUser; let data = null if (user) { const usersRef = collection(db, "users") - const q = query(usersRef, where("uid", "==", user.uid)) + const q = query(usersRef, where("uid", "==", uid)) const querySnapshot = await getDocs(q); querySnapshot.forEach((doc: DocumentData) => { data = doc.data() - // setDriverData(doc.data()) console.log(doc.id, " => ", doc.data()); }); return data; -- cgit v1.2.3