Skip to content

Commit e63fa06

Browse files
authored
Merge pull request #192 from JoelVR17/feat/hotels-page
Feat/hotels page
2 parents a816ed1 + db11b93 commit e63fa06

File tree

9 files changed

+281
-6
lines changed

9 files changed

+281
-6
lines changed

src/@types/hotel.entity.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
export type Hotel = {
2+
id: number;
3+
name: string;
4+
image: string;
5+
location: string;
6+
stars: number;
7+
price: number;
8+
isFavorite: boolean;
9+
};

src/app/dashboard/hotel/page.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import Hotels from "@/components/hotels/pages/Hotels";
2+
3+
export default function Home() {
4+
return <Hotels />;
5+
}

src/app/dashboard/layout.tsx

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
11
"use client";
22

3-
import { useEffect, useState } from 'react';
3+
import { useEffect, useState } from "react";
44
import { useRouter, usePathname } from "next/navigation";
55
import { useGlobalAuthenticationStore } from "@/core/store/data";
66
import { SideBar } from "@/components/layouts/SideBar";
77
import { Header } from "@/components/layouts/Header";
8-
import type { ReactNode } from 'react';
8+
import type { ReactNode } from "react";
99

1010
// Define public routes that don't require authentication
1111
const PUBLIC_ROUTES = [
12+
"/dashboard/hotel",
1213
"/dashboard/hotel/payment",
1314
"/dashboard/hotel/details",
1415
"/dashboard/hotel/search",
@@ -24,14 +25,14 @@ const Layout = ({ children }: { children: ReactNode }) => {
2425
useEffect(() => {
2526
const checkAuth = async () => {
2627
try {
27-
const isPublicRoute = PUBLIC_ROUTES.some(route => pathname === route);
28-
28+
const isPublicRoute = PUBLIC_ROUTES.some((route) => pathname === route);
29+
2930
// Only redirect if not authenticated and trying to access a protected route
3031
if (!address && !isPublicRoute) {
3132
router.push("/");
3233
setIsAuthError(true);
3334
}
34-
35+
3536
setIsLoading(false);
3637
} catch (error) {
3738
console.error("Authentication error:", error);
@@ -54,7 +55,7 @@ const Layout = ({ children }: { children: ReactNode }) => {
5455
}
5556

5657
// Show error state only for protected routes that failed authentication
57-
if (isAuthError && !PUBLIC_ROUTES.some(route => pathname === route)) {
58+
if (isAuthError && !PUBLIC_ROUTES.some((route) => pathname === route)) {
5859
return null;
5960
}
6061

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
export const hotelsMockData = [
2+
{
3+
id: 1,
4+
name: "Shikara Hotel",
5+
image: "/room1.png",
6+
location: "123 Main Street, Central Area",
7+
stars: 4.5,
8+
price: 40.14,
9+
isFavorite: false,
10+
},
11+
{
12+
id: 2,
13+
name: "Shikara Hotel",
14+
image: "/room1.png",
15+
location: "456 Park Avenue, Downtown",
16+
stars: 4.8,
17+
price: 40.14,
18+
isFavorite: false,
19+
},
20+
{
21+
id: 3,
22+
name: "Shikara Hotel",
23+
image: "/room1.png",
24+
location: "789 Ocean Drive, Beach Area",
25+
stars: 4.2,
26+
price: 40.14,
27+
isFavorite: false,
28+
},
29+
{
30+
id: 4,
31+
name: "Shikara Hotel",
32+
image: "/room1.png",
33+
location: "321 Mountain View, Uptown",
34+
stars: 4.6,
35+
price: 40.14,
36+
isFavorite: false,
37+
},
38+
{
39+
id: 5,
40+
name: "Shikara Hotel",
41+
image: "/room1.png",
42+
location: "654 River Road, Riverside",
43+
stars: 4.3,
44+
price: 40.14,
45+
isFavorite: false,
46+
},
47+
{
48+
id: 6,
49+
name: "Shikara Hotel",
50+
image: "/room1.png",
51+
location: "987 Forest Lane, Woodland",
52+
stars: 4.7,
53+
price: 40.14,
54+
isFavorite: false,
55+
},
56+
];
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import HotelGrid from "./HotelGrid";
2+
import SearchFilters from "./SearchFilters";
3+
4+
export default function HotelBookingContainer() {
5+
return (
6+
<div className="p-6 w-full">
7+
<h1 className="text-2xl font-semibold mb-4">Find hotel to stay</h1>
8+
9+
{/* Search and Filters */}
10+
<SearchFilters />
11+
12+
{/* Hotel Grid */}
13+
<HotelGrid />
14+
</div>
15+
);
16+
}
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
"use client";
2+
3+
import { Heart, MapPin } from "lucide-react";
4+
import { Button } from "@/components/ui/button";
5+
import { Card, CardContent, CardFooter } from "@/components/ui/card";
6+
import { Hotel } from "@/@types/hotel.entity";
7+
8+
interface HotelCardProps {
9+
hotel: Hotel;
10+
onToggleFavorite: (id: number) => void;
11+
}
12+
13+
export default function HotelCard({ hotel, onToggleFavorite }: HotelCardProps) {
14+
return (
15+
<Card className="overflow-hidden">
16+
<div className="relative">
17+
<img
18+
src={hotel.image || "/placeholder.svg"}
19+
alt={hotel.name}
20+
className="w-full h-[150px] object-cover"
21+
/>
22+
<Button
23+
variant="ghost"
24+
size="icon"
25+
className="absolute top-2 right-2 bg-white/80 hover:bg-white rounded-full h-8 w-8"
26+
onClick={() => onToggleFavorite(hotel.id)}
27+
>
28+
<Heart
29+
className={`h-5 w-5 ${hotel.isFavorite ? "fill-red-500 text-red-500" : "text-gray-500"}`}
30+
/>
31+
</Button>
32+
</div>
33+
<CardContent className="p-4">
34+
<h3 className="font-semibold text-lg">{hotel.name}</h3>
35+
<div className="flex items-center text-sm text-gray-500 mt-1">
36+
<MapPin className="h-4 w-4 mr-1" />
37+
<span>{hotel.location}</span>
38+
</div>
39+
<div className="flex items-center mt-1">
40+
<div className="flex">
41+
{Array(Math.floor(hotel.stars))
42+
.fill(0)
43+
.map((_, i) => (
44+
<svg
45+
key={i}
46+
className="w-4 h-4 text-yellow-400"
47+
fill="currentColor"
48+
viewBox="0 0 20 20"
49+
>
50+
<path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118l-2.8-2.034c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z"></path>
51+
</svg>
52+
))}
53+
{hotel.stars % 1 !== 0 && (
54+
<svg
55+
className="w-4 h-4 text-yellow-400"
56+
fill="currentColor"
57+
viewBox="0 0 20 20"
58+
>
59+
<defs>
60+
<linearGradient id="halfStarGradient">
61+
<stop offset="50%" stopColor="currentColor" />
62+
<stop offset="50%" stopColor="#D1D5DB" />
63+
</linearGradient>
64+
</defs>
65+
<path
66+
fill="url(#halfStarGradient)"
67+
d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118l-2.8-2.034c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z"
68+
></path>
69+
</svg>
70+
)}
71+
</div>
72+
<span className="text-sm text-gray-500 ml-1">{hotel.stars}</span>
73+
</div>
74+
</CardContent>
75+
<CardFooter className="p-4 pt-0 flex justify-between items-center">
76+
<div>
77+
<span className="font-bold text-lg">${hotel.price.toFixed(2)}</span>
78+
<span className="text-sm text-gray-500">/night</span>
79+
</div>
80+
</CardFooter>
81+
</Card>
82+
);
83+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
"use client";
2+
3+
import { useState } from "react";
4+
import { Button } from "@/components/ui/button";
5+
import { Hotel } from "@/@types/hotel.entity";
6+
import { hotelsMockData } from "@/components/hotels/mocks/hotel.mock";
7+
import HotelCard from "./HotelCard";
8+
9+
export default function HotelGrid() {
10+
const [hotels, setHotels] = useState<Hotel[]>(hotelsMockData);
11+
12+
const toggleFavorite = (id: number) => {
13+
setHotels(
14+
hotels.map((hotel) =>
15+
hotel.id === id ? { ...hotel, isFavorite: !hotel.isFavorite } : hotel
16+
)
17+
);
18+
};
19+
20+
return (
21+
<>
22+
<div className="flex justify-end mb-4">
23+
<Button variant="link" className="text-blue-500">
24+
View all
25+
</Button>
26+
</div>
27+
28+
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
29+
{hotels.map((hotel) => (
30+
<HotelCard
31+
key={hotel.id}
32+
hotel={hotel}
33+
onToggleFavorite={toggleFavorite}
34+
/>
35+
))}
36+
</div>
37+
</>
38+
);
39+
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
"use client";
2+
3+
import { Calendar, Search } from "lucide-react";
4+
import { Button } from "@/components/ui/button";
5+
import { Input } from "@/components/ui/input";
6+
import {
7+
Select,
8+
SelectContent,
9+
SelectItem,
10+
SelectTrigger,
11+
SelectValue,
12+
} from "@/components/ui/select";
13+
14+
export default function SearchFilters() {
15+
return (
16+
<div className="flex flex-col md:flex-row gap-4 mb-6">
17+
<div className="flex flex-col">
18+
<label className="text-sm text-gray-500 mb-1">Date</label>
19+
<div className="flex items-center">
20+
<Select defaultValue="jul-12-14">
21+
<SelectTrigger className="w-[240px]">
22+
<Calendar className="h-4 w-4 mr-2" />
23+
<SelectValue placeholder="Select date" />
24+
</SelectTrigger>
25+
<SelectContent>
26+
<SelectItem value="jul-12-14">Jul 12 - Jul 14</SelectItem>
27+
<SelectItem value="jul-15-17">Jul 15 - Jul 17</SelectItem>
28+
<SelectItem value="jul-18-20">Jul 18 - Jul 20</SelectItem>
29+
</SelectContent>
30+
</Select>
31+
</div>
32+
</div>
33+
34+
<div className="flex flex-col">
35+
<label className="text-sm text-gray-500 mb-1">Where to</label>
36+
<div className="flex items-center">
37+
<Input
38+
className="w-[240px]"
39+
placeholder="City, place, see points"
40+
defaultValue="City, place, see points"
41+
/>
42+
</div>
43+
</div>
44+
45+
<div className="flex items-end">
46+
<Button className="bg-blue-500 hover:bg-blue-600">
47+
<Search className="h-4 w-4 mr-2" />
48+
Search
49+
</Button>
50+
</div>
51+
</div>
52+
);
53+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import HotelBookingContainer from "../overall/HotelBookingContainer";
2+
3+
const Hotels = () => {
4+
return (
5+
<div className="flex min-h-screen py-10">
6+
<div className="flex-1">
7+
<HotelBookingContainer />
8+
</div>
9+
</div>
10+
);
11+
};
12+
13+
export default Hotels;

0 commit comments

Comments
 (0)