Skip to content

Commit

Permalink
Merge pull request #177 from Josue19-08/feat/hotel-details-page
Browse files Browse the repository at this point in the history
feat(details): implement details page
  • Loading branch information
zleypner authored Feb 27, 2025
2 parents 22b5a2f + fe5a62d commit 920e03c
Show file tree
Hide file tree
Showing 10 changed files with 277 additions and 1 deletion.
51 changes: 51 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Binary file added public/img/room1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/img/room2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
62 changes: 62 additions & 0 deletions src/app/dashboard/hotel/details/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
'use client';
import { useState } from 'react';
import Header from '@/components/layouts/Header';
import { SideBar } from '@/components/layouts/SideBar';
import Gallery from '@/components/hotels/details/Gallery';
import Information from '@/components/hotels/details/Information';
import Details from '@/components/hotels/details/Details';
import HotelMap from '@/components/hotels/payment/Map';

export default function HotelPage() {
const images = [
"/img/room1.png",
"/img/room2.png",
"/img/room2.png",
"/img/room2.png",
];

const coordinates: [number, number] = [9.9333, -84.0833];

return (
<div className="bg-gray-100 min-h-screen dark:bg-dark-background text-black dark:text-white text-sm">
<Header />
<div className="flex flex-col lg:flex-row mt-8">
<SideBar className="hidden md:block" notificationCount={2} />
<div className="flex-grow p-4 flex flex-col items-center gap-6">

<div className="w-full md:w-2/3">
<Gallery images={images} />
</div>

<div className="w-full md:w-2/3 flex flex-wrap">
<div className="w-full md:w-3/4 lg:w-3/4">
<Information
name="Shikara Hotel"
location="329 Calle Santos, Paseo Colón, San José, Costa Rica"
price="$40.18"
/>
</div>
<div className="hidden md:block md:w-1/4 lg:w-1/4"></div>
</div>

<div className="w-full md:w-2/3 flex flex-wrap gap-4">
<div className="w-full md:w-3/4 lg:w-3/4 flex gap-4">
<div className="w-full md:w-1/2 min-h-[250px]">
<Details
beds={2}
baths={1}
description="Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book."
/>
</div>
<div className="w-full md:w-1/2 min-h-[250px]">
<HotelMap coordinates={coordinates} hotelName="Shikara Hotel" />
</div>
</div>
<div className="hidden md:block md:w-1/4 lg:w-1/4"></div>
</div>

</div>
</div>
</div>
);
}
3 changes: 2 additions & 1 deletion src/app/dashboard/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ import { Header } from "@/components/layouts/Header";

// Define public routes that don't require authentication
const PUBLIC_ROUTES = [
"/dashboard/hotel/payment"
"/dashboard/hotel/payment",
"/dashboard/hotel/details"
];

const Layout = ({ children }: { children: React.ReactNode }) => {
Expand Down
36 changes: 36 additions & 0 deletions src/components/hotels/details/Details.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { Card } from "@/components/ui/card";
import { Bed, Bath } from "lucide-react";

interface DetailsProps {
beds: number;
baths: number;
description: string;
}

export default function Details({ beds, baths, description }: DetailsProps) {
return (
<Card className="flex flex-col justify-between min-h-[200px] sm:min-h-[250px] md:min-h-[300px] p-4 sm:p-6 md:p-8">
<div className="flex flex-wrap items-start gap-6 sm:gap-8">
<div className="flex items-center gap-2 sm:gap-3">
<div className="p-3 sm:p-4 bg-blue-100 dark:bg-blue-700 rounded-full">
<Bed className="w-5 sm:w-6 md:w-7 h-5 sm:h-6 md:h-7 text-blue-600 dark:text-blue-300" />
</div>
<span className="text-base sm:text-lg md:text-xl font-medium">{beds} bd.</span>
</div>
<div className="flex items-center gap-2 sm:gap-3">
<div className="p-3 sm:p-4 bg-blue-100 dark:bg-blue-700 rounded-full">
<Bath className="w-5 sm:w-6 md:w-7 h-5 sm:h-6 md:h-7 text-blue-600 dark:text-blue-300" />
</div>
<span className="text-base sm:text-lg md:text-xl font-medium">{baths} ba.</span>
</div>
</div>

<div className="mt-auto">
<h2 className="text-lg sm:text-xl md:text-2xl font-semibold">Hotel details</h2>
<p className="text-gray-600 dark:text-gray-300 text-xs sm:text-sm md:text-base">
{description}
</p>
</div>
</Card>
);
}
26 changes: 26 additions & 0 deletions src/components/hotels/details/Gallery.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
"use client";

import { useState } from "react";
import Image from "@/components/ui/image";
import VerticalCarousel from "@/components/hotels/details/VerticalCarousel";

interface GalleryProps {
images: string[];
}

export default function Gallery({ images }: GalleryProps) {
const [selectedImage, setSelectedImage] = useState(images[0]);

return (
<div className="flex flex-col md:flex-row gap-4">

<div className="w-full md:w-3/4">
<Image src={selectedImage} alt="Selected Hotel Image" className="w-full h-auto rounded-lg" />
</div>

<div className="w-full md:w-1/4">
<VerticalCarousel images={images} onSelect={setSelectedImage} selectedImage={selectedImage} />
</div>
</div>
);
}
28 changes: 28 additions & 0 deletions src/components/hotels/details/Information.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { Card } from "@/components/ui/card";
import { MapPin } from "lucide-react";

interface InformationProps {
name: string;
location: string;
price: string;
}

export default function Information({ name, location, price }: InformationProps) {
return (
<Card className="flex flex-col gap-4 p-4 md:p-6 lg:p-8">
<div className="flex flex-wrap justify-between items-start gap-2">
<h1 className="text-2xl sm:text-3xl md:text-4xl font-bold">{name}</h1>
<span className="bg-blue-600 text-white px-4 md:px-6 py-2 rounded-lg font-semibold text-base sm:text-lg md:text-xl">
{price} / night
</span>
</div>

<div className="flex items-center gap-3 sm:gap-4 text-gray-600 dark:text-gray-300 text-base sm:text-lg md:text-xl">
<div className="w-10 sm:w-12 h-10 sm:h-12 flex items-center justify-center bg-blue-200 dark:bg-blue-700 rounded-full">
<MapPin className="w-6 sm:w-7 h-6 sm:h-7 text-blue-600 dark:text-blue-300" />
</div>
<span className="text-sm sm:text-base md:text-lg">{location}</span>
</div>
</Card>
);
}
58 changes: 58 additions & 0 deletions src/components/hotels/details/VerticalCarousel.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { useState } from "react";
import Image from "@/components/ui/image";

interface VerticalCarouselProps {
images: string[];
onSelect: (src: string) => void;
selectedImage: string;
}

export default function VerticalCarousel({ images, onSelect, selectedImage }: VerticalCarouselProps) {
const [startIndex, setStartIndex] = useState(0);
const visibleImages = 3;
const maxIndex = images.length - visibleImages;

const scrollUp = () => {
if (startIndex > 0) setStartIndex(startIndex - 1);
};

const scrollDown = () => {
if (startIndex < maxIndex) setStartIndex(startIndex + 1);
};

return (
<div className="relative flex flex-col h-full w-full">
{images.length > visibleImages && (
<button
onClick={scrollUp}
className="absolute -top-6 left-1/2 transform -translate-x-1/2 p-2 bg-gray-200 rounded-full hover:bg-gray-300 text-sm disabled:opacity-50"
disabled={startIndex === 0}
>
</button>
)}

<div className="flex flex-col gap-2 h-full">
{images.slice(startIndex, startIndex + visibleImages).map((img, index) => (
<button
key={index}
onClick={() => onSelect(img)}
className="w-full h-1/3 rounded-lg overflow-hidden transition"
>
<Image src={img} alt={`Thumbnail ${index}`} className="w-full h-full object-cover" />
</button>
))}
</div>

{images.length > visibleImages && (
<button
onClick={scrollDown}
className="absolute -bottom-6 left-1/2 transform -translate-x-1/2 p-2 bg-gray-200 rounded-full hover:bg-gray-300 text-sm disabled:opacity-50"
disabled={startIndex >= maxIndex}
>
</button>
)}
</div>
);
}
14 changes: 14 additions & 0 deletions src/components/ui/image.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
interface ImageProps {
src: string;
alt?: string;
className?: string;
}

export default function Image({ src, alt = "Image", className }: ImageProps) {
return (
<div className={`w-full h-auto rounded-lg overflow-hidden ${className}`}>
<img src={src} alt={alt} className="w-full h-full object-cover" />
</div>
);
}

0 comments on commit 920e03c

Please sign in to comment.