Skip to content

Commit 10a3629

Browse files
SitarassitarassCopilot
authored
My rides UI (#8)
* style: my-rides initial UI * Update front-end/src/utils/myRidesUtils.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update front-end/src/hooks/useIntersectionObserver.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: sitaras <sitaras@thinkdesquared.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
1 parent 0a83f3e commit 10a3629

File tree

19 files changed

+794
-74
lines changed

19 files changed

+794
-74
lines changed

front-end/components.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@
1616
"utils": "@/lib/utils",
1717
"ui": "@/components/ui",
1818
"lib": "@/lib",
19-
"hooks": "@/hooks"
19+
"hooks": "@/hooks",
20+
"mock": "@/mock"
2021
},
2122
"registries": {}
2223
}

front-end/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
"@radix-ui/react-separator": "^1.1.2",
2121
"@radix-ui/react-slot": "^1.2.3",
2222
"@radix-ui/react-switch": "^1.2.6",
23+
"@radix-ui/react-tabs": "^1.1.13",
2324
"@radix-ui/react-tooltip": "^1.1.6",
2425
"@tanstack/react-query": "^5.90.2",
2526
"axios": "^1.7.9",

front-end/src/api-actions/user.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { formatToUTC } from "@/utils/dateUtils";
77

88
export const getUserInfo = async () => {
99
try {
10-
const response = fetcher("/me/user-info");
10+
const response = fetcher("me/user-info");
1111

1212
return response;
1313
} catch (error: any) {
@@ -17,7 +17,7 @@ export const getUserInfo = async () => {
1717

1818
export const getUserProfile = async () => {
1919
try {
20-
const response = fetcher<ProfileResponse>("/me/profile");
20+
const response = fetcher<ProfileResponse>("me/profile");
2121

2222
return response;
2323
} catch (error: any) {
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
"use client";
2+
3+
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
4+
import Typography from "@/components/ui/typography";
5+
import React, { useState } from "react";
6+
import RidesList from "@/components/RidesList/RidesList";
7+
import { Car, User } from "lucide-react";
8+
9+
type TabValue = "passenger" | "driver";
10+
11+
const MyRidesPage = () => {
12+
const [activeTab, setActiveTab] = useState<TabValue>("passenger");
13+
14+
const tabs = [
15+
{
16+
name: "As Passenger",
17+
value: "passenger" as TabValue,
18+
icon: User,
19+
description: "Rides you've booked",
20+
},
21+
{
22+
name: "As Driver",
23+
value: "driver" as TabValue,
24+
icon: Car,
25+
description: "Rides you're offering",
26+
},
27+
];
28+
29+
return (
30+
<section className="flex justify-center w-full py-8 px-4">
31+
<div className="flex flex-col gap-8 max-w-4xl w-full">
32+
<div className="flex flex-col gap-2">
33+
<Typography variant="h2">My Rides</Typography>
34+
<Typography className="text-muted-foreground">
35+
Manage your upcoming and past journeys
36+
</Typography>
37+
</div>
38+
39+
<Tabs
40+
value={activeTab}
41+
onValueChange={(value) => setActiveTab(value as TabValue)}
42+
className="w-full"
43+
>
44+
<TabsList className="w-full p-0 border-b rounded-none bg-transparent h-auto">
45+
{tabs.map((tab) => {
46+
const Icon = tab.icon;
47+
return (
48+
<TabsTrigger
49+
key={tab.value}
50+
value={tab.value}
51+
className="flex-1 rounded-none h-full bg-transparent data-[state=active]:bg-transparent data-[state=active]:shadow-none border-b-2 border-transparent data-[state=active]:border-primary py-4 px-6 gap-2"
52+
>
53+
<Icon className="w-4 h-4" />
54+
<span className="hidden sm:inline">
55+
<Typography className="font-medium">{tab.name}</Typography>
56+
</span>
57+
<span className="sm:hidden">
58+
<Typography className="font-medium">
59+
{tab.value === "passenger" ? "Passenger" : "Driver"}
60+
</Typography>
61+
</span>
62+
</TabsTrigger>
63+
);
64+
})}
65+
</TabsList>
66+
67+
{tabs.map((tab) => (
68+
<TabsContent key={tab.value} value={tab.value} className="mt-6">
69+
<div className="mb-4">
70+
<Typography className="text-sm text-muted-foreground">
71+
{tab.description}
72+
</Typography>
73+
</div>
74+
<RidesList type={tab.value} />
75+
</TabsContent>
76+
))}
77+
</Tabs>
78+
</div>
79+
</section>
80+
);
81+
};
82+
83+
export default MyRidesPage;

front-end/src/app/globals.css

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,8 @@ a {
7070
--accent-foreground: 0 0% 9%;
7171
--destructive: 0 84.2% 60.2%;
7272
--destructive-foreground: 0 0% 98%;
73+
--success: 120 61% 34%;
74+
--success-foreground: 0 0% 98%;
7375
--border: 0 0% 89.8%;
7476
--input: 0 0% 89.8%;
7577
--ring: 0 0% 3.9%;
@@ -97,6 +99,8 @@ a {
9799
--accent-foreground: 0 0% 98%;
98100
--destructive: 0 62.8% 30.6%;
99101
--destructive-foreground: 0 0% 98%;
102+
--success: 120 61% 34%;
103+
--success-foreground: 0 0% 98%;
100104
--border: 0 0% 14.9%;
101105
--input: 0 0% 14.9%;
102106
--ring: 0 0% 83.1%;
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import { Loader2 } from "lucide-react";
2+
import React from "react";
3+
import Typography from "../ui/typography";
4+
import { cn } from "@/lib/utils";
5+
6+
type DivProps = React.HTMLAttributes<HTMLDivElement>;
7+
8+
interface LoadingStateProps {
9+
label?: string;
10+
classname?: string;
11+
divProps?: DivProps;
12+
ref?: React.Ref<HTMLDivElement>;
13+
}
14+
15+
const LoadingState = ({
16+
label = "Loading...",
17+
classname,
18+
divProps,
19+
ref,
20+
}: LoadingStateProps) => {
21+
return (
22+
<div
23+
{...divProps}
24+
ref={ref}
25+
className={cn(
26+
"flex flex-col items-center justify-center py-12 gap-3",
27+
classname
28+
)}
29+
>
30+
<Loader2 className="w-8 h-8 animate-spin text-primary" />
31+
<Typography className="text-muted-foreground">{label}</Typography>
32+
</div>
33+
);
34+
};
35+
36+
export default LoadingState;

front-end/src/components/Navbar/navbar.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import { DropdownMenu, DropdownMenuTrigger } from "../ui/dropdown-menu";
2020
const defaultLinks: NavbarNavItem[] = [
2121
{ href: routes.home, label: "Home" },
2222
{ href: routes.createRoute, label: "Create Route", protected: true },
23+
{ href: routes.myRides, label: "My rides", protected: true },
2324
];
2425

2526
const Navbar = React.forwardRef<HTMLElement, NavbarProps>(
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
import React from "react";
2+
import {
3+
Card,
4+
CardContent,
5+
CardFooter,
6+
CardHeader,
7+
CardTitle,
8+
} from "@/components/ui/card";
9+
import { cn } from "@/lib/utils";
10+
import Typography from "@/components/ui/typography";
11+
import { Separator } from "@/components/ui/separator";
12+
import { Button } from "@/components/ui/button";
13+
import { Badge } from "@/components/ui/badge";
14+
import { ArrowRight, Calendar, MapPin, Star, User, Users } from "lucide-react";
15+
import Link from "next/link";
16+
import type { Ride } from "@/types/ride.types";
17+
import { getStatusVariant, getStatusLabel } from "@/utils/myRidesUtils";
18+
19+
interface RideCardProps {
20+
ride: Ride;
21+
viewType: "passenger" | "driver";
22+
buttonLabel?: string;
23+
className?: string;
24+
}
25+
26+
const RideCard = ({
27+
ride,
28+
viewType,
29+
buttonLabel = "View details",
30+
className,
31+
}: RideCardProps) => {
32+
const cardDateFormatter = new Intl.DateTimeFormat("en-GB", {
33+
dateStyle: "medium",
34+
timeStyle: "short",
35+
});
36+
37+
const statusVariant = getStatusVariant(ride.status);
38+
const isCompleted = ride.status === "completed";
39+
const isCancelled = ride.status === "cancelled" || ride.status === "rejected";
40+
41+
return (
42+
<Card
43+
className={cn(
44+
"shadow-sm hover:shadow-lg transition-all duration-300 border-l-4",
45+
isCompleted
46+
? "border-l-green-500/50 hover:border-l-green-500"
47+
: isCancelled
48+
? "border-l-red-500/50 hover:border-l-red-500"
49+
: "border-l-primary/20 hover:border-l-primary",
50+
className
51+
)}
52+
>
53+
<CardHeader className="pb-6">
54+
<div className="flex items-start justify-between gap-3 flex-wrap">
55+
<CardTitle className="flex items-center gap-3 text-xl">
56+
<div className="p-2 bg-primary/10 rounded-full">
57+
<MapPin className="w-4 h-4 text-primary" />
58+
</div>
59+
<span className="flex items-center gap-2 flex-wrap">
60+
<Typography className="font-bold">{ride.origin}</Typography>
61+
<ArrowRight className="w-4 h-4 text-muted-foreground flex-shrink-0" />
62+
<Typography className="font-bold">{ride.destination}</Typography>
63+
</span>
64+
</CardTitle>
65+
<Badge variant={statusVariant} className="px-3 py-1 shrink-0">
66+
{getStatusLabel(ride.status)}
67+
</Badge>
68+
</div>
69+
</CardHeader>
70+
71+
<CardContent className="space-y-4 pb-4">
72+
<div className="flex flex-col gap-3">
73+
<div className="flex items-center gap-2 text-sm">
74+
<Calendar className="w-4 h-4 text-primary flex-shrink-0" />
75+
<Typography className="font-medium">
76+
{cardDateFormatter.format(ride.departureDate)}
77+
</Typography>
78+
</div>
79+
{viewType === "passenger" && ride.driver && (
80+
<div className="flex items-center gap-2 rounded-full">
81+
<User className="w-3.5 h-3.5 text-primary" />
82+
<Typography className="font-medium">
83+
{ride.driver.name}
84+
</Typography>
85+
{ride.driver.rating && (
86+
<div className="flex items-center gap-1 ml-1">
87+
<Star className="w-3.5 h-3.5 fill-yellow-400 text-yellow-400" />
88+
<Typography className="font-semibold text-xs">
89+
{ride.driver.rating}
90+
</Typography>
91+
</div>
92+
)}
93+
</div>
94+
)}
95+
{viewType === "driver" &&
96+
ride.passengers &&
97+
ride.passengers.length > 0 && (
98+
<div className="flex items-center gap-2 pt-2">
99+
<Users className="w-4 h-4 text-primary flex-shrink-0" />
100+
<Typography className="text-sm text-muted-foreground">
101+
{ride.passengers.length} passenger
102+
{ride.passengers.length !== 1 ? "s" : ""} booked
103+
</Typography>
104+
</div>
105+
)}
106+
</div>
107+
</CardContent>
108+
109+
<Separator />
110+
111+
<CardFooter className="flex justify-between items-center pt-4">
112+
<div className="flex flex-col gap-1">
113+
<Typography className="text-2xl font-bold text-primary">
114+
{ride.pricePerSeat?.toFixed(2)}
115+
</Typography>
116+
<Typography className="text-xs text-muted-foreground">
117+
per seat
118+
</Typography>
119+
</div>
120+
<Link href={`/my-rides/${ride.id}`}>
121+
<Button size="default" className="font-semibold" tabIndex={-1}>
122+
{buttonLabel}
123+
</Button>
124+
</Link>
125+
</CardFooter>
126+
</Card>
127+
);
128+
};
129+
130+
export default RideCard;

0 commit comments

Comments
 (0)