From f7fb8539d1aa9f6b2797ecce45b81f989d40663c Mon Sep 17 00:00:00 2001 From: asabizanjo Date: Thu, 20 Nov 2025 20:32:35 +0000 Subject: [PATCH] features components --- src/components/details/amenities.tsx | 79 +++++ src/components/details/booking-card.tsx | 72 +++++ src/components/details/description.tsx | 14 + src/components/details/features.tsx | 387 ++---------------------- src/components/details/header.tsx | 39 +++ src/components/details/highlights.tsx | 26 ++ src/components/details/host-info.tsx | 31 ++ src/components/details/image-grid.tsx | 61 ++++ src/components/details/reviews.tsx | 118 ++++++++ src/components/details/types.ts | 40 +++ 10 files changed, 513 insertions(+), 354 deletions(-) create mode 100644 src/components/details/amenities.tsx create mode 100644 src/components/details/booking-card.tsx create mode 100644 src/components/details/description.tsx create mode 100644 src/components/details/header.tsx create mode 100644 src/components/details/highlights.tsx create mode 100644 src/components/details/host-info.tsx create mode 100644 src/components/details/image-grid.tsx create mode 100644 src/components/details/reviews.tsx create mode 100644 src/components/details/types.ts diff --git a/src/components/details/amenities.tsx b/src/components/details/amenities.tsx new file mode 100644 index 0000000..ba51c66 --- /dev/null +++ b/src/components/details/amenities.tsx @@ -0,0 +1,79 @@ +"use client" + +import { useState, useEffect } from "react"; +import { X } from "lucide-react"; +import { Amenity } from "./types"; + +interface AmenitiesProps { + amenities: Amenity[]; +} + +export function Amenities({ amenities }: AmenitiesProps) { + const [showAmenities, setShowAmenities] = useState(false); + + useEffect(() => { + if (showAmenities) { + document.body.style.overflow = 'hidden'; + } else { + document.body.style.overflow = 'unset'; + } + return () => { + document.body.style.overflow = 'unset'; + }; + }, [showAmenities]); + + return ( +
+

What this place offers

+
+ {amenities.slice(0, 6).map((item, idx) => ( +
+ + {item.label} +
+ ))} +
+ + + {/* Amenities Modal */} + {showAmenities && ( +
+
setShowAmenities(false)} /> +
+
+

What this place offers

+ +
+
+
+ {Array.from(new Set(amenities.map(a => a.category))).map(category => ( +
+

{category}

+
+ {amenities.filter(a => a.category === category).map((item, idx) => ( +
+ + {item.label} +
+ ))} +
+
+ ))} +
+
+
+
+ )} +
+ ); +} diff --git a/src/components/details/booking-card.tsx b/src/components/details/booking-card.tsx new file mode 100644 index 0000000..d0fa90f --- /dev/null +++ b/src/components/details/booking-card.tsx @@ -0,0 +1,72 @@ +import { Star } from "lucide-react"; + +interface BookingCardProps { + price: number; + rating: number; + reviewCount: number; +} + +export function BookingCard({ price, rating, reviewCount }: BookingCardProps) { + return ( +
+
+
+
+ ₨{price.toLocaleString()} + / night +
+
+ + {rating} · {reviewCount} reviews +
+
+ +
+
+
+ + +
+
+ + +
+
+
+ + +
+
+ + + +

You won't be charged yet

+ +
+
+ ₨{price.toLocaleString()} x 5 nights + ₨{(price * 5).toLocaleString()} +
+
+ Cleaning fee + ₨5,000 +
+
+ Service fee + ₨8,000 +
+
+ Total + ₨{(price * 5 + 13000).toLocaleString()} +
+
+
+
+ ); +} diff --git a/src/components/details/description.tsx b/src/components/details/description.tsx new file mode 100644 index 0000000..bc72db4 --- /dev/null +++ b/src/components/details/description.tsx @@ -0,0 +1,14 @@ +interface DescriptionProps { + description: string; +} + +export function Description({ description }: DescriptionProps) { + return ( +
+

About this place

+

+ {description} +

+
+ ); +} diff --git a/src/components/details/features.tsx b/src/components/details/features.tsx index 99cbbc8..2c0b4bd 100644 --- a/src/components/details/features.tsx +++ b/src/components/details/features.tsx @@ -1,9 +1,16 @@ "use client" -import { Star, MapPin, Share, Heart, Wifi, Car, Utensils, Wind, Monitor, X, Waves, Dumbbell, Coffee, Briefcase, Droplets, Tv } from "lucide-react"; -import { useState, useEffect } from "react"; +import { Wifi, Car, Utensils, Wind, Monitor, Waves, Dumbbell, Coffee, Briefcase, Droplets, Tv } from "lucide-react"; import localFont from "next/font/local"; -import Image from "next/image"; +import { Header } from "./header"; +import { ImageGrid } from "./image-grid"; +import { HostInfo } from "./host-info"; +import { Highlights } from "./highlights"; +import { Description } from "./description"; +import { Amenities } from "./amenities"; +import { BookingCard } from "./booking-card"; +import { Reviews } from "./reviews"; +import { Property } from "./types"; const figtree = localFont({ src: [ @@ -13,7 +20,7 @@ const figtree = localFont({ ], }) -const property = { +const property: Property = { title: "Luxury Villa with Panoramic Mountain Views", location: "Islamabad, Pakistan", guests: 4, @@ -95,368 +102,40 @@ const property = { } function Features() { - const [showAmenities, setShowAmenities] = useState(false); - const [showReviews, setShowReviews] = useState(false); - - // Prevent body scroll when modal is open - useEffect(() => { - if (showAmenities || showReviews) { - document.body.style.overflow = 'hidden'; - } else { - document.body.style.overflow = 'unset'; - } - return () => { - document.body.style.overflow = 'unset'; - }; - }, [showAmenities, showReviews]); - return (
- {/* Header Section */} -
-
-

{property.title}

-
-
- - - {property.rating} · {property.reviewCount} reviews - - | - - - {property.location} - -
-
- - -
-
-
-
- - {/* Image Grid */} -
-
-
- Main view -
-
-
- View 2 -
-
- View 3 -
-
-
-
- View 4 -
-
- View 5 - -
-
-
-
+
+ {/* Main Content */}
- {/* Left Column */}
- - {/* Host Info & Stats */} -
-
-

Hosted by {property.host.name}

-

- {property.guests} guests · {property.bedrooms} bedrooms · {property.beds} beds · {property.baths} baths -

-
-
- {property.host.name} -
-
- - {/* Highlights */} -
-
-
- -
-
-

Top rated host

-

John has received 5-star ratings from 95% of recent guests.

-
-
-
-
- -
-
-

Great location

-

100% of recent guests gave the location a 5-star rating.

-
-
-
- - {/* Description */} -
-

About this place

-

- {property.description} -

-
- - {/* Amenities */} -
-

What this place offers

-
- {property.amenities.slice(0, 6).map((item, idx) => ( -
- - {item.label} -
- ))} -
- -
- + + + +
{/* Right Column - Sticky Booking Card */} -
-
-
-
- ₨{property.price.toLocaleString()} - / night -
-
- - {property.rating} · {property.reviewCount} reviews -
-
- -
-
-
- - -
-
- - -
-
-
- - -
-
- - - -

You won't be charged yet

- -
-
- ₨{property.price.toLocaleString()} x 5 nights - ₨{(property.price * 5).toLocaleString()} -
-
- Cleaning fee - ₨5,000 -
-
- Service fee - ₨8,000 -
-
- Total - ₨{(property.price * 5 + 13000).toLocaleString()} -
-
-
-
+
- {/* Reviews Section */} -
-
- -

{property.rating} · {property.reviewCount} reviews

-
- -
- {property.reviews.slice(0, 4).map((review) => ( -
-
- {review.user} -
-

{review.user}

-

{review.date}

-
-
-

- "{review.comment}" -

-
- ))} -
- -
- - {/* Amenities Modal */} - {showAmenities && ( -
-
setShowAmenities(false)} /> -
-
-

What this place offers

- -
-
-
- {Array.from(new Set(property.amenities.map(a => a.category))).map(category => ( -
-

{category}

-
- {property.amenities.filter(a => a.category === category).map((item, idx) => ( -
- - {item.label} -
- ))} -
-
- ))} -
-
-
-
- )} - - {/* Reviews Modal */} - {showReviews && ( -
-
setShowReviews(false)} /> -
-
-
- -

{property.rating} · {property.reviewCount} reviews

-
- -
-
-
- {property.reviews.map((review) => ( -
-
- {review.user} -
-

{review.user}

-

{review.date}

-
-
-
- {[...Array(5)].map((_, i) => ( - - ))} -
-

- {review.comment} -

-
- ))} -
-
-
-
- )} +
) } diff --git a/src/components/details/header.tsx b/src/components/details/header.tsx new file mode 100644 index 0000000..7429b05 --- /dev/null +++ b/src/components/details/header.tsx @@ -0,0 +1,39 @@ +import { Star, MapPin, Share, Heart } from "lucide-react"; +import { Property } from "./types"; + +interface HeaderProps { + property: Property; +} + +export function Header({ property }: HeaderProps) { + return ( +
+
+

{property.title}

+
+
+ + + {property.rating} · {property.reviewCount} reviews + + | + + + {property.location} + +
+
+ + +
+
+
+
+ ); +} diff --git a/src/components/details/highlights.tsx b/src/components/details/highlights.tsx new file mode 100644 index 0000000..8fe7051 --- /dev/null +++ b/src/components/details/highlights.tsx @@ -0,0 +1,26 @@ +import { Star, MapPin } from "lucide-react"; + +export function Highlights() { + return ( +
+
+
+ +
+
+

Top rated host

+

John has received 5-star ratings from 95% of recent guests.

+
+
+
+
+ +
+
+

Great location

+

100% of recent guests gave the location a 5-star rating.

+
+
+
+ ); +} diff --git a/src/components/details/host-info.tsx b/src/components/details/host-info.tsx new file mode 100644 index 0000000..d1ac7a9 --- /dev/null +++ b/src/components/details/host-info.tsx @@ -0,0 +1,31 @@ +import Image from "next/image"; +import { Host } from "./types"; + +interface HostInfoProps { + host: Host; + guests: number; + bedrooms: number; + beds: number; + baths: number; +} + +export function HostInfo({ host, guests, bedrooms, beds, baths }: HostInfoProps) { + return ( +
+
+

Hosted by {host.name}

+

+ {guests} guests · {bedrooms} bedrooms · {beds} beds · {baths} baths +

+
+
+ {host.name} +
+
+ ); +} diff --git a/src/components/details/image-grid.tsx b/src/components/details/image-grid.tsx new file mode 100644 index 0000000..bcf64d4 --- /dev/null +++ b/src/components/details/image-grid.tsx @@ -0,0 +1,61 @@ +import Image from "next/image"; + +interface ImageGridProps { + images: string[]; +} + +export function ImageGrid({ images }: ImageGridProps) { + return ( +
+
+
+ Main view +
+
+
+ View 2 +
+
+ View 3 +
+
+
+
+ View 4 +
+
+ View 5 + +
+
+
+
+ ); +} diff --git a/src/components/details/reviews.tsx b/src/components/details/reviews.tsx new file mode 100644 index 0000000..5e1ea3c --- /dev/null +++ b/src/components/details/reviews.tsx @@ -0,0 +1,118 @@ +"use client" + +import { useState, useEffect } from "react"; +import { Star, X } from "lucide-react"; +import Image from "next/image"; +import { Review } from "./types"; + +interface ReviewsProps { + reviews: Review[]; + rating: number; + reviewCount: number; +} + +export function Reviews({ reviews, rating, reviewCount }: ReviewsProps) { + const [showReviews, setShowReviews] = useState(false); + + useEffect(() => { + if (showReviews) { + document.body.style.overflow = 'hidden'; + } else { + document.body.style.overflow = 'unset'; + } + return () => { + document.body.style.overflow = 'unset'; + }; + }, [showReviews]); + + return ( +
+
+ +

{rating} · {reviewCount} reviews

+
+ +
+ {reviews.slice(0, 4).map((review) => ( +
+
+ {review.user} +
+

{review.user}

+

{review.date}

+
+
+

+ "{review.comment}" +

+
+ ))} +
+ + + {/* Reviews Modal */} + {showReviews && ( +
+
setShowReviews(false)} /> +
+
+
+ +

{rating} · {reviewCount} reviews

+
+ +
+
+
+ {reviews.map((review) => ( +
+
+ {review.user} +
+

{review.user}

+

{review.date}

+
+
+
+ {[...Array(5)].map((_, i) => ( + + ))} +
+

+ {review.comment} +

+
+ ))} +
+
+
+
+ )} +
+ ); +} diff --git a/src/components/details/types.ts b/src/components/details/types.ts new file mode 100644 index 0000000..10b4f22 --- /dev/null +++ b/src/components/details/types.ts @@ -0,0 +1,40 @@ +import { LucideIcon } from "lucide-react"; + +export interface Amenity { + icon: LucideIcon; + label: string; + category: string; +} + +export interface Review { + id: number; + user: string; + avatar: string; + date: string; + rating: number; + comment: string; +} + +export interface Host { + name: string; + image: string; + joined: string; + isSuperhost: boolean; +} + +export interface Property { + title: string; + location: string; + guests: number; + bedrooms: number; + beds: number; + baths: number; + rating: number; + reviewCount: number; + price: number; + description: string; + amenities: Amenity[]; + host: Host; + images: string[]; + reviews: Review[]; +}