Browse Source
feat : age label added to passengers
feat : age label added to passengers
-API is not connected feat : bills page UI addedmain
sina_sajjadi
3 weeks ago
62 changed files with 783 additions and 6747 deletions
-
2next.config.js
-
1src/app/(account-pages)/(components)/Nav.tsx
-
68src/app/(account-pages)/bills/BillCard.tsx
-
113src/app/(account-pages)/bills/[slug]/page.tsx
-
106src/app/(account-pages)/bills/page.tsx
-
48src/app/(car-listings)/SectionGridFilterCard.tsx
-
112src/app/(car-listings)/SectionGridHasMap.tsx
-
683src/app/(car-listings)/TabFilters.tsx
-
59src/app/(car-listings)/layout.tsx
-
14src/app/(car-listings)/listing-car-map/page.tsx
-
14src/app/(car-listings)/listing-car/page.tsx
-
1src/app/(client-components)/(Header)/SiteHeader.tsx
-
134src/app/(client-components)/(HeroSearchForm)/(car-search-form)/RentalCarDatesRangeInput.tsx
-
70src/app/(client-components)/(HeroSearchForm)/(car-search-form)/RentalCarSearchForm.tsx
-
119src/app/(client-components)/(HeroSearchForm)/(experiences-search-form)/ExperiencesDateSingleInput.tsx
-
27src/app/(client-components)/(HeroSearchForm)/(experiences-search-form)/ExperiencesSearchForm.tsx
-
152src/app/(client-components)/(HeroSearchForm)/(flight-search-form)/FlightDateRangeInput.tsx
-
246src/app/(client-components)/(HeroSearchForm)/(flight-search-form)/FlightSearchForm.tsx
-
66src/app/(client-components)/(HeroSearchForm)/(real-estate-search-form)/HeroRealEstateSearchForm.tsx
-
137src/app/(client-components)/(HeroSearchForm)/(real-estate-search-form)/PriceRangeInput.tsx
-
126src/app/(client-components)/(HeroSearchForm)/(real-estate-search-form)/PropertyTypeSelect.tsx
-
25src/app/(client-components)/(HeroSearchForm)/(real-estate-search-form)/RealEstateSearchForm.tsx
-
69src/app/(client-components)/(HeroSearchForm)/GuestsInput.tsx
-
129src/app/(client-components)/(HeroSearchFormSmall)/(car-search-form)/RentalCarDatesRangeInput.tsx
-
72src/app/(client-components)/(HeroSearchFormSmall)/(car-search-form)/RentalCarSearchForm.tsx
-
114src/app/(client-components)/(HeroSearchFormSmall)/(experiences-search-form)/ExperiencesDateSingleInput.tsx
-
29src/app/(client-components)/(HeroSearchFormSmall)/(experiences-search-form)/ExperiencesSearchForm.tsx
-
148src/app/(client-components)/(HeroSearchFormSmall)/(flight-search-form)/FlightDateRangeInput.tsx
-
247src/app/(client-components)/(HeroSearchFormSmall)/(flight-search-form)/FlightSearchForm.tsx
-
51src/app/(experience-listings)/SectionGridFilterCard.tsx
-
113src/app/(experience-listings)/SectionGridHasMap.tsx
-
538src/app/(experience-listings)/TabFilters.tsx
-
56src/app/(experience-listings)/layout.tsx
-
14src/app/(experience-listings)/listing-experiences-map/page.tsx
-
14src/app/(experience-listings)/listing-experiences/page.tsx
-
195src/app/(home)/home-2/page.tsx
-
104src/app/(home)/home-3/page.tsx
-
55src/app/(listing-detail)/SectionDateRange.tsx
-
77src/app/(listing-detail)/layout.tsx
-
118src/app/(listing-detail)/listing-car-detail/RentalCarDatesRangeInput.tsx
-
68src/app/(listing-detail)/listing-car-detail/constant.ts
-
542src/app/(listing-detail)/listing-car-detail/page.tsx
-
122src/app/(listing-detail)/listing-experiences-detail/GuestsInput.tsx
-
109src/app/(listing-detail)/listing-experiences-detail/StayDatesRangeInput.tsx
-
39src/app/(listing-detail)/listing-experiences-detail/constant.ts
-
505src/app/(listing-detail)/listing-experiences-detail/page.tsx
-
122src/app/(listing-detail)/listing-stay-detail/GuestsInput.tsx
-
109src/app/(listing-detail)/listing-stay-detail/StayDatesRangeInput.tsx
-
71src/app/(listing-detail)/listing-stay-detail/constant.ts
-
637src/app/(listing-detail)/listing-stay-detail/page.tsx
-
4src/app/(real-estate-listings)/layout.tsx
-
45src/app/(server-components)/SectionHero2ArchivePage.tsx
-
171src/app/add-listing/[[...stepIndex]]/PageAddListing1.tsx
-
62src/app/add-listing/[[...stepIndex]]/page.tsx
-
2src/app/blog/SectionMagazine5.tsx
-
79src/app/custom-trip/page.tsx
-
89src/app/tours/[slug]/GuestsInput.tsx
-
8src/app/tours/[slug]/page.tsx
-
25src/app/tours/layout.tsx
-
57src/components/MobileFooterSticky.tsx
-
0src/components/ModalReserveMobile.tsx
-
26src/components/contexts/tourDetails.tsx
@ -0,0 +1,68 @@ |
|||||
|
// BillCard.tsx
|
||||
|
import React from "react"; |
||||
|
import ButtonPrimary from "@/shared/ButtonPrimary"; |
||||
|
export type BillStatus = "Awaiting Payment" | "Approved" | "Rejected" | "Pending"; |
||||
|
import { BiSolidPlaneAlt } from "react-icons/bi"; |
||||
|
import { FaSimCard } from "react-icons/fa"; |
||||
|
import { BsCart3 } from "react-icons/bs"; |
||||
|
import { MdCurrencyExchange } from "react-icons/md"; |
||||
|
export interface Bill { |
||||
|
title: string; |
||||
|
issuedDate: string; |
||||
|
expirationDate: string; |
||||
|
amount: number; |
||||
|
status: BillStatus; |
||||
|
} |
||||
|
|
||||
|
|
||||
|
interface BillCardProps { |
||||
|
bill: Bill; |
||||
|
onViewDetail: (bill: Bill) => void; // Add onViewDetail prop
|
||||
|
} |
||||
|
|
||||
|
const statusStyles: { [key in BillStatus]: string } = { |
||||
|
"Awaiting Payment": "bg-yellow-200 text-yellow-700", |
||||
|
Approved: "bg-green-200 text-green-700", |
||||
|
Rejected: "bg-red-200 text-red-700", |
||||
|
Pending: "bg-blue-200 text-blue-700", |
||||
|
}; |
||||
|
|
||||
|
const BillCard: React.FC<BillCardProps> = ({ bill, onViewDetail }) => { |
||||
|
return ( |
||||
|
<div className="bg-white shadow-md rounded-lg p-4 mb-4"> |
||||
|
<div className="flex items-center justify-between"> |
||||
|
<h2 className="text-lg font-semibold flex items-center space-x-2"> |
||||
|
{bill.title.includes("Tour") && <span><BiSolidPlaneAlt className="text-bronze" size={25} /></span>} |
||||
|
{bill.title.includes("SIM Card") && <span><FaSimCard className="text-bronze" size={25} /></span>} |
||||
|
{bill.title.includes("Shop") && <span><BsCart3 className="text-bronze" size={25} /></span>} |
||||
|
{bill.title.includes("Tasrif") && <span><MdCurrencyExchange className="text-bronze" size={25} /></span>} |
||||
|
<span>{bill.title}</span> |
||||
|
</h2> |
||||
|
<span className={`px-2 py-1 text-sm rounded-full ${statusStyles[bill.status]}`}> |
||||
|
{bill.status} |
||||
|
</span> |
||||
|
</div> |
||||
|
|
||||
|
<div className="mt-4 text-sm text-gray-600"> |
||||
|
<div className="flex justify-between mb-2"> |
||||
|
<span>Issued Date:</span> |
||||
|
<span>{bill.issuedDate}</span> |
||||
|
</div> |
||||
|
<div className="flex justify-between mb-2"> |
||||
|
<span>Expiration Date:</span> |
||||
|
<span>{bill.expirationDate}</span> |
||||
|
</div> |
||||
|
<div className="flex justify-between mt-2 font-semibold"> |
||||
|
<span>Tour Invoice Amount:</span> |
||||
|
<span className="text-orange-600">${bill.amount.toFixed(2)}</span> |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
<ButtonPrimary onClick={() => onViewDetail(bill)} className="mt-4"> |
||||
|
View Bill |
||||
|
</ButtonPrimary> |
||||
|
</div> |
||||
|
); |
||||
|
}; |
||||
|
|
||||
|
export default BillCard; |
@ -0,0 +1,113 @@ |
|||||
|
// BillDetailCard.tsx
|
||||
|
import React from "react"; |
||||
|
import ButtonPrimary from "@/shared/ButtonPrimary"; |
||||
|
import Button from "@/shared/Button"; |
||||
|
|
||||
|
|
||||
|
export type BillStatus = "Awaiting Payment" | "Approved" | "Rejected" | "Pending"; |
||||
|
|
||||
|
export interface Bill { |
||||
|
title: string; |
||||
|
issuedDate: string; |
||||
|
expirationDate: string; |
||||
|
amount: number; |
||||
|
status: BillStatus; |
||||
|
passengers: { type: string; count: number }[]; |
||||
|
accountNumber: string; |
||||
|
message?: string; |
||||
|
uploadedImage?: { |
||||
|
name: string; |
||||
|
size: string; |
||||
|
src: string; |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
|
||||
|
|
||||
|
interface BillDetailCardProps { |
||||
|
bill: Bill; |
||||
|
} |
||||
|
|
||||
|
const statusStyles: { [key in BillStatus]: string } = { |
||||
|
"Awaiting Payment": "bg-yellow-200 text-yellow-700", |
||||
|
Approved: "bg-green-200 text-green-700", |
||||
|
Rejected: "bg-red-200 text-red-700", |
||||
|
Pending: "bg-blue-200 text-blue-700", |
||||
|
}; |
||||
|
|
||||
|
const BillDetailCard: React.FC<BillDetailCardProps> = ({ bill }) => { |
||||
|
return ( |
||||
|
<div className="bg-white shadow-md rounded-lg p-4 mb-4"> |
||||
|
<div className="flex items-center justify-between"> |
||||
|
<h2 className="text-lg font-semibold">{bill.title}</h2> |
||||
|
<span className={`px-2 py-1 text-sm rounded-full ${statusStyles[bill.status]}`}> |
||||
|
{bill.status} |
||||
|
</span> |
||||
|
</div> |
||||
|
|
||||
|
{bill.message && ( |
||||
|
<div className="bg-red-100 text-red-700 p-3 mt-4 rounded-md"> |
||||
|
<h3 className="font-semibold">Why was it rejected?</h3> |
||||
|
<p className="text-sm mt-1">{bill.message}</p> |
||||
|
</div> |
||||
|
)} |
||||
|
|
||||
|
<div className="mt-4 text-sm text-gray-600"> |
||||
|
<div className="flex justify-between mb-2"> |
||||
|
<span>Issued Date:</span> |
||||
|
<span>{bill.issuedDate}</span> |
||||
|
</div> |
||||
|
<div className="flex justify-between mb-2"> |
||||
|
<span>Expiration Date:</span> |
||||
|
<span>{bill.expirationDate}</span> |
||||
|
</div> |
||||
|
|
||||
|
<div className="mt-4"> |
||||
|
<h3 className="font-semibold">Number of Passengers</h3> |
||||
|
{bill.passengers.map((p, index) => ( |
||||
|
<div key={index} className="flex justify-between"> |
||||
|
<span>#{p.count} ({p.type})</span> |
||||
|
</div> |
||||
|
))} |
||||
|
</div> |
||||
|
|
||||
|
<div className="flex justify-between mt-4 font-semibold"> |
||||
|
<span>Tour Price:</span> |
||||
|
<span className="text-orange-600">${bill.amount}</span> |
||||
|
</div> |
||||
|
|
||||
|
<div className="mt-4"> |
||||
|
<h3 className="font-semibold">Account Number</h3> |
||||
|
<div className="flex items-center bg-gray-100 p-2 rounded-md mt-2"> |
||||
|
<span className="flex-grow">{bill.accountNumber}</span> |
||||
|
<Button className="text-blue-500">Copy</Button> |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
{bill.uploadedImage ? ( |
||||
|
<div className="mt-4"> |
||||
|
<h3 className="font-semibold">Uploaded Image</h3> |
||||
|
<img src={bill.uploadedImage.src} alt="Uploaded" className="mt-2 rounded-md" /> |
||||
|
<div className="flex justify-between mt-2"> |
||||
|
<span>{bill.uploadedImage.name}</span> |
||||
|
<span className="text-gray-500 text-sm">{bill.uploadedImage.size}</span> |
||||
|
<Button className="text-red-500">Delete</Button> |
||||
|
</div> |
||||
|
</div> |
||||
|
) : ( |
||||
|
<div className="mt-4"> |
||||
|
<label className="block font-semibold mb-2">Upload Passport Image</label> |
||||
|
<div className="flex items-center bg-gray-100 p-2 rounded-md"> |
||||
|
<Button className="bg-orange-500 text-white">Upload</Button> |
||||
|
<span className="flex-grow text-gray-500 text-sm ml-4">No File Selected</span> |
||||
|
</div> |
||||
|
</div> |
||||
|
)} |
||||
|
</div> |
||||
|
|
||||
|
<ButtonPrimary className="w-full mt-6">Submit</ButtonPrimary> |
||||
|
</div> |
||||
|
); |
||||
|
}; |
||||
|
|
||||
|
export default BillDetailCard; |
@ -0,0 +1,106 @@ |
|||||
|
// BillsPage.tsx
|
||||
|
"use client" |
||||
|
import React, { useState } from "react"; |
||||
|
import BillCard from "./BillCard"; |
||||
|
import BillDetailCard from "./[slug]/page"; |
||||
|
|
||||
|
|
||||
|
// types.ts
|
||||
|
export type BillStatus = "Awaiting Payment" | "Approved" | "Rejected" | "Pending"; |
||||
|
|
||||
|
export interface Bill { |
||||
|
title: string; |
||||
|
issuedDate: string; |
||||
|
expirationDate: string; |
||||
|
amount: number; |
||||
|
status: BillStatus; |
||||
|
passengers : {} |
||||
|
accountNumber: string, |
||||
|
message? : string |
||||
|
|
||||
|
} |
||||
|
|
||||
|
|
||||
|
const bills: Bill[] = [ |
||||
|
{ |
||||
|
title: "Karbala Tour Bill", |
||||
|
issuedDate: "12 Jan 2023", |
||||
|
expirationDate: "10 Jan 2023", |
||||
|
amount: 960, |
||||
|
status: "Awaiting Payment", |
||||
|
passengers: [ |
||||
|
{ type: "Adult", count: 3 }, |
||||
|
{ type: "Child", count: 2 }, |
||||
|
{ type: "Infant", count: 1 }, |
||||
|
], |
||||
|
accountNumber: "123-456-7890-0123", |
||||
|
}, |
||||
|
{ |
||||
|
title: "SIM Card Bill", |
||||
|
issuedDate: "12 Jan 2023", |
||||
|
expirationDate: "10 Jan 2023", |
||||
|
amount: 960, |
||||
|
status: "Approved", |
||||
|
passengers: [ |
||||
|
{ type: "Adult", count: 2 }, |
||||
|
{ type: "Child", count: 1 }, |
||||
|
], |
||||
|
accountNumber: "987-654-3210-0123", |
||||
|
}, |
||||
|
{ |
||||
|
title: "Shop Bill", |
||||
|
issuedDate: "12 Jan 2023", |
||||
|
expirationDate: "10 Jan 2023", |
||||
|
amount: 960, |
||||
|
status: "Rejected", |
||||
|
passengers: [ |
||||
|
{ type: "Adult", count: 1 }, |
||||
|
], |
||||
|
accountNumber: "456-789-0123-4567", |
||||
|
message: |
||||
|
"The uploaded image does not meet the required quality standards. Please use a higher-resolution photo.", |
||||
|
}, |
||||
|
{ |
||||
|
title: "Tasrif Bill", |
||||
|
issuedDate: "12 Jan 2023", |
||||
|
expirationDate: "10 Jan 2023", |
||||
|
amount: 960, |
||||
|
status: "Pending", |
||||
|
passengers: [ |
||||
|
{ type: "Adult", count: 3 }, |
||||
|
{ type: "Child", count: 2 }, |
||||
|
], |
||||
|
accountNumber: "321-654-9870-1234", |
||||
|
}, |
||||
|
]; |
||||
|
|
||||
|
const BillsPage: React.FC = () => { |
||||
|
const [selectedBill, setSelectedBill] = useState<Bill | null>(null); |
||||
|
|
||||
|
const handleViewDetail = (bill: Bill) => { |
||||
|
setSelectedBill(bill); |
||||
|
}; |
||||
|
|
||||
|
const handleBackToList = () => { |
||||
|
setSelectedBill(null); |
||||
|
}; |
||||
|
|
||||
|
return ( |
||||
|
<div className="p-4 bg-gray-100 min-h-screen"> |
||||
|
{selectedBill ? ( |
||||
|
<div> |
||||
|
<button onClick={handleBackToList} className="text-blue-500 mb-4"> |
||||
|
← Back to Bills |
||||
|
</button> |
||||
|
<BillDetailCard bill={selectedBill} /> |
||||
|
</div> |
||||
|
) : ( |
||||
|
bills.map((bill, index) => ( |
||||
|
<BillCard key={index} bill={bill} onViewDetail={handleViewDetail} /> |
||||
|
)) |
||||
|
)} |
||||
|
</div> |
||||
|
); |
||||
|
}; |
||||
|
|
||||
|
export default BillsPage; |
@ -1,48 +0,0 @@ |
|||||
import React, { FC } from "react"; |
|
||||
import { DEMO_CAR_LISTINGS } from "@/data/listings"; |
|
||||
import { CarDataType } from "@/data/types"; |
|
||||
import Pagination from "@/shared/Pagination"; |
|
||||
import TabFilters from "./TabFilters"; |
|
||||
import Heading2 from "@/shared/Heading2"; |
|
||||
import CarCard from "@/components/CarCard"; |
|
||||
|
|
||||
export interface SectionGridFilterCardProps { |
|
||||
className?: string; |
|
||||
data?: CarDataType[]; |
|
||||
} |
|
||||
|
|
||||
const DEMO_DATA: CarDataType[] = DEMO_CAR_LISTINGS.filter((_, i) => i < 12); |
|
||||
|
|
||||
const SectionGridFilterCard: FC<SectionGridFilterCardProps> = ({ |
|
||||
className = "", |
|
||||
data = DEMO_DATA, |
|
||||
}) => { |
|
||||
return ( |
|
||||
<div className={`nc-SectionGridFilterCard ${className}`}> |
|
||||
<Heading2 |
|
||||
heading="Cars in Tokyo" |
|
||||
subHeading={ |
|
||||
<span className="block text-neutral-500 dark:text-neutral-400 mt-3"> |
|
||||
233 cars |
|
||||
<span className="mx-2">·</span> |
|
||||
Aug 12 - 18 |
|
||||
</span> |
|
||||
} |
|
||||
/> |
|
||||
|
|
||||
<div className="mb-8 lg:mb-11"> |
|
||||
<TabFilters /> |
|
||||
</div> |
|
||||
<div className="grid grid-cols-1 gap-6 md:gap-8 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4"> |
|
||||
{data.map((car) => ( |
|
||||
<CarCard key={car.id} data={car} /> |
|
||||
))} |
|
||||
</div> |
|
||||
<div className="flex mt-16 justify-center items-center"> |
|
||||
<Pagination /> |
|
||||
</div> |
|
||||
</div> |
|
||||
); |
|
||||
}; |
|
||||
|
|
||||
export default SectionGridFilterCard; |
|
@ -1,112 +0,0 @@ |
|||||
"use client"; |
|
||||
|
|
||||
import React, { FC, useState } from "react"; |
|
||||
import GoogleMapReact from "google-map-react"; |
|
||||
import { DEMO_CAR_LISTINGS } from "@/data/listings"; |
|
||||
import ButtonClose from "@/shared/ButtonClose"; |
|
||||
import Checkbox from "@/shared/Checkbox"; |
|
||||
import Pagination from "@/shared/Pagination"; |
|
||||
import TabFilters from "./TabFilters"; |
|
||||
import Heading2 from "@/shared/Heading2"; |
|
||||
import CarCardH from "@/components/CarCardH"; |
|
||||
import AnyReactComponent from "@/components/AnyReactComponent/AnyReactComponent"; |
|
||||
|
|
||||
const DEMO_CARS = DEMO_CAR_LISTINGS.filter((_, i) => i < 12); |
|
||||
|
|
||||
export interface SectionGridHasMapProps {} |
|
||||
|
|
||||
const SectionGridHasMap: FC<SectionGridHasMapProps> = () => { |
|
||||
const [currentHoverID, setCurrentHoverID] = useState<string | number>(-1); |
|
||||
const [showFullMapFixed, setShowFullMapFixed] = useState(false); |
|
||||
|
|
||||
return ( |
|
||||
<div> |
|
||||
<div className="relative flex min-h-screen"> |
|
||||
{/* CARDSSSS */} |
|
||||
<div className="min-h-screen w-full xl:w-[780px] 2xl:w-[880px] flex-shrink-0 xl:px-8 "> |
|
||||
<Heading2 |
|
||||
heading="Cars in Tokyo" |
|
||||
subHeading={ |
|
||||
<span className="block text-neutral-500 dark:text-neutral-400 mt-3"> |
|
||||
233 cars |
|
||||
<span className="mx-2">·</span> |
|
||||
Aug 12 - 18 |
|
||||
</span> |
|
||||
} |
|
||||
/> |
|
||||
<div className="mb-8 lg:mb-11"> |
|
||||
<TabFilters /> |
|
||||
</div> |
|
||||
<div className="grid grid-cols-1 gap-8"> |
|
||||
{DEMO_CARS.map((item) => ( |
|
||||
<div |
|
||||
key={item.id} |
|
||||
onMouseEnter={() => setCurrentHoverID((_) => item.id)} |
|
||||
onMouseLeave={() => setCurrentHoverID((_) => -1)} |
|
||||
> |
|
||||
<CarCardH data={item} /> |
|
||||
</div> |
|
||||
))} |
|
||||
</div> |
|
||||
<div className="flex mt-16 justify-center items-center"> |
|
||||
<Pagination /> |
|
||||
</div> |
|
||||
</div> |
|
||||
|
|
||||
<div |
|
||||
className="flex xl:hidden items-center justify-center fixed bottom-8 left-1/2 transform -translate-x-1/2 px-6 py-2 bg-neutral-900 text-white shadow-2xl rounded-full z-30 space-x-3 text-sm cursor-pointer" |
|
||||
onClick={() => setShowFullMapFixed(true)} |
|
||||
> |
|
||||
<i className="text-lg las la-map"></i> |
|
||||
<span>Show map</span> |
|
||||
</div> |
|
||||
|
|
||||
{/* MAPPPPP */} |
|
||||
<div |
|
||||
className={`xl:flex-grow xl:static xl:block ${ |
|
||||
showFullMapFixed ? "fixed inset-0 z-50" : "hidden" |
|
||||
}`}
|
|
||||
> |
|
||||
{showFullMapFixed && ( |
|
||||
<ButtonClose |
|
||||
onClick={() => setShowFullMapFixed(false)} |
|
||||
className="bg-white absolute z-50 left-3 top-3 shadow-lg rounded-xl w-10 h-10" |
|
||||
/> |
|
||||
)} |
|
||||
|
|
||||
<div className="fixed xl:sticky top-0 xl:top-[88px] left-0 w-full h-full xl:h-[calc(100vh-88px)] rounded-md overflow-hidden"> |
|
||||
<div className="absolute bottom-5 left-3 lg:bottom-auto lg:top-2.5 lg:left-1/2 transform lg:-translate-x-1/2 py-2 px-4 bg-white shadow-xl z-10 rounded-2xl min-w-max"> |
|
||||
<Checkbox |
|
||||
className="text-xs xl:text-sm text-neutral-800" |
|
||||
name="xx" |
|
||||
label="Search ass I move the map" |
|
||||
/> |
|
||||
</div> |
|
||||
{/* BELLOW IS MY GOOGLE API KEY -- PLEASE DELETE AND TYPE YOUR API KEY */} |
|
||||
|
|
||||
<GoogleMapReact |
|
||||
bootstrapURLKeys={{ |
|
||||
key: "AIzaSyAGVJfZMAKYfZ71nzL_v5i3LjTTWnCYwTY", |
|
||||
}} |
|
||||
yesIWantToUseGoogleMapApiInternals |
|
||||
defaultZoom={12} |
|
||||
defaultCenter={DEMO_CARS[0].map} |
|
||||
> |
|
||||
{DEMO_CARS.map((item) => ( |
|
||||
<AnyReactComponent |
|
||||
isSelected={currentHoverID === item.id} |
|
||||
key={item.id} |
|
||||
lat={item.map.lat} |
|
||||
lng={item.map.lng} |
|
||||
car={item} |
|
||||
/> |
|
||||
))} |
|
||||
</GoogleMapReact> |
|
||||
</div> |
|
||||
</div> |
|
||||
</div> |
|
||||
</div> |
|
||||
); |
|
||||
}; |
|
||||
|
|
||||
export default SectionGridHasMap; |
|
@ -1,683 +0,0 @@ |
|||||
"use client"; |
|
||||
|
|
||||
import React, { Fragment, useState } from "react"; |
|
||||
import { Dialog, Popover, Transition } from "@headlessui/react"; |
|
||||
import NcInputNumber from "@/components/NcInputNumber"; |
|
||||
import ButtonPrimary from "@/shared/ButtonPrimary"; |
|
||||
import ButtonThird from "@/shared/ButtonThird"; |
|
||||
import ButtonClose from "@/shared/ButtonClose"; |
|
||||
import Checkbox from "@/shared/Checkbox"; |
|
||||
import convertNumbThousand from "@/utils/convertNumbThousand"; |
|
||||
import Slider from "rc-slider"; |
|
||||
|
|
||||
// DEMO DATA
|
|
||||
const typeOfCar = [ |
|
||||
{ name: "Small", description: "$68" }, |
|
||||
{ name: "Medium", description: "$128" }, |
|
||||
{ name: "Large", description: "$268" }, |
|
||||
{ name: "SUV", description: "$268" }, |
|
||||
{ name: "Van", description: "$268" }, |
|
||||
{ name: "Luxury", description: "$268" }, |
|
||||
]; |
|
||||
|
|
||||
const carSpecifications = [ |
|
||||
{ name: "With air conditioning" }, |
|
||||
{ name: "Automatic transmission" }, |
|
||||
{ name: "Manual transmission" }, |
|
||||
{ name: "2 doors" }, |
|
||||
{ name: "4+ doors" }, |
|
||||
]; |
|
||||
|
|
||||
//
|
|
||||
const mileage = [{ name: "Unlimited" }, { name: "Limited" }]; |
|
||||
const supplier = [ |
|
||||
{ name: "Alamo", defaultChecked: true }, |
|
||||
{ name: "Avis", defaultChecked: true }, |
|
||||
{ name: "Budget" }, |
|
||||
{ name: "Dollar" }, |
|
||||
]; |
|
||||
const insurance = [ |
|
||||
{ name: "No insurance", defaultChecked: true }, |
|
||||
{ name: "Zero excess " }, |
|
||||
{ name: "Inclusive" }, |
|
||||
]; |
|
||||
|
|
||||
const TabFilters = () => { |
|
||||
const [isOpenMoreFilter, setisOpenMoreFilter] = useState(false); |
|
||||
const [isOpenMoreFilterMobile, setisOpenMoreFilterMobile] = useState(false); |
|
||||
const [rangePrices, setRangePrices] = useState([0, 1000]); |
|
||||
const [isOnSale, setIsOnSale] = useState(true); |
|
||||
//
|
|
||||
const closeModalMoreFilter = () => setisOpenMoreFilter(false); |
|
||||
const openModalMoreFilter = () => setisOpenMoreFilter(true); |
|
||||
//
|
|
||||
const closeModalMoreFilterMobile = () => setisOpenMoreFilterMobile(false); |
|
||||
const openModalMoreFilterMobile = () => setisOpenMoreFilterMobile(true); |
|
||||
|
|
||||
const renderXClear = () => { |
|
||||
return ( |
|
||||
<span className="w-4 h-4 rounded-full bg-primary-500 text-white flex items-center justify-center ml-3 cursor-pointer"> |
|
||||
<svg |
|
||||
xmlns="http://www.w3.org/2000/svg" |
|
||||
className="h-3 w-3" |
|
||||
viewBox="0 0 20 20" |
|
||||
fill="currentColor" |
|
||||
> |
|
||||
<path |
|
||||
fillRule="evenodd" |
|
||||
d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z" |
|
||||
clipRule="evenodd" |
|
||||
/> |
|
||||
</svg> |
|
||||
</span> |
|
||||
); |
|
||||
}; |
|
||||
|
|
||||
const renderTabsTypeOfCars = () => { |
|
||||
return ( |
|
||||
<Popover className="relative"> |
|
||||
{({ open, close }) => ( |
|
||||
<> |
|
||||
<Popover.Button |
|
||||
className={`flex items-center justify-center px-4 py-2 text-sm rounded-full border border-neutral-300 dark:border-neutral-700 hover:border-neutral-400 dark:hover:border-neutral-6000 focus:outline-none ${ |
|
||||
open ? "!border-primary-500 " : "" |
|
||||
}`}
|
|
||||
> |
|
||||
<span>Car type</span> |
|
||||
<i className="las la-angle-down ml-2"></i> |
|
||||
</Popover.Button> |
|
||||
<Transition |
|
||||
as={Fragment} |
|
||||
enter="transition ease-out duration-200" |
|
||||
enterFrom="opacity-0 translate-y-1" |
|
||||
enterTo="opacity-100 translate-y-0" |
|
||||
leave="transition ease-in duration-150" |
|
||||
leaveFrom="opacity-100 translate-y-0" |
|
||||
leaveTo="opacity-0 translate-y-1" |
|
||||
> |
|
||||
<Popover.Panel className="absolute z-10 w-screen max-w-sm px-4 mt-3 left-0 sm:px-0 lg:max-w-md"> |
|
||||
<div className="overflow-hidden rounded-2xl shadow-xl bg-white dark:bg-neutral-900 border border-neutral-200 dark:border-neutral-700"> |
|
||||
<div className="relative flex flex-col px-5 py-6 space-y-5"> |
|
||||
{typeOfCar.map((item) => ( |
|
||||
<div key={item.name} className=""> |
|
||||
<Checkbox |
|
||||
name={item.name} |
|
||||
label={item.name} |
|
||||
subLabel={item.description} |
|
||||
/> |
|
||||
</div> |
|
||||
))} |
|
||||
</div> |
|
||||
<div className="p-5 bg-neutral-50 dark:bg-neutral-900 dark:border-t dark:border-neutral-800 flex items-center justify-between"> |
|
||||
<ButtonThird onClick={close} sizeClass="px-4 py-2 sm:px-5"> |
|
||||
Clear |
|
||||
</ButtonThird> |
|
||||
<ButtonPrimary |
|
||||
onClick={close} |
|
||||
sizeClass="px-4 py-2 sm:px-5" |
|
||||
> |
|
||||
Apply |
|
||||
</ButtonPrimary> |
|
||||
</div> |
|
||||
</div> |
|
||||
</Popover.Panel> |
|
||||
</Transition> |
|
||||
</> |
|
||||
)} |
|
||||
</Popover> |
|
||||
); |
|
||||
}; |
|
||||
|
|
||||
const renderTabsPriceRage = () => { |
|
||||
return ( |
|
||||
<Popover className="relative"> |
|
||||
{({ open, close }) => ( |
|
||||
<> |
|
||||
<Popover.Button |
|
||||
className={`flex items-center justify-center px-4 py-2 text-sm rounded-full border border-primary-500 bg-primary-50 text-primary-700 focus:outline-none `} |
|
||||
> |
|
||||
<span> |
|
||||
{`$${convertNumbThousand( |
|
||||
rangePrices[0] |
|
||||
)} - $${convertNumbThousand(rangePrices[1])}`}{" "}
|
|
||||
</span> |
|
||||
{renderXClear()} |
|
||||
</Popover.Button> |
|
||||
<Transition |
|
||||
as={Fragment} |
|
||||
enter="transition ease-out duration-200" |
|
||||
enterFrom="opacity-0 translate-y-1" |
|
||||
enterTo="opacity-100 translate-y-0" |
|
||||
leave="transition ease-in duration-150" |
|
||||
leaveFrom="opacity-100 translate-y-0" |
|
||||
leaveTo="opacity-0 translate-y-1" |
|
||||
> |
|
||||
<Popover.Panel className="absolute z-10 w-screen max-w-sm px-4 mt-3 left-0 sm:px-0 "> |
|
||||
<div className="overflow-hidden rounded-2xl shadow-xl bg-white dark:bg-neutral-900 border border-neutral-200 dark:border-neutral-700"> |
|
||||
<div className="relative flex flex-col px-5 py-6 space-y-8"> |
|
||||
<div className="space-y-5"> |
|
||||
<span className="font-medium">Price per day</span> |
|
||||
<Slider |
|
||||
range |
|
||||
className="text-red-400" |
|
||||
min={0} |
|
||||
max={2000} |
|
||||
defaultValue={[rangePrices[0], rangePrices[1]]} |
|
||||
allowCross={false} |
|
||||
onChange={(e) => setRangePrices(e as number[])} |
|
||||
/> |
|
||||
</div> |
|
||||
|
|
||||
<div className="flex justify-between space-x-5"> |
|
||||
<div> |
|
||||
<label |
|
||||
htmlFor="minPrice" |
|
||||
className="block text-sm font-medium text-neutral-700 dark:text-neutral-300" |
|
||||
> |
|
||||
Min price |
|
||||
</label> |
|
||||
<div className="mt-1 relative rounded-md"> |
|
||||
<div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none"> |
|
||||
<span className="text-neutral-500 sm:text-sm"> |
|
||||
$ |
|
||||
</span> |
|
||||
</div> |
|
||||
<input |
|
||||
type="text" |
|
||||
name="minPrice" |
|
||||
disabled |
|
||||
id="minPrice" |
|
||||
className="focus:ring-indigo-500 focus:border-indigo-500 block w-full pl-7 pr-3 sm:text-sm border-neutral-200 rounded-full text-neutral-900" |
|
||||
value={rangePrices[0]} |
|
||||
/> |
|
||||
</div> |
|
||||
</div> |
|
||||
<div> |
|
||||
<label |
|
||||
htmlFor="maxPrice" |
|
||||
className="block text-sm font-medium text-neutral-700 dark:text-neutral-300" |
|
||||
> |
|
||||
Max price |
|
||||
</label> |
|
||||
<div className="mt-1 relative rounded-md"> |
|
||||
<div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none"> |
|
||||
<span className="text-neutral-500 sm:text-sm"> |
|
||||
$ |
|
||||
</span> |
|
||||
</div> |
|
||||
<input |
|
||||
type="text" |
|
||||
disabled |
|
||||
name="maxPrice" |
|
||||
id="maxPrice" |
|
||||
className="focus:ring-indigo-500 focus:border-indigo-500 block w-full pl-7 pr-3 sm:text-sm border-neutral-200 rounded-full text-neutral-900" |
|
||||
value={rangePrices[1]} |
|
||||
/> |
|
||||
</div> |
|
||||
</div> |
|
||||
</div> |
|
||||
</div> |
|
||||
<div className="p-5 bg-neutral-50 dark:bg-neutral-900 dark:border-t dark:border-neutral-800 flex items-center justify-between"> |
|
||||
<ButtonThird onClick={close} sizeClass="px-4 py-2 sm:px-5"> |
|
||||
Clear |
|
||||
</ButtonThird> |
|
||||
<ButtonPrimary |
|
||||
onClick={close} |
|
||||
sizeClass="px-4 py-2 sm:px-5" |
|
||||
> |
|
||||
Apply |
|
||||
</ButtonPrimary> |
|
||||
</div> |
|
||||
</div> |
|
||||
</Popover.Panel> |
|
||||
</Transition> |
|
||||
</> |
|
||||
)} |
|
||||
</Popover> |
|
||||
); |
|
||||
}; |
|
||||
|
|
||||
const renderTabOnSale = () => { |
|
||||
return ( |
|
||||
<div |
|
||||
className={`flex items-center justify-center px-4 py-2 text-sm rounded-full border focus:outline-none cursor-pointer transition-all ${ |
|
||||
isOnSale |
|
||||
? "border-primary-500 bg-primary-50 text-primary-700" |
|
||||
: "border-neutral-300 dark:border-neutral-700" |
|
||||
}`}
|
|
||||
onClick={() => setIsOnSale(!isOnSale)} |
|
||||
> |
|
||||
<span>On sale</span> |
|
||||
{isOnSale && renderXClear()} |
|
||||
</div> |
|
||||
); |
|
||||
}; |
|
||||
|
|
||||
const renderTabsGuestsAndBags = () => { |
|
||||
return ( |
|
||||
<Popover className="relative"> |
|
||||
{({ open, close }) => ( |
|
||||
<> |
|
||||
<Popover.Button |
|
||||
className={`flex items-center justify-center px-4 py-2 text-sm rounded-full border border-neutral-300 dark:border-neutral-700 hover:border-neutral-400 dark:hover:border-neutral-6000 focus:outline-none ${ |
|
||||
open ? "!border-primary-500 " : "" |
|
||||
}`}
|
|
||||
> |
|
||||
<span>Guests & Bags</span> |
|
||||
<i className="las la-angle-down ml-2"></i> |
|
||||
</Popover.Button> |
|
||||
<Transition |
|
||||
as={Fragment} |
|
||||
enter="transition ease-out duration-200" |
|
||||
enterFrom="opacity-0 translate-y-1" |
|
||||
enterTo="opacity-100 translate-y-0" |
|
||||
leave="transition ease-in duration-150" |
|
||||
leaveFrom="opacity-100 translate-y-0" |
|
||||
leaveTo="opacity-0 translate-y-1" |
|
||||
> |
|
||||
<Popover.Panel className="absolute z-10 w-screen max-w-sm px-4 mt-3 left-0 sm:px-0 lg:max-w-md"> |
|
||||
<div className="overflow-hidden rounded-2xl shadow-xl bg-white dark:bg-neutral-900 border border-neutral-200 dark:border-neutral-700"> |
|
||||
<div className="relative flex flex-col px-5 py-6 space-y-5"> |
|
||||
<NcInputNumber label="Passengers" max={40} /> |
|
||||
<NcInputNumber label="Bags" max={40} /> |
|
||||
</div> |
|
||||
<div className="p-5 bg-neutral-50 dark:bg-neutral-900 dark:border-t dark:border-neutral-800 flex items-center justify-between"> |
|
||||
<ButtonThird onClick={close} sizeClass="px-4 py-2 sm:px-5"> |
|
||||
Clear |
|
||||
</ButtonThird> |
|
||||
<ButtonPrimary |
|
||||
onClick={close} |
|
||||
sizeClass="px-4 py-2 sm:px-5" |
|
||||
> |
|
||||
Apply |
|
||||
</ButtonPrimary> |
|
||||
</div> |
|
||||
</div> |
|
||||
</Popover.Panel> |
|
||||
</Transition> |
|
||||
</> |
|
||||
)} |
|
||||
</Popover> |
|
||||
); |
|
||||
}; |
|
||||
|
|
||||
const renderMoreFilterItem = ( |
|
||||
data: { |
|
||||
name: string; |
|
||||
description?: string; |
|
||||
defaultChecked?: boolean; |
|
||||
}[] |
|
||||
) => { |
|
||||
const list1 = data.filter((_, i) => i < data.length / 2); |
|
||||
const list2 = data.filter((_, i) => i >= data.length / 2); |
|
||||
return ( |
|
||||
<div className="grid grid-cols-2 gap-y-7 gap-x-4 sm:gap-8"> |
|
||||
<div className="flex flex-col space-y-5"> |
|
||||
{list1.map((item) => ( |
|
||||
<Checkbox |
|
||||
key={item.name} |
|
||||
name={item.name} |
|
||||
subLabel={item.description} |
|
||||
label={item.name} |
|
||||
defaultChecked={!!item.defaultChecked} |
|
||||
/> |
|
||||
))} |
|
||||
</div> |
|
||||
<div className="flex flex-col space-y-5"> |
|
||||
{list2.map((item) => ( |
|
||||
<Checkbox |
|
||||
key={item.name} |
|
||||
name={item.name} |
|
||||
subLabel={item.description} |
|
||||
label={item.name} |
|
||||
defaultChecked={!!item.defaultChecked} |
|
||||
/> |
|
||||
))} |
|
||||
</div> |
|
||||
</div> |
|
||||
); |
|
||||
}; |
|
||||
|
|
||||
// Morefilter for mobile mode
|
|
||||
const renderTabMobileFilter = () => { |
|
||||
return ( |
|
||||
<div> |
|
||||
<div |
|
||||
className={`flex items-center justify-center px-4 py-2 text-sm rounded-full border border-primary-500 bg-primary-50 text-primary-700 focus:outline-none cursor-pointer`} |
|
||||
onClick={openModalMoreFilterMobile} |
|
||||
> |
|
||||
<span> |
|
||||
<span className="hidden sm:inline">Car</span> filters (3) |
|
||||
</span> |
|
||||
{renderXClear()} |
|
||||
</div> |
|
||||
|
|
||||
<Transition appear show={isOpenMoreFilterMobile} as={Fragment}> |
|
||||
<Dialog |
|
||||
as="div" |
|
||||
className="fixed inset-0 z-50 overflow-y-auto" |
|
||||
onClose={closeModalMoreFilterMobile} |
|
||||
> |
|
||||
<div className="min-h-screen text-center"> |
|
||||
<Transition.Child |
|
||||
as={Fragment} |
|
||||
enter="ease-out duration-300" |
|
||||
enterFrom="opacity-0" |
|
||||
enterTo="opacity-100" |
|
||||
leave="ease-in duration-200" |
|
||||
leaveFrom="opacity-100" |
|
||||
leaveTo="opacity-0" |
|
||||
> |
|
||||
<Dialog.Overlay className="fixed inset-0 bg-black bg-opacity-40 dark:bg-opacity-60" /> |
|
||||
</Transition.Child> |
|
||||
|
|
||||
{/* This element is to trick the browser into centering the modal contents. */} |
|
||||
<span |
|
||||
className="inline-block h-screen align-middle" |
|
||||
aria-hidden="true" |
|
||||
> |
|
||||
​ |
|
||||
</span> |
|
||||
<Transition.Child |
|
||||
className="inline-block py-8 px-2 h-screen w-full max-w-4xl" |
|
||||
enter="ease-out duration-300" |
|
||||
enterFrom="opacity-0 scale-95" |
|
||||
enterTo="opacity-100 scale-100" |
|
||||
leave="ease-in duration-200" |
|
||||
leaveFrom="opacity-100 scale-100" |
|
||||
leaveTo="opacity-0 scale-95" |
|
||||
> |
|
||||
<div className="inline-flex flex-col w-full max-w-4xl text-left align-middle transition-all transform overflow-hidden rounded-2xl bg-white dark:bg-neutral-900 dark:border dark:border-neutral-700 dark:text-neutral-100 shadow-xl h-full"> |
|
||||
<div className="relative flex-shrink-0 px-6 py-4 border-b border-neutral-200 dark:border-neutral-800 text-center"> |
|
||||
<Dialog.Title |
|
||||
as="h3" |
|
||||
className="text-lg font-medium leading-6 text-gray-900" |
|
||||
> |
|
||||
Experiences filters |
|
||||
</Dialog.Title> |
|
||||
<span className="absolute left-3 top-3"> |
|
||||
<ButtonClose onClick={closeModalMoreFilterMobile} /> |
|
||||
</span> |
|
||||
</div> |
|
||||
|
|
||||
<div className="flex-grow overflow-y-auto"> |
|
||||
<div className="px-4 sm:px-6 divide-y divide-neutral-200 dark:divide-neutral-800"> |
|
||||
{/* ------ */} |
|
||||
<div className="py-7"> |
|
||||
<h3 className="text-xl font-medium">Type of car</h3> |
|
||||
<div className="mt-6 relative "> |
|
||||
{renderMoreFilterItem(typeOfCar)} |
|
||||
</div> |
|
||||
</div> |
|
||||
|
|
||||
{/* ------ */} |
|
||||
<div className="py-7"> |
|
||||
<h3 className="text-xl font-medium"> |
|
||||
Car specifications |
|
||||
</h3> |
|
||||
<div className="mt-6 relative "> |
|
||||
{renderMoreFilterItem(carSpecifications)} |
|
||||
</div> |
|
||||
</div> |
|
||||
|
|
||||
{/* --------- */} |
|
||||
{/* ---- */} |
|
||||
<div className="py-7"> |
|
||||
<h3 className="text-xl font-medium">Range Prices</h3> |
|
||||
<div className="mt-6 relative "> |
|
||||
<div className="relative flex flex-col space-y-8"> |
|
||||
<div className="space-y-5"> |
|
||||
<Slider |
|
||||
range |
|
||||
className="text-red-400" |
|
||||
min={0} |
|
||||
max={2000} |
|
||||
defaultValue={[0, 1000]} |
|
||||
allowCross={false} |
|
||||
onChange={(e) => setRangePrices(e as number[])} |
|
||||
/> |
|
||||
</div> |
|
||||
|
|
||||
<div className="flex justify-between space-x-5"> |
|
||||
<div> |
|
||||
<label |
|
||||
htmlFor="minPrice" |
|
||||
className="block text-sm font-medium text-neutral-700 dark:text-neutral-300" |
|
||||
> |
|
||||
Min price |
|
||||
</label> |
|
||||
<div className="mt-1 relative rounded-md"> |
|
||||
<div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none"> |
|
||||
<span className="text-neutral-500 sm:text-sm"> |
|
||||
$ |
|
||||
</span> |
|
||||
</div> |
|
||||
<input |
|
||||
type="text" |
|
||||
name="minPrice" |
|
||||
disabled |
|
||||
id="minPrice" |
|
||||
className="focus:ring-indigo-500 focus:border-indigo-500 block w-full pl-7 pr-3 sm:text-sm border-neutral-200 rounded-full text-neutral-900" |
|
||||
value={rangePrices[0]} |
|
||||
/> |
|
||||
</div> |
|
||||
</div> |
|
||||
<div> |
|
||||
<label |
|
||||
htmlFor="maxPrice" |
|
||||
className="block text-sm font-medium text-neutral-700 dark:text-neutral-300" |
|
||||
> |
|
||||
Max price |
|
||||
</label> |
|
||||
<div className="mt-1 relative rounded-md"> |
|
||||
<div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none"> |
|
||||
<span className="text-neutral-500 sm:text-sm"> |
|
||||
$ |
|
||||
</span> |
|
||||
</div> |
|
||||
<input |
|
||||
type="text" |
|
||||
disabled |
|
||||
name="maxPrice" |
|
||||
id="maxPrice" |
|
||||
className="focus:ring-indigo-500 focus:border-indigo-500 block w-full pl-7 pr-3 sm:text-sm border-neutral-200 rounded-full text-neutral-900" |
|
||||
value={rangePrices[1]} |
|
||||
/> |
|
||||
</div> |
|
||||
</div> |
|
||||
</div> |
|
||||
</div> |
|
||||
</div> |
|
||||
</div> |
|
||||
|
|
||||
{/* ------ */} |
|
||||
<div className="py-7"> |
|
||||
<h3 className="text-xl font-medium"> |
|
||||
Passengers & Bags |
|
||||
</h3> |
|
||||
<div className="mt-6 relative flex-col px-5 py-6 space-y-5"> |
|
||||
<NcInputNumber label="Passengers" max={40} /> |
|
||||
<NcInputNumber label="Bags" max={40} /> |
|
||||
</div> |
|
||||
</div> |
|
||||
|
|
||||
{/* ------ */} |
|
||||
<div className="py-7"> |
|
||||
<h3 className="text-xl font-medium">Mileage</h3> |
|
||||
<div className="mt-6 relative "> |
|
||||
{renderMoreFilterItem(mileage)} |
|
||||
</div> |
|
||||
</div> |
|
||||
|
|
||||
{/* ------ */} |
|
||||
<div className="py-7"> |
|
||||
<h3 className="text-xl font-medium">Supplier</h3> |
|
||||
<div className="mt-6 relative "> |
|
||||
{renderMoreFilterItem(supplier)} |
|
||||
</div> |
|
||||
</div> |
|
||||
|
|
||||
{/* ------ */} |
|
||||
<div className="py-7"> |
|
||||
<h3 className="text-xl font-medium">Insurance</h3> |
|
||||
<div className="mt-6 relative "> |
|
||||
{renderMoreFilterItem(insurance)} |
|
||||
</div> |
|
||||
</div> |
|
||||
</div> |
|
||||
</div> |
|
||||
|
|
||||
<div className="p-4 sm:p-6 flex-shrink-0 bg-neutral-50 dark:bg-neutral-900 dark:border-t dark:border-neutral-800 flex items-center justify-between"> |
|
||||
<ButtonThird |
|
||||
onClick={closeModalMoreFilterMobile} |
|
||||
sizeClass="px-4 py-2 sm:px-5" |
|
||||
> |
|
||||
Clear |
|
||||
</ButtonThird> |
|
||||
<ButtonPrimary |
|
||||
onClick={closeModalMoreFilterMobile} |
|
||||
sizeClass="px-4 py-2 sm:px-5" |
|
||||
> |
|
||||
Apply |
|
||||
</ButtonPrimary> |
|
||||
</div> |
|
||||
</div> |
|
||||
</Transition.Child> |
|
||||
</div> |
|
||||
</Dialog> |
|
||||
</Transition> |
|
||||
</div> |
|
||||
); |
|
||||
}; |
|
||||
|
|
||||
//
|
|
||||
const renderTabMoreFilter = () => { |
|
||||
return ( |
|
||||
<div> |
|
||||
<div |
|
||||
className={`flex items-center justify-center px-4 py-2 text-sm rounded-full border border-primary-500 bg-primary-50 text-primary-700 focus:outline-none cursor-pointer`} |
|
||||
onClick={openModalMoreFilter} |
|
||||
> |
|
||||
<span>More filters (3)</span> |
|
||||
{renderXClear()} |
|
||||
</div> |
|
||||
|
|
||||
<Transition appear show={isOpenMoreFilter} as={Fragment}> |
|
||||
<Dialog |
|
||||
as="div" |
|
||||
className="fixed inset-0 z-50 overflow-y-auto" |
|
||||
onClose={closeModalMoreFilter} |
|
||||
> |
|
||||
<div className="min-h-screen text-center"> |
|
||||
<Transition.Child |
|
||||
as={Fragment} |
|
||||
enter="ease-out duration-300" |
|
||||
enterFrom="opacity-0" |
|
||||
enterTo="opacity-100" |
|
||||
leave="ease-in duration-200" |
|
||||
leaveFrom="opacity-100" |
|
||||
leaveTo="opacity-0" |
|
||||
> |
|
||||
<Dialog.Overlay className="fixed inset-0 bg-black bg-opacity-40 dark:bg-opacity-60" /> |
|
||||
</Transition.Child> |
|
||||
|
|
||||
{/* This element is to trick the browser into centering the modal contents. */} |
|
||||
<span |
|
||||
className="inline-block h-screen align-middle" |
|
||||
aria-hidden="true" |
|
||||
> |
|
||||
​ |
|
||||
</span> |
|
||||
<Transition.Child |
|
||||
className="inline-block py-8 px-2 h-screen w-full max-w-4xl" |
|
||||
enter="ease-out duration-300" |
|
||||
enterFrom="opacity-0 scale-95" |
|
||||
enterTo="opacity-100 scale-100" |
|
||||
leave="ease-in duration-200" |
|
||||
leaveFrom="opacity-100 scale-100" |
|
||||
leaveTo="opacity-0 scale-95" |
|
||||
> |
|
||||
<div className="inline-flex flex-col w-full max-w-4xl text-left align-middle transition-all transform overflow-hidden rounded-2xl bg-white dark:bg-neutral-900 dark:border dark:border-neutral-700 dark:text-neutral-100 shadow-xl h-full"> |
|
||||
<div className="relative flex-shrink-0 px-6 py-4 border-b border-neutral-200 dark:border-neutral-800 text-center"> |
|
||||
<Dialog.Title |
|
||||
as="h3" |
|
||||
className="text-lg font-medium leading-6 text-gray-900" |
|
||||
> |
|
||||
More filters |
|
||||
</Dialog.Title> |
|
||||
<span className="absolute left-3 top-3"> |
|
||||
<ButtonClose onClick={closeModalMoreFilter} /> |
|
||||
</span> |
|
||||
</div> |
|
||||
|
|
||||
<div className="flex-grow overflow-y-auto"> |
|
||||
<div className="px-4 sm:px-6 divide-y divide-neutral-200 dark:divide-neutral-800"> |
|
||||
<div className="py-7"> |
|
||||
<h3 className="text-xl font-medium"> |
|
||||
Car specifications |
|
||||
</h3> |
|
||||
<div className="mt-6 relative "> |
|
||||
{renderMoreFilterItem(carSpecifications)} |
|
||||
</div> |
|
||||
</div> |
|
||||
<div className="py-7"> |
|
||||
<h3 className="text-xl font-medium">Mileage</h3> |
|
||||
<div className="mt-6 relative "> |
|
||||
{renderMoreFilterItem(mileage)} |
|
||||
</div> |
|
||||
</div> |
|
||||
<div className="py-7"> |
|
||||
<h3 className="text-xl font-medium">Supplier</h3> |
|
||||
<div className="mt-6 relative "> |
|
||||
{renderMoreFilterItem(supplier)} |
|
||||
</div> |
|
||||
</div> |
|
||||
<div className="py-7"> |
|
||||
<h3 className="text-xl font-medium">Insurance</h3> |
|
||||
<div className="mt-6 relative "> |
|
||||
{renderMoreFilterItem(insurance)} |
|
||||
</div> |
|
||||
</div> |
|
||||
</div> |
|
||||
</div> |
|
||||
|
|
||||
<div className="p-6 flex-shrink-0 bg-neutral-50 dark:bg-neutral-900 dark:border-t dark:border-neutral-800 flex items-center justify-between"> |
|
||||
<ButtonThird |
|
||||
onClick={closeModalMoreFilter} |
|
||||
sizeClass="px-4 py-2 sm:px-5" |
|
||||
> |
|
||||
Clear |
|
||||
</ButtonThird> |
|
||||
<ButtonPrimary |
|
||||
onClick={closeModalMoreFilter} |
|
||||
sizeClass="px-4 py-2 sm:px-5" |
|
||||
> |
|
||||
Apply |
|
||||
</ButtonPrimary> |
|
||||
</div> |
|
||||
</div> |
|
||||
</Transition.Child> |
|
||||
</div> |
|
||||
</Dialog> |
|
||||
</Transition> |
|
||||
</div> |
|
||||
); |
|
||||
}; |
|
||||
|
|
||||
return ( |
|
||||
<div className="flex lg:space-x-4"> |
|
||||
<div className="hidden lg:flex space-x-4"> |
|
||||
{renderTabsTypeOfCars()} |
|
||||
{renderTabsPriceRage()} |
|
||||
{renderTabsGuestsAndBags()} |
|
||||
{renderTabMoreFilter()} |
|
||||
</div> |
|
||||
<div className="flex lg:hidden space-x-4"> |
|
||||
{renderTabMobileFilter()} |
|
||||
{renderTabOnSale()} |
|
||||
</div> |
|
||||
</div> |
|
||||
); |
|
||||
}; |
|
||||
|
|
||||
export default TabFilters; |
|
@ -1,59 +0,0 @@ |
|||||
import BgGlassmorphism from "@/components/BgGlassmorphism"; |
|
||||
import React, { ReactNode } from "react"; |
|
||||
import SectionHeroArchivePage from "../(server-components)/SectionHeroArchivePage"; |
|
||||
import heroRightImage from "@/images/hero-right-car.png"; |
|
||||
import BackgroundSection from "@/components/BackgroundSection"; |
|
||||
import SectionSliderNewCategories from "@/components/SectionSliderNewCategories"; |
|
||||
import SectionSubscribe2 from "@/components/SectionSubscribe2"; |
|
||||
import SectionGridAuthorBox from "@/components/SectionGridAuthorBox"; |
|
||||
|
|
||||
const Layout = ({ children }: { children: ReactNode }) => { |
|
||||
return ( |
|
||||
<div className={`nc-ListingCarMapPage relative `}> |
|
||||
<BgGlassmorphism /> |
|
||||
|
|
||||
{/* SECTION HERO */} |
|
||||
<div className="container pt-10 pb-24 lg:pt-16 lg:pb-28"> |
|
||||
<SectionHeroArchivePage |
|
||||
rightImage={heroRightImage} |
|
||||
currentPage="Cars" |
|
||||
currentTab="Cars" |
|
||||
listingType={ |
|
||||
<> |
|
||||
<i className="text-2xl las la-car"></i> |
|
||||
<span className="ml-2.5">1512 cars</span> |
|
||||
</> |
|
||||
} |
|
||||
/> |
|
||||
</div> |
|
||||
|
|
||||
{/* SECTION */} |
|
||||
{children} |
|
||||
|
|
||||
<div className="container overflow-hidden"> |
|
||||
{/* SECTION 1 */} |
|
||||
<div className="relative py-16"> |
|
||||
<BackgroundSection /> |
|
||||
<SectionSliderNewCategories |
|
||||
heading="Explore by types of stays" |
|
||||
subHeading="Explore houses based on 10 types of stays" |
|
||||
categoryCardType="card5" |
|
||||
itemPerRow={5} |
|
||||
sliderStyle="style2" |
|
||||
/> |
|
||||
</div> |
|
||||
|
|
||||
{/* SECTION */} |
|
||||
<SectionSubscribe2 className="py-24 lg:py-28" /> |
|
||||
|
|
||||
{/* SECTION */} |
|
||||
<div className="relative py-16 mb-24 lg:mb-28"> |
|
||||
<BackgroundSection className="bg-orange-50 dark:bg-black dark:bg-opacity-20 " /> |
|
||||
<SectionGridAuthorBox /> |
|
||||
</div> |
|
||||
</div> |
|
||||
</div> |
|
||||
); |
|
||||
}; |
|
||||
|
|
||||
export default Layout; |
|
@ -1,14 +0,0 @@ |
|||||
import React, { FC } from "react"; |
|
||||
import SectionGridHasMap from "../SectionGridHasMap"; |
|
||||
|
|
||||
export interface ListingCarMapPageProps {} |
|
||||
|
|
||||
const ListingCarMapPage: FC<ListingCarMapPageProps> = () => { |
|
||||
return ( |
|
||||
<div className="container pb-24 lg:pb-28 2xl:pl-10 xl:pr-0 xl:max-w-none"> |
|
||||
<SectionGridHasMap /> |
|
||||
</div> |
|
||||
); |
|
||||
}; |
|
||||
|
|
||||
export default ListingCarMapPage; |
|
@ -1,14 +0,0 @@ |
|||||
import React, { FC } from "react"; |
|
||||
import SectionGridFilterCard from "../SectionGridFilterCard"; |
|
||||
|
|
||||
export interface ListingCarPageProps {} |
|
||||
|
|
||||
const ListingCarPage: FC<ListingCarPageProps> = () => { |
|
||||
return ( |
|
||||
<div className="container "> |
|
||||
<SectionGridFilterCard className="pb-24 lg:pb-28" /> |
|
||||
</div> |
|
||||
); |
|
||||
}; |
|
||||
|
|
||||
export default ListingCarPage; |
|
@ -1,134 +0,0 @@ |
|||||
"use client"; |
|
||||
|
|
||||
import React, { Fragment, useState } from "react"; |
|
||||
import { FC } from "react"; |
|
||||
import DatePicker from "react-datepicker"; |
|
||||
import { Popover, Transition } from "@headlessui/react"; |
|
||||
import { CalendarIcon } from "@heroicons/react/24/outline"; |
|
||||
import DatePickerCustomHeaderTwoMonth from "@/components/DatePickerCustomHeaderTwoMonth"; |
|
||||
import DatePickerCustomDay from "@/components/DatePickerCustomDay"; |
|
||||
import ClearDataButton from "../ClearDataButton"; |
|
||||
import ButtonSubmit from "../ButtonSubmit"; |
|
||||
|
|
||||
export interface RentalCarDatesRangeInputProps { |
|
||||
className?: string; |
|
||||
fieldClassName?: string; |
|
||||
hasButtonSubmit?: boolean; |
|
||||
} |
|
||||
|
|
||||
const RentalCarDatesRangeInput: FC<RentalCarDatesRangeInputProps> = ({ |
|
||||
className = "", |
|
||||
fieldClassName = "[ nc-hero-field-padding ]", |
|
||||
hasButtonSubmit = true, |
|
||||
}) => { |
|
||||
const [startDate, setStartDate] = useState<Date | null>( |
|
||||
new Date("2023/03/01") |
|
||||
); |
|
||||
const [endDate, setEndDate] = useState<Date | null>(new Date("2023/03/16")); |
|
||||
|
|
||||
const onChangeDate = (dates: [Date | null, Date | null]) => { |
|
||||
const [start, end] = dates; |
|
||||
setStartDate(start); |
|
||||
setEndDate(end); |
|
||||
}; |
|
||||
|
|
||||
const renderInput = () => { |
|
||||
return ( |
|
||||
<> |
|
||||
<div className="text-neutral-300 dark:text-neutral-400"> |
|
||||
<CalendarIcon className="w-5 h-5 lg:w-7 lg:h-7" /> |
|
||||
</div> |
|
||||
<div className="flex-grow text-left"> |
|
||||
<span className="block xl:text-lg font-semibold"> |
|
||||
{startDate?.toLocaleDateString("en-US", { |
|
||||
month: "short", |
|
||||
day: "2-digit", |
|
||||
}) || "Add dates"} |
|
||||
{endDate |
|
||||
? " - " + |
|
||||
endDate?.toLocaleDateString("en-US", { |
|
||||
month: "short", |
|
||||
day: "2-digit", |
|
||||
}) |
|
||||
: ""} |
|
||||
</span> |
|
||||
<span className="block mt-1 text-sm text-neutral-400 leading-none font-light"> |
|
||||
{"Pick up - Drop off"} |
|
||||
</span> |
|
||||
</div> |
|
||||
</> |
|
||||
); |
|
||||
}; |
|
||||
|
|
||||
return ( |
|
||||
<> |
|
||||
<Popover |
|
||||
className={`RentalCarDatesRangeInput relative flex ${className}`} |
|
||||
> |
|
||||
{({ open }) => ( |
|
||||
<> |
|
||||
<div |
|
||||
className={`flex-1 z-10 flex items-center focus:outline-none ${ |
|
||||
open ? "nc-hero-field-focused" : "" |
|
||||
}`}
|
|
||||
> |
|
||||
<Popover.Button |
|
||||
className={`flex-1 z-10 flex relative ${fieldClassName} items-center space-x-3 focus:outline-none `} |
|
||||
> |
|
||||
{renderInput()} |
|
||||
|
|
||||
{startDate && open && ( |
|
||||
<ClearDataButton onClick={() => onChangeDate([null, null])} /> |
|
||||
)} |
|
||||
</Popover.Button> |
|
||||
|
|
||||
{/* BUTTON SUBMIT OF FORM */} |
|
||||
{hasButtonSubmit && ( |
|
||||
<div className="pr-2 xl:pr-4"> |
|
||||
<ButtonSubmit href="/listing-car-detail" /> |
|
||||
</div> |
|
||||
)} |
|
||||
</div> |
|
||||
|
|
||||
{open && ( |
|
||||
<div className="h-8 absolute self-center top-1/2 -translate-y-1/2 z-0 -left-0.5 right-0.5 bg-white dark:bg-neutral-800"></div> |
|
||||
)} |
|
||||
|
|
||||
<Transition |
|
||||
as={Fragment} |
|
||||
enter="transition ease-out duration-200" |
|
||||
enterFrom="opacity-0 translate-y-1" |
|
||||
enterTo="opacity-100 translate-y-0" |
|
||||
leave="transition ease-in duration-150" |
|
||||
leaveFrom="opacity-100 translate-y-0" |
|
||||
leaveTo="opacity-0 translate-y-1" |
|
||||
> |
|
||||
<Popover.Panel className="absolute left-1/2 z-10 mt-3 top-full w-screen max-w-sm -translate-x-1/2 transform px-4 sm:px-0 lg:max-w-3xl"> |
|
||||
<div className="overflow-hidden rounded-3xl shadow-lg ring-1 ring-black ring-opacity-5 bg-white dark:bg-neutral-800 p-8"> |
|
||||
<DatePicker |
|
||||
selected={startDate} |
|
||||
onChange={onChangeDate} |
|
||||
startDate={startDate} |
|
||||
endDate={endDate} |
|
||||
selectsRange |
|
||||
monthsShown={2} |
|
||||
showPopperArrow={false} |
|
||||
inline |
|
||||
renderCustomHeader={(p) => ( |
|
||||
<DatePickerCustomHeaderTwoMonth {...p} /> |
|
||||
)} |
|
||||
renderDayContents={(day, date) => ( |
|
||||
<DatePickerCustomDay dayOfMonth={day} date={date} /> |
|
||||
)} |
|
||||
/> |
|
||||
</div> |
|
||||
</Popover.Panel> |
|
||||
</Transition> |
|
||||
</> |
|
||||
)} |
|
||||
</Popover> |
|
||||
</> |
|
||||
); |
|
||||
}; |
|
||||
|
|
||||
export default RentalCarDatesRangeInput; |
|
@ -1,70 +0,0 @@ |
|||||
"use client"; |
|
||||
|
|
||||
import React, { FC, useState } from "react"; |
|
||||
import LocationInput from "../LocationInput"; |
|
||||
import RentalCarDatesRangeInput from "./RentalCarDatesRangeInput"; |
|
||||
|
|
||||
export interface RentalCarSearchFormProps {} |
|
||||
|
|
||||
const RentalCarSearchForm: FC<RentalCarSearchFormProps> = ({}) => { |
|
||||
const [dropOffLocationType, setDropOffLocationType] = useState< |
|
||||
"same" | "different" |
|
||||
>("different"); |
|
||||
|
|
||||
const renderRadioBtn = () => { |
|
||||
return ( |
|
||||
<div className=" py-5 [ nc-hero-field-padding ] flex items-center flex-wrap flex-row border-b border-neutral-100 dark:border-neutral-700"> |
|
||||
<div |
|
||||
className={`py-1.5 px-4 flex items-center rounded-full font-medium text-xs cursor-pointer mr-2 my-1 sm:mr-3 ${ |
|
||||
dropOffLocationType === "different" |
|
||||
? "bg-black text-white shadow-black/10 shadow-lg" |
|
||||
: "border border-neutral-300 dark:border-neutral-700" |
|
||||
}`}
|
|
||||
onClick={(e) => setDropOffLocationType("different")} |
|
||||
> |
|
||||
Different drop off |
|
||||
</div> |
|
||||
<div |
|
||||
className={`py-1.5 px-4 flex items-center rounded-full font-medium text-xs cursor-pointer mr-2 my-1 sm:mr-3 ${ |
|
||||
dropOffLocationType === "same" |
|
||||
? "bg-black text-white shadow-black/10 shadow-lg" |
|
||||
: "border border-neutral-300 dark:border-neutral-700" |
|
||||
}`}
|
|
||||
onClick={(e) => setDropOffLocationType("same")} |
|
||||
> |
|
||||
Same drop off |
|
||||
</div> |
|
||||
</div> |
|
||||
); |
|
||||
}; |
|
||||
|
|
||||
const isDdropOffdifferent = dropOffLocationType === "different"; |
|
||||
|
|
||||
return ( |
|
||||
<form className="w-full relative mt-8 rounded-[40px] xl:rounded-[49px] rounded-t-2xl xl:rounded-t-3xl shadow-xl dark:shadow-2xl bg-white dark:bg-neutral-800"> |
|
||||
{renderRadioBtn()} |
|
||||
<div className={`relative flex flex-row`}> |
|
||||
<LocationInput |
|
||||
placeHolder="City or Airport" |
|
||||
desc="Pick up location" |
|
||||
className="flex-1" |
|
||||
/> |
|
||||
{isDdropOffdifferent && ( |
|
||||
<> |
|
||||
<div className="self-center border-r border-slate-200 dark:border-slate-700 h-8"></div> |
|
||||
<LocationInput |
|
||||
placeHolder="City or Airport" |
|
||||
desc="Drop off location" |
|
||||
className="flex-1" |
|
||||
divHideVerticalLineClass="-inset-x-0.5" |
|
||||
/> |
|
||||
</> |
|
||||
)} |
|
||||
<div className="self-center border-r border-slate-200 dark:border-slate-700 h-8"></div> |
|
||||
<RentalCarDatesRangeInput className="flex-1" /> |
|
||||
</div> |
|
||||
</form> |
|
||||
); |
|
||||
}; |
|
||||
|
|
||||
export default RentalCarSearchForm; |
|
@ -1,119 +0,0 @@ |
|||||
"use client"; |
|
||||
|
|
||||
import React, { Fragment, useState } from "react"; |
|
||||
import { FC } from "react"; |
|
||||
import DatePicker from "react-datepicker"; |
|
||||
import { Popover, Transition } from "@headlessui/react"; |
|
||||
import { CalendarIcon } from "@heroicons/react/24/outline"; |
|
||||
import DatePickerCustomHeaderTwoMonth from "@/components/DatePickerCustomHeaderTwoMonth"; |
|
||||
import DatePickerCustomDay from "@/components/DatePickerCustomDay"; |
|
||||
import ClearDataButton from "../ClearDataButton"; |
|
||||
|
|
||||
export interface ExperiencesDateSingleInputProps { |
|
||||
className?: string; |
|
||||
fieldClassName?: string; |
|
||||
} |
|
||||
|
|
||||
const ExperiencesDateSingleInput: FC<ExperiencesDateSingleInputProps> = ({ |
|
||||
className = "", |
|
||||
fieldClassName = "[ nc-hero-field-padding ]", |
|
||||
}) => { |
|
||||
const [startDate, setStartDate] = useState<Date | null>( |
|
||||
new Date("2023/03/01") |
|
||||
); |
|
||||
const [endDate, setEndDate] = useState<Date | null>(new Date("2023/03/16")); |
|
||||
|
|
||||
const onChangeDate = (dates: [Date | null, Date | null]) => { |
|
||||
const [start, end] = dates; |
|
||||
setStartDate(start); |
|
||||
setEndDate(end); |
|
||||
}; |
|
||||
|
|
||||
const renderInput = () => { |
|
||||
return ( |
|
||||
<> |
|
||||
<div className="text-neutral-300 dark:text-neutral-400"> |
|
||||
<CalendarIcon className="w-5 h-5 lg:w-7 lg:h-7" /> |
|
||||
</div> |
|
||||
<div className="flex-grow text-left"> |
|
||||
<span className="block xl:text-lg font-semibold"> |
|
||||
{startDate?.toLocaleDateString("en-US", { |
|
||||
month: "short", |
|
||||
day: "2-digit", |
|
||||
}) || "Date"} |
|
||||
{endDate |
|
||||
? " - " + |
|
||||
endDate?.toLocaleDateString("en-US", { |
|
||||
month: "short", |
|
||||
day: "2-digit", |
|
||||
}) |
|
||||
: ""} |
|
||||
</span> |
|
||||
<span className="block mt-1 text-sm text-neutral-400 leading-none font-light"> |
|
||||
{startDate ? "Date" : `Add dates`} |
|
||||
</span> |
|
||||
</div> |
|
||||
</> |
|
||||
); |
|
||||
}; |
|
||||
|
|
||||
return ( |
|
||||
<> |
|
||||
<Popover |
|
||||
className={`ExperiencesDateSingleInput relative flex ${className}`} |
|
||||
> |
|
||||
{({ open }) => ( |
|
||||
<> |
|
||||
<Popover.Button |
|
||||
className={`flex-1 z-10 flex relative ${fieldClassName} items-center space-x-3 focus:outline-none ${ |
|
||||
open ? "nc-hero-field-focused" : "" |
|
||||
}`}
|
|
||||
> |
|
||||
{renderInput()} |
|
||||
{startDate && open && ( |
|
||||
<ClearDataButton onClick={() => onChangeDate([null, null])} /> |
|
||||
)} |
|
||||
</Popover.Button> |
|
||||
|
|
||||
{open && ( |
|
||||
<div className="h-8 absolute self-center top-1/2 -translate-y-1/2 z-0 -inset-x-0.5 bg-white dark:bg-neutral-800"></div> |
|
||||
)} |
|
||||
|
|
||||
<Transition |
|
||||
as={Fragment} |
|
||||
enter="transition ease-out duration-200" |
|
||||
enterFrom="opacity-0 translate-y-1" |
|
||||
enterTo="opacity-100 translate-y-0" |
|
||||
leave="transition ease-in duration-150" |
|
||||
leaveFrom="opacity-100 translate-y-0" |
|
||||
leaveTo="opacity-0 translate-y-1" |
|
||||
> |
|
||||
<Popover.Panel className="absolute left-1/2 z-10 mt-3 top-full w-screen max-w-sm -translate-x-1/2 transform px-4 sm:px-0 lg:max-w-3xl"> |
|
||||
<div className="overflow-hidden rounded-3xl shadow-lg ring-1 ring-black ring-opacity-5 bg-white dark:bg-neutral-800 p-8"> |
|
||||
<DatePicker |
|
||||
selected={startDate} |
|
||||
onChange={onChangeDate} |
|
||||
startDate={startDate} |
|
||||
endDate={endDate} |
|
||||
selectsRange |
|
||||
monthsShown={2} |
|
||||
showPopperArrow={false} |
|
||||
inline |
|
||||
renderCustomHeader={(p) => ( |
|
||||
<DatePickerCustomHeaderTwoMonth {...p} /> |
|
||||
)} |
|
||||
renderDayContents={(day, date) => ( |
|
||||
<DatePickerCustomDay dayOfMonth={day} date={date} /> |
|
||||
)} |
|
||||
/> |
|
||||
</div> |
|
||||
</Popover.Panel> |
|
||||
</Transition> |
|
||||
</> |
|
||||
)} |
|
||||
</Popover> |
|
||||
</> |
|
||||
); |
|
||||
}; |
|
||||
|
|
||||
export default ExperiencesDateSingleInput; |
|
@ -1,27 +0,0 @@ |
|||||
import React, { FC } from "react"; |
|
||||
import LocationInput from "../LocationInput"; |
|
||||
import GuestsInput from "../GuestsInput"; |
|
||||
import ExperiencesDateSingleInput from "./ExperiencesDateSingleInput"; |
|
||||
|
|
||||
export interface ExperiencesSearchFormProps {} |
|
||||
|
|
||||
const ExperiencesSearchForm: FC<ExperiencesSearchFormProps> = ({}) => { |
|
||||
const renderForm = () => { |
|
||||
return ( |
|
||||
<form className="w-full relative mt-8 flex flex-col md:flex-row rounded-3xl md:rounded-full shadow-xl dark:shadow-2xl bg-white dark:bg-neutral-800 "> |
|
||||
<LocationInput className="flex-[1.5]" /> |
|
||||
<div className="self-center border-r border-slate-200 dark:border-slate-700 h-8"></div> |
|
||||
<ExperiencesDateSingleInput className="flex-1" /> |
|
||||
<div className="self-center border-r border-slate-200 dark:border-slate-700 h-8"></div> |
|
||||
<GuestsInput |
|
||||
className="flex-1" |
|
||||
buttonSubmitHref="/listing-experiences" |
|
||||
/> |
|
||||
</form> |
|
||||
); |
|
||||
}; |
|
||||
|
|
||||
return renderForm(); |
|
||||
}; |
|
||||
|
|
||||
export default ExperiencesSearchForm; |
|
@ -1,152 +0,0 @@ |
|||||
"use client"; |
|
||||
|
|
||||
import React, { Fragment, useState } from "react"; |
|
||||
import { FC } from "react"; |
|
||||
import DatePicker from "react-datepicker"; |
|
||||
import { Popover, Transition } from "@headlessui/react"; |
|
||||
import { CalendarIcon } from "@heroicons/react/24/outline"; |
|
||||
import DatePickerCustomHeaderTwoMonth from "@/components/DatePickerCustomHeaderTwoMonth"; |
|
||||
import DatePickerCustomDay from "@/components/DatePickerCustomDay"; |
|
||||
import ClearDataButton from "../ClearDataButton"; |
|
||||
import ButtonSubmit from "../ButtonSubmit"; |
|
||||
|
|
||||
export interface FlightDateRangeInputProps { |
|
||||
className?: string; |
|
||||
fieldClassName?: string; |
|
||||
hasButtonSubmit?: boolean; |
|
||||
selectsRange?: boolean; |
|
||||
} |
|
||||
|
|
||||
const FlightDateRangeInput: FC<FlightDateRangeInputProps> = ({ |
|
||||
className = "", |
|
||||
fieldClassName = "[ nc-hero-field-padding ]", |
|
||||
hasButtonSubmit = true, |
|
||||
selectsRange = true, |
|
||||
}) => { |
|
||||
const [startDate, setStartDate] = useState<Date | null>( |
|
||||
new Date("2023/05/01") |
|
||||
); |
|
||||
const [endDate, setEndDate] = useState<Date | null>(new Date("2023/05/16")); |
|
||||
|
|
||||
const onChangeRangeDate = (dates: [Date | null, Date | null]) => { |
|
||||
const [start, end] = dates; |
|
||||
setStartDate(start); |
|
||||
setEndDate(end); |
|
||||
}; |
|
||||
|
|
||||
const renderInput = () => { |
|
||||
return ( |
|
||||
<> |
|
||||
<div className="text-neutral-300 dark:text-neutral-400"> |
|
||||
<CalendarIcon className="w-5 h-5 lg:w-7 lg:h-7" /> |
|
||||
</div> |
|
||||
<div className="flex-grow text-left"> |
|
||||
<span className="block xl:text-lg font-semibold"> |
|
||||
{startDate?.toLocaleDateString("en-US", { |
|
||||
month: "short", |
|
||||
day: "2-digit", |
|
||||
}) || "Add dates"} |
|
||||
{selectsRange && endDate |
|
||||
? " - " + |
|
||||
endDate?.toLocaleDateString("en-US", { |
|
||||
month: "short", |
|
||||
day: "2-digit", |
|
||||
}) |
|
||||
: ""} |
|
||||
</span> |
|
||||
<span className="block mt-1 text-sm text-neutral-400 leading-none font-light"> |
|
||||
{selectsRange ? "Pick up - Drop off" : "Pick up date"} |
|
||||
</span> |
|
||||
</div> |
|
||||
</> |
|
||||
); |
|
||||
}; |
|
||||
|
|
||||
return ( |
|
||||
<> |
|
||||
<Popover className={`FlightDateRangeInput relative flex ${className}`}> |
|
||||
{({ open }) => ( |
|
||||
<> |
|
||||
<div |
|
||||
className={`flex-1 z-10 flex items-center focus:outline-none ${ |
|
||||
open ? "nc-hero-field-focused" : "" |
|
||||
}`}
|
|
||||
> |
|
||||
<Popover.Button |
|
||||
className={`flex-1 z-10 flex relative ${fieldClassName} items-center space-x-3 focus:outline-none `} |
|
||||
> |
|
||||
{renderInput()} |
|
||||
|
|
||||
{startDate && open && ( |
|
||||
<ClearDataButton |
|
||||
onClick={() => onChangeRangeDate([null, null])} |
|
||||
/> |
|
||||
)} |
|
||||
</Popover.Button> |
|
||||
|
|
||||
{/* BUTTON SUBMIT OF FORM */} |
|
||||
{hasButtonSubmit && ( |
|
||||
<div className="pr-2 xl:pr-4"> |
|
||||
<ButtonSubmit href="/listing-car-detail" /> |
|
||||
</div> |
|
||||
)} |
|
||||
</div> |
|
||||
|
|
||||
{open && ( |
|
||||
<div className="h-8 absolute self-center top-1/2 -translate-y-1/2 z-0 -left-0.5 right-10 bg-white dark:bg-neutral-800"></div> |
|
||||
)} |
|
||||
|
|
||||
<Transition |
|
||||
as={Fragment} |
|
||||
enter="transition ease-out duration-200" |
|
||||
enterFrom="opacity-0 translate-y-1" |
|
||||
enterTo="opacity-100 translate-y-0" |
|
||||
leave="transition ease-in duration-150" |
|
||||
leaveFrom="opacity-100 translate-y-0" |
|
||||
leaveTo="opacity-0 translate-y-1" |
|
||||
> |
|
||||
<Popover.Panel className="absolute left-1/2 z-20 mt-3 top-full w-screen max-w-sm -translate-x-1/2 transform px-4 sm:px-0 lg:max-w-3xl"> |
|
||||
<div className="overflow-hidden rounded-3xl shadow-lg ring-1 ring-black ring-opacity-5 bg-white dark:bg-neutral-800 p-8"> |
|
||||
{selectsRange ? ( |
|
||||
<DatePicker |
|
||||
selected={startDate} |
|
||||
onChange={onChangeRangeDate} |
|
||||
startDate={startDate} |
|
||||
endDate={endDate} |
|
||||
selectsRange |
|
||||
monthsShown={2} |
|
||||
showPopperArrow={false} |
|
||||
inline |
|
||||
renderCustomHeader={(p) => ( |
|
||||
<DatePickerCustomHeaderTwoMonth {...p} /> |
|
||||
)} |
|
||||
renderDayContents={(day, date) => ( |
|
||||
<DatePickerCustomDay dayOfMonth={day} date={date} /> |
|
||||
)} |
|
||||
/> |
|
||||
) : ( |
|
||||
<DatePicker |
|
||||
selected={startDate} |
|
||||
onChange={(date) => setStartDate(date)} |
|
||||
monthsShown={2} |
|
||||
showPopperArrow={false} |
|
||||
inline |
|
||||
renderCustomHeader={(p) => ( |
|
||||
<DatePickerCustomHeaderTwoMonth {...p} /> |
|
||||
)} |
|
||||
renderDayContents={(day, date) => ( |
|
||||
<DatePickerCustomDay dayOfMonth={day} date={date} /> |
|
||||
)} |
|
||||
/> |
|
||||
)} |
|
||||
</div> |
|
||||
</Popover.Panel> |
|
||||
</Transition> |
|
||||
</> |
|
||||
)} |
|
||||
</Popover> |
|
||||
</> |
|
||||
); |
|
||||
}; |
|
||||
|
|
||||
export default FlightDateRangeInput; |
|
@ -1,246 +0,0 @@ |
|||||
"use client"; |
|
||||
|
|
||||
import React, { FC, useState } from "react"; |
|
||||
import LocationInput from "../LocationInput"; |
|
||||
import { Popover, Transition } from "@headlessui/react"; |
|
||||
import { ChevronDownIcon } from "@heroicons/react/24/solid"; |
|
||||
import { Fragment } from "react"; |
|
||||
import NcInputNumber from "@/components/NcInputNumber"; |
|
||||
import FlightDateRangeInput from "./FlightDateRangeInput"; |
|
||||
import { GuestsObject } from "../../type"; |
|
||||
|
|
||||
export interface FlightSearchFormProps {} |
|
||||
|
|
||||
const flightClass = [ |
|
||||
{ |
|
||||
name: "Economy", |
|
||||
href: "##", |
|
||||
}, |
|
||||
{ |
|
||||
name: "Business", |
|
||||
href: "##", |
|
||||
}, |
|
||||
{ |
|
||||
name: "Multiple", |
|
||||
href: "##", |
|
||||
}, |
|
||||
]; |
|
||||
|
|
||||
export type TypeDropOffLocationType = "roundTrip" | "oneWay" | ""; |
|
||||
|
|
||||
const FlightSearchForm: FC<FlightSearchFormProps> = ({}) => { |
|
||||
const [dropOffLocationType, setDropOffLocationType] = |
|
||||
useState<TypeDropOffLocationType>("roundTrip"); |
|
||||
const [flightClassState, setFlightClassState] = useState("Economy"); |
|
||||
|
|
||||
const [guestAdultsInputValue, setGuestAdultsInputValue] = useState(2); |
|
||||
const [guestChildrenInputValue, setGuestChildrenInputValue] = useState(1); |
|
||||
const [guestInfantsInputValue, setGuestInfantsInputValue] = useState(1); |
|
||||
|
|
||||
const handleChangeData = (value: number, type: keyof GuestsObject) => { |
|
||||
let newValue = { |
|
||||
guestAdults: guestAdultsInputValue, |
|
||||
guestChildren: guestChildrenInputValue, |
|
||||
guestInfants: guestInfantsInputValue, |
|
||||
}; |
|
||||
if (type === "guestAdults") { |
|
||||
setGuestAdultsInputValue(value); |
|
||||
newValue.guestAdults = value; |
|
||||
} |
|
||||
if (type === "guestChildren") { |
|
||||
setGuestChildrenInputValue(value); |
|
||||
newValue.guestChildren = value; |
|
||||
} |
|
||||
if (type === "guestInfants") { |
|
||||
setGuestInfantsInputValue(value); |
|
||||
newValue.guestInfants = value; |
|
||||
} |
|
||||
}; |
|
||||
|
|
||||
const totalGuests = |
|
||||
guestChildrenInputValue + guestAdultsInputValue + guestInfantsInputValue; |
|
||||
|
|
||||
const renderGuest = () => { |
|
||||
return ( |
|
||||
<Popover className="relative"> |
|
||||
{({ open }) => ( |
|
||||
<> |
|
||||
<Popover.Button |
|
||||
as="button" |
|
||||
className={`
|
|
||||
${open ? "" : ""} |
|
||||
px-4 py-1.5 rounded-md inline-flex items-center font-medium hover:text-opacity-100 focus:outline-none focus-visible:ring-2 focus-visible:ring-white focus-visible:ring-opacity-75 text-xs`}
|
|
||||
> |
|
||||
<span>{`${totalGuests || ""} Guests`}</span> |
|
||||
<ChevronDownIcon |
|
||||
className={`${ |
|
||||
open ? "" : "text-opacity-70" |
|
||||
} ml-2 h-4 w-4 group-hover:text-opacity-80 transition ease-in-out duration-150`}
|
|
||||
aria-hidden="true" |
|
||||
/> |
|
||||
</Popover.Button> |
|
||||
<Transition |
|
||||
as={Fragment} |
|
||||
enter="transition ease-out duration-200" |
|
||||
enterFrom="opacity-0 translate-y-1" |
|
||||
enterTo="opacity-100 translate-y-0" |
|
||||
leave="transition ease-in duration-150" |
|
||||
leaveFrom="opacity-100 translate-y-0" |
|
||||
leaveTo="opacity-0 translate-y-1" |
|
||||
> |
|
||||
<Popover.Panel className="absolute z-20 w-full sm:min-w-[340px] max-w-sm bg-white dark:bg-neutral-800 top-full mt-3 left-1/2 -translate-x-1/2 py-5 sm:py-6 px-4 sm:px-8 rounded-3xl shadow-xl ring-1 ring-black/5 dark:ring-white/10"> |
|
||||
<NcInputNumber |
|
||||
className="w-full" |
|
||||
defaultValue={guestAdultsInputValue} |
|
||||
onChange={(value) => handleChangeData(value, "guestAdults")} |
|
||||
max={10} |
|
||||
min={1} |
|
||||
label="Adults" |
|
||||
desc="Ages 13 or above" |
|
||||
/> |
|
||||
<NcInputNumber |
|
||||
className="w-full mt-6" |
|
||||
defaultValue={guestChildrenInputValue} |
|
||||
onChange={(value) => handleChangeData(value, "guestChildren")} |
|
||||
max={4} |
|
||||
label="Children" |
|
||||
desc="Ages 2–12" |
|
||||
/> |
|
||||
|
|
||||
<NcInputNumber |
|
||||
className="w-full mt-6" |
|
||||
defaultValue={guestInfantsInputValue} |
|
||||
onChange={(value) => handleChangeData(value, "guestInfants")} |
|
||||
max={4} |
|
||||
label="Infants" |
|
||||
desc="Ages 0–2" |
|
||||
/> |
|
||||
</Popover.Panel> |
|
||||
</Transition> |
|
||||
</> |
|
||||
)} |
|
||||
</Popover> |
|
||||
); |
|
||||
}; |
|
||||
|
|
||||
const renderSelectClass = () => { |
|
||||
return ( |
|
||||
<Popover className="relative"> |
|
||||
{({ open, close }) => ( |
|
||||
<> |
|
||||
<Popover.Button |
|
||||
className={`
|
|
||||
${open ? "" : ""} |
|
||||
px-4 py-1.5 rounded-md inline-flex items-center font-medium hover:text-opacity-100 focus:outline-none focus-visible:ring-2 focus-visible:ring-white focus-visible:ring-opacity-75 text-xs`}
|
|
||||
> |
|
||||
<span>{`${flightClassState}`}</span> |
|
||||
<ChevronDownIcon |
|
||||
className={`${ |
|
||||
open ? "" : "text-opacity-70" |
|
||||
} ml-2 h-4 w-4 group-hover:text-opacity-80 transition ease-in-out duration-150`}
|
|
||||
aria-hidden="true" |
|
||||
/> |
|
||||
</Popover.Button> |
|
||||
<Transition |
|
||||
as={Fragment} |
|
||||
enter="transition ease-out duration-200" |
|
||||
enterFrom="opacity-0 translate-y-1" |
|
||||
enterTo="opacity-100 translate-y-0" |
|
||||
leave="transition ease-in duration-150" |
|
||||
leaveFrom="opacity-100 translate-y-0" |
|
||||
leaveTo="opacity-0 translate-y-1" |
|
||||
> |
|
||||
<Popover.Panel className="absolute z-20 w-screen max-w-[200px] sm:max-w-[220px] px-4 top-full mt-3 transform -translate-x-1/2 left-1/2 sm:px-0 "> |
|
||||
<div className="overflow-hidden rounded-2xl shadow-lg ring-1 ring-black/5 dark:ring-white/10 "> |
|
||||
<div className="relative grid gap-8 bg-white dark:bg-neutral-800 p-7 "> |
|
||||
{flightClass.map((item) => ( |
|
||||
<a |
|
||||
key={item.name} |
|
||||
href={item.href} |
|
||||
onClick={(e) => { |
|
||||
e.preventDefault(); |
|
||||
setFlightClassState(item.name); |
|
||||
close(); |
|
||||
}} |
|
||||
className="flex items-center p-2 -m-3 transition duration-150 ease-in-out rounded-lg hover:bg-gray-50 dark:hover:bg-gray-700 focus:outline-none focus-visible:ring focus-visible:ring-orange-500 focus-visible:ring-opacity-50" |
|
||||
> |
|
||||
<p className="text-sm font-medium ">{item.name}</p> |
|
||||
</a> |
|
||||
))} |
|
||||
</div> |
|
||||
</div> |
|
||||
</Popover.Panel> |
|
||||
</Transition> |
|
||||
</> |
|
||||
)} |
|
||||
</Popover> |
|
||||
); |
|
||||
}; |
|
||||
|
|
||||
const renderRadioBtn = () => { |
|
||||
return ( |
|
||||
<div className=" py-5 [ nc-hero-field-padding ] flex flex-row flex-wrap border-b border-neutral-100 dark:border-neutral-700"> |
|
||||
<div |
|
||||
className={`py-1.5 px-4 flex items-center rounded-full font-medium text-xs cursor-pointer mr-2 my-1 sm:mr-3 ${ |
|
||||
dropOffLocationType === "roundTrip" |
|
||||
? "bg-black shadow-black/10 shadow-lg text-white" |
|
||||
: "border border-neutral-300 dark:border-neutral-700" |
|
||||
}`}
|
|
||||
onClick={(e) => setDropOffLocationType("roundTrip")} |
|
||||
> |
|
||||
Round-trip |
|
||||
</div> |
|
||||
<div |
|
||||
className={`py-1.5 px-4 flex items-center rounded-full font-medium text-xs cursor-pointer mr-2 my-1 sm:mr-3 ${ |
|
||||
dropOffLocationType === "oneWay" |
|
||||
? "bg-black text-white shadow-black/10 shadow-lg" |
|
||||
: "border border-neutral-300 dark:border-neutral-700" |
|
||||
}`}
|
|
||||
onClick={(e) => setDropOffLocationType("oneWay")} |
|
||||
> |
|
||||
One-way |
|
||||
</div> |
|
||||
|
|
||||
<div className="self-center border-r border-slate-200 dark:border-slate-700 h-8 mr-2 my-1 sm:mr-3"></div> |
|
||||
|
|
||||
<div className="mr-2 my-1 sm:mr-3 border border-neutral-300 dark:border-neutral-700 rounded-full"> |
|
||||
{renderSelectClass()} |
|
||||
</div> |
|
||||
<div className="my-1 border border-neutral-300 dark:border-neutral-700 rounded-full"> |
|
||||
{renderGuest()} |
|
||||
</div> |
|
||||
</div> |
|
||||
); |
|
||||
}; |
|
||||
|
|
||||
const renderForm = () => { |
|
||||
return ( |
|
||||
<form className="w-full relative mt-8 rounded-[40px] xl:rounded-[49px] rounded-t-2xl xl:rounded-t-3xl shadow-xl dark:shadow-2xl bg-white dark:bg-neutral-800"> |
|
||||
{renderRadioBtn()} |
|
||||
<div className="flex flex-1 rounded-full"> |
|
||||
<LocationInput |
|
||||
placeHolder="Flying from" |
|
||||
desc="Where do you want to fly from?" |
|
||||
className="flex-1" |
|
||||
/> |
|
||||
<div className="self-center border-r border-slate-200 dark:border-slate-700 h-8"></div> |
|
||||
<LocationInput |
|
||||
placeHolder="Flying to" |
|
||||
desc="Where you want to fly to?" |
|
||||
className="flex-1" |
|
||||
divHideVerticalLineClass=" -inset-x-0.5" |
|
||||
/> |
|
||||
<div className="self-center border-r border-slate-200 dark:border-slate-700 h-8"></div> |
|
||||
<FlightDateRangeInput |
|
||||
selectsRange={dropOffLocationType !== "oneWay"} |
|
||||
className="flex-1" |
|
||||
/> |
|
||||
</div> |
|
||||
</form> |
|
||||
); |
|
||||
}; |
|
||||
|
|
||||
return renderForm(); |
|
||||
}; |
|
||||
|
|
||||
export default FlightSearchForm; |
|
@ -1,66 +0,0 @@ |
|||||
"use client"; |
|
||||
|
|
||||
import React, { FC, useState } from "react"; |
|
||||
import RealEstateSearchForm from "./RealEstateSearchForm"; |
|
||||
|
|
||||
export type SearchRealEstateTab = "Buy" | "Rent" | "Sell"; |
|
||||
|
|
||||
export interface HeroRealEstateSearchFormProps { |
|
||||
className?: string; |
|
||||
currentTab?: SearchRealEstateTab; |
|
||||
} |
|
||||
|
|
||||
const HeroRealEstateSearchForm: FC<HeroRealEstateSearchFormProps> = ({ |
|
||||
className = "", |
|
||||
currentTab = "Buy", |
|
||||
}) => { |
|
||||
const tabs: SearchRealEstateTab[] = ["Buy", "Rent", "Sell"]; |
|
||||
const [tabActive, setTabActive] = useState<SearchRealEstateTab>(currentTab); |
|
||||
|
|
||||
const renderTab = () => { |
|
||||
return ( |
|
||||
<ul className="ml-6 md:ml-16 xl:ml-20 inline-flex space-x-4 sm:space-x-8 lg:space-x-10 bg-white dark:bg-neutral-900 pb-6 md:p-6 !pl-0 xl:p-0 rounded-t-3xl"> |
|
||||
{tabs.map((tab) => { |
|
||||
const active = tab === tabActive; |
|
||||
return ( |
|
||||
<li |
|
||||
onClick={() => setTabActive(tab)} |
|
||||
className={`flex items-center cursor-pointer text-sm lg:text-base font-medium ${ |
|
||||
active |
|
||||
? "" |
|
||||
: "text-neutral-500 hover:text-neutral-700 dark:hover:text-neutral-100" |
|
||||
} `}
|
|
||||
key={tab} |
|
||||
> |
|
||||
{active && ( |
|
||||
<span className="block w-2.5 h-2.5 rounded-full bg-neutral-800 dark:bg-neutral-100 mr-2" /> |
|
||||
)} |
|
||||
<span>{tab}</span> |
|
||||
</li> |
|
||||
); |
|
||||
})} |
|
||||
</ul> |
|
||||
); |
|
||||
}; |
|
||||
|
|
||||
const renderForm = () => { |
|
||||
switch (tabActive) { |
|
||||
case "Buy": |
|
||||
return <RealEstateSearchForm />; |
|
||||
|
|
||||
default: |
|
||||
return <RealEstateSearchForm />; |
|
||||
} |
|
||||
}; |
|
||||
|
|
||||
return ( |
|
||||
<div |
|
||||
className={`nc-HeroRealEstateSearchForm w-full max-w-6xl py-5 lg:py-0 ${className}`} |
|
||||
> |
|
||||
{renderTab()} |
|
||||
{renderForm()} |
|
||||
</div> |
|
||||
); |
|
||||
}; |
|
||||
|
|
||||
export default HeroRealEstateSearchForm; |
|
@ -1,137 +0,0 @@ |
|||||
"use client"; |
|
||||
|
|
||||
import React, { Fragment, useState, FC } from "react"; |
|
||||
import { Popover, Transition } from "@headlessui/react"; |
|
||||
import Slider from "rc-slider"; |
|
||||
import convertNumbThousand from "@/utils/convertNumbThousand"; |
|
||||
import ButtonSubmit from "../ButtonSubmit"; |
|
||||
import { CurrencyDollarIcon } from "@heroicons/react/24/outline"; |
|
||||
|
|
||||
export interface PriceRangeInputProps { |
|
||||
onChange?: (data: any) => void; |
|
||||
fieldClassName?: string; |
|
||||
} |
|
||||
|
|
||||
const PriceRangeInput: FC<PriceRangeInputProps> = ({ |
|
||||
onChange, |
|
||||
fieldClassName = "[ nc-hero-field-padding ]", |
|
||||
}) => { |
|
||||
const [rangePrices, setRangePrices] = useState([100000, 4000000]); |
|
||||
|
|
||||
return ( |
|
||||
<Popover className="flex relative flex-[1.3]"> |
|
||||
{({ open, close }) => ( |
|
||||
<> |
|
||||
<div |
|
||||
className={`flex-1 z-10 flex items-center focus:outline-none cursor-pointer ${ |
|
||||
open ? "nc-hero-field-focused" : "" |
|
||||
}`}
|
|
||||
> |
|
||||
<Popover.Button |
|
||||
className={`flex-1 flex text-left items-center focus:outline-none ${fieldClassName} space-x-3 `} |
|
||||
onClickCapture={() => document.querySelector("html")?.click()} |
|
||||
> |
|
||||
<div className="text-neutral-300 dark:text-neutral-400"> |
|
||||
<CurrencyDollarIcon className="w-5 h-5 lg:w-7 lg:h-7" /> |
|
||||
</div> |
|
||||
<div className="flex-grow"> |
|
||||
<span className="block xl:text-lg font-semibold truncate"> |
|
||||
{`$${convertNumbThousand( |
|
||||
rangePrices[0] / 1000 |
|
||||
)}k ~ $${convertNumbThousand(rangePrices[1] / 1000)}k`}
|
|
||||
</span> |
|
||||
<span className="block mt-1 text-sm text-neutral-400 leading-none font-light "> |
|
||||
Choose price range |
|
||||
</span> |
|
||||
</div> |
|
||||
</Popover.Button> |
|
||||
|
|
||||
{/* BUTTON SUBMIT OF FORM */} |
|
||||
<div className="pr-2 xl:pr-4"> |
|
||||
<ButtonSubmit href="/listing-real-estate" /> |
|
||||
</div> |
|
||||
</div> |
|
||||
|
|
||||
{open && ( |
|
||||
<div className="h-8 absolute self-center top-1/2 -translate-y-1/2 z-0 -left-0.5 right-1 bg-white dark:bg-neutral-800"></div> |
|
||||
)} |
|
||||
|
|
||||
<Transition |
|
||||
as={Fragment} |
|
||||
enter="transition ease-out duration-200" |
|
||||
enterFrom="opacity-0 translate-y-1" |
|
||||
enterTo="opacity-100 translate-y-0" |
|
||||
leave="transition ease-in duration-150" |
|
||||
leaveFrom="opacity-100 translate-y-0" |
|
||||
leaveTo="opacity-0 translate-y-1" |
|
||||
> |
|
||||
<Popover.Panel className="absolute left-0 lg:right-0 z-10 w-full sm:min-w-[340px] max-w-sm bg-white dark:bg-neutral-800 top-full mt-3 py-5 sm:py-6 px-4 sm:px-8 rounded-3xl shadow-xl"> |
|
||||
<div className="relative flex flex-col space-y-8"> |
|
||||
<div className="space-y-5"> |
|
||||
<span className="font-medium">Range Price </span> |
|
||||
<Slider |
|
||||
range |
|
||||
className="text-red-400" |
|
||||
min={10000} |
|
||||
max={10000000} |
|
||||
defaultValue={[rangePrices[0], rangePrices[1]]} |
|
||||
allowCross={false} |
|
||||
step={1000} |
|
||||
onChange={(e) => setRangePrices(e as number[])} |
|
||||
/> |
|
||||
</div> |
|
||||
|
|
||||
<div className="flex justify-between space-x-3"> |
|
||||
<div> |
|
||||
<label |
|
||||
htmlFor="minPrice" |
|
||||
className="block text-sm font-medium text-neutral-700 dark:text-neutral-300" |
|
||||
> |
|
||||
Min price |
|
||||
</label> |
|
||||
<div className="mt-1 relative rounded-md"> |
|
||||
<div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none"> |
|
||||
<span className="text-neutral-500 sm:text-sm">$</span> |
|
||||
</div> |
|
||||
<input |
|
||||
type="text" |
|
||||
disabled |
|
||||
name="minPrice" |
|
||||
id="minPrice" |
|
||||
className="focus:ring-primary-500 focus:border-primary-500 block w-full pl-7 pr-3 sm:text-sm border-neutral-200 dark:border-neutral-500 rounded-full text-neutral-900 dark:text-neutral-200 bg-transparent" |
|
||||
value={convertNumbThousand(rangePrices[0])} |
|
||||
/> |
|
||||
</div> |
|
||||
</div> |
|
||||
<div> |
|
||||
<label |
|
||||
htmlFor="maxPrice" |
|
||||
className="block text-sm font-medium text-neutral-700 dark:text-neutral-300" |
|
||||
> |
|
||||
Max price |
|
||||
</label> |
|
||||
<div className="mt-1 relative rounded-md"> |
|
||||
<div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none"> |
|
||||
<span className="text-neutral-500 sm:text-sm">$</span> |
|
||||
</div> |
|
||||
<input |
|
||||
disabled |
|
||||
type="text" |
|
||||
name="maxPrice" |
|
||||
id="maxPrice" |
|
||||
className="focus:ring-primary-500 focus:border-priring-primary-500 block w-full pl-7 pr-3 sm:text-sm border-neutral-200 dark:border-neutral-500 rounded-full text-neutral-900 dark:text-neutral-200 bg-transparent" |
|
||||
value={convertNumbThousand(rangePrices[1])} |
|
||||
/> |
|
||||
</div> |
|
||||
</div> |
|
||||
</div> |
|
||||
</div> |
|
||||
</Popover.Panel> |
|
||||
</Transition> |
|
||||
</> |
|
||||
)} |
|
||||
</Popover> |
|
||||
); |
|
||||
}; |
|
||||
|
|
||||
export default PriceRangeInput; |
|
@ -1,126 +0,0 @@ |
|||||
"use client"; |
|
||||
import React, { Fragment, FC } from "react"; |
|
||||
import { Popover, Transition } from "@headlessui/react"; |
|
||||
import Checkbox from "@/shared/Checkbox"; |
|
||||
import { ClassOfProperties } from "../../type"; |
|
||||
import { HomeIcon } from "@heroicons/react/24/outline"; |
|
||||
|
|
||||
const defaultPropertyType: ClassOfProperties[] = [ |
|
||||
{ |
|
||||
name: "Duplex House", |
|
||||
description: "Have a place to yourself", |
|
||||
checked: true, |
|
||||
}, |
|
||||
{ |
|
||||
name: "Ferme House", |
|
||||
description: "Have your own room and share some common spaces", |
|
||||
checked: false, |
|
||||
}, |
|
||||
{ |
|
||||
name: "Chalet House", |
|
||||
description: |
|
||||
"Have a private or shared room in a boutique hotel, hostel, and more", |
|
||||
checked: false, |
|
||||
}, |
|
||||
{ |
|
||||
name: "Maison House", |
|
||||
description: "Stay in a shared space, like a common room", |
|
||||
checked: false, |
|
||||
}, |
|
||||
]; |
|
||||
|
|
||||
export interface PropertyTypeSelectProps { |
|
||||
onChange?: (data: any) => void; |
|
||||
fieldClassName?: string; |
|
||||
} |
|
||||
|
|
||||
const PropertyTypeSelect: FC<PropertyTypeSelectProps> = ({ |
|
||||
onChange, |
|
||||
fieldClassName = "[ nc-hero-field-padding ]", |
|
||||
}) => { |
|
||||
const [typeOfProperty, setTypeOfProperty] = |
|
||||
React.useState<ClassOfProperties[]>(defaultPropertyType); |
|
||||
|
|
||||
let typeOfPropertyText = ""; |
|
||||
if (typeOfProperty && typeOfProperty.length > 0) { |
|
||||
typeOfPropertyText = typeOfProperty |
|
||||
.filter((item) => item.checked) |
|
||||
.map((item) => { |
|
||||
return item.name; |
|
||||
}) |
|
||||
.join(", "); |
|
||||
} |
|
||||
return ( |
|
||||
<Popover className="flex relative flex-1"> |
|
||||
{({ open, close }) => ( |
|
||||
<> |
|
||||
<Popover.Button |
|
||||
className={`flex z-10 text-left w-full flex-shrink-0 items-center ${fieldClassName} space-x-3 focus:outline-none cursor-pointer ${ |
|
||||
open ? "nc-hero-field-focused" : "" |
|
||||
}`}
|
|
||||
onClickCapture={() => document.querySelector("html")?.click()} |
|
||||
> |
|
||||
<div className="text-neutral-300 dark:text-neutral-400"> |
|
||||
<HomeIcon className="w-5 h-5 lg:w-7 lg:h-7" /> |
|
||||
</div> |
|
||||
<div className="flex-1"> |
|
||||
<span className="block xl:text-lg font-semibold overflow-hidden"> |
|
||||
<span className="line-clamp-1"> |
|
||||
{typeOfPropertyText || `Type`} |
|
||||
</span> |
|
||||
</span> |
|
||||
<span className="block mt-1 text-sm text-neutral-400 leading-none font-light "> |
|
||||
Property type |
|
||||
</span> |
|
||||
</div> |
|
||||
</Popover.Button> |
|
||||
|
|
||||
{open && ( |
|
||||
<div className="h-8 absolute self-center top-1/2 -translate-y-1/2 z-0 -inset-x-0.5 bg-white dark:bg-neutral-800"></div> |
|
||||
)} |
|
||||
|
|
||||
<Transition |
|
||||
as={Fragment} |
|
||||
enter="transition ease-out duration-200" |
|
||||
enterFrom="opacity-0 translate-y-1" |
|
||||
enterTo="opacity-100 translate-y-0" |
|
||||
leave="transition ease-in duration-150" |
|
||||
leaveFrom="opacity-100 translate-y-0" |
|
||||
leaveTo="opacity-0 translate-y-1" |
|
||||
> |
|
||||
<Popover.Panel className="absolute left-0 z-10 w-full sm:min-w-[340px] max-w-sm bg-white dark:bg-neutral-800 top-full mt-3 py-5 sm:py-6 px-4 sm:px-8 rounded-3xl shadow-xl"> |
|
||||
<div className=""> |
|
||||
<div className="relative flex flex-col space-y-5"> |
|
||||
{typeOfProperty.map((item, index) => ( |
|
||||
<div key={item.name} className=""> |
|
||||
<Checkbox |
|
||||
name={item.name} |
|
||||
label={item.name} |
|
||||
subLabel={item.description} |
|
||||
defaultChecked={typeOfProperty[index].checked} |
|
||||
onChange={(e) => { |
|
||||
const newState = typeOfProperty.map((item, i) => { |
|
||||
if (i === index) { |
|
||||
return { ...item, checked: e }; |
|
||||
} |
|
||||
return item; |
|
||||
}); |
|
||||
setTypeOfProperty(() => { |
|
||||
return newState; |
|
||||
}); |
|
||||
onChange && onChange(newState); |
|
||||
}} |
|
||||
/> |
|
||||
</div> |
|
||||
))} |
|
||||
</div> |
|
||||
</div> |
|
||||
</Popover.Panel> |
|
||||
</Transition> |
|
||||
</> |
|
||||
)} |
|
||||
</Popover> |
|
||||
); |
|
||||
}; |
|
||||
|
|
||||
export default PropertyTypeSelect; |
|
@ -1,25 +0,0 @@ |
|||||
import React, { FC } from "react"; |
|
||||
import LocationInput from "../LocationInput"; |
|
||||
import PriceRangeInput from "./PriceRangeInput"; |
|
||||
import PropertyTypeSelect from "./PropertyTypeSelect"; |
|
||||
|
|
||||
export interface RealEstateSearchFormProps {} |
|
||||
|
|
||||
const RealEstateSearchForm: FC<RealEstateSearchFormProps> = ({}) => { |
|
||||
const renderForm = () => { |
|
||||
return ( |
|
||||
<form className="w-full relative xl:mt-8 flex flex-col lg:flex-row lg:items-center rounded-3xl lg:rounded-full shadow-xl dark:shadow-2xl bg-white dark:bg-neutral-800 divide-y divide-neutral-200 dark:divide-neutral-700 lg:divide-y-0"> |
|
||||
<LocationInput className="flex-[1.5]" /> |
|
||||
|
|
||||
<div className="self-center border-r border-slate-200 dark:border-slate-700 h-8"></div> |
|
||||
<PropertyTypeSelect /> |
|
||||
<div className="self-center border-r border-slate-200 dark:border-slate-700 h-8"></div> |
|
||||
<PriceRangeInput /> |
|
||||
</form> |
|
||||
); |
|
||||
}; |
|
||||
|
|
||||
return renderForm(); |
|
||||
}; |
|
||||
|
|
||||
export default RealEstateSearchForm; |
|
@ -1,129 +0,0 @@ |
|||||
"use client"; |
|
||||
|
|
||||
import React, { Fragment, useState, FC } from "react"; |
|
||||
import DatePicker from "react-datepicker"; |
|
||||
import { Popover, Transition } from "@headlessui/react"; |
|
||||
import DatePickerCustomHeaderTwoMonth from "@/components/DatePickerCustomHeaderTwoMonth"; |
|
||||
import DatePickerCustomDay from "@/components/DatePickerCustomDay"; |
|
||||
import ClearDataButton from "../ClearDataButton"; |
|
||||
import ButtonSubmit from "../ButtonSubmit"; |
|
||||
|
|
||||
export interface RentalCarDatesRangeInputProps { |
|
||||
className?: string; |
|
||||
fieldClassName?: string; |
|
||||
hasButtonSubmit?: boolean; |
|
||||
} |
|
||||
|
|
||||
const RentalCarDatesRangeInput: FC<RentalCarDatesRangeInputProps> = ({ |
|
||||
className = "", |
|
||||
fieldClassName = "[ nc-hero-field-padding--small ]", |
|
||||
hasButtonSubmit = true, |
|
||||
}) => { |
|
||||
const [startDate, setStartDate] = useState<Date | null>( |
|
||||
new Date("2023/03/01") |
|
||||
); |
|
||||
const [endDate, setEndDate] = useState<Date | null>(new Date("2023/03/16")); |
|
||||
|
|
||||
const onChangeDate = (dates: [Date | null, Date | null]) => { |
|
||||
const [start, end] = dates; |
|
||||
setStartDate(start); |
|
||||
setEndDate(end); |
|
||||
}; |
|
||||
|
|
||||
const renderInput = () => { |
|
||||
return ( |
|
||||
<> |
|
||||
<div className="flex-grow text-left"> |
|
||||
<span className="block xl:text-base font-semibold"> |
|
||||
{startDate?.toLocaleDateString("en-US", { |
|
||||
month: "short", |
|
||||
day: "2-digit", |
|
||||
}) || "Add dates"} |
|
||||
{endDate |
|
||||
? " - " + |
|
||||
endDate?.toLocaleDateString("en-US", { |
|
||||
month: "short", |
|
||||
day: "2-digit", |
|
||||
}) |
|
||||
: ""} |
|
||||
</span> |
|
||||
<span className="block mt-1 text-sm text-neutral-400 leading-none font-light"> |
|
||||
{"Pick up - Drop off"} |
|
||||
</span> |
|
||||
</div> |
|
||||
</> |
|
||||
); |
|
||||
}; |
|
||||
|
|
||||
return ( |
|
||||
<> |
|
||||
<Popover |
|
||||
className={`RentalCarDatesRangeInput relative flex ${className}`} |
|
||||
> |
|
||||
{({ open }) => ( |
|
||||
<> |
|
||||
<div |
|
||||
className={`flex-1 z-10 flex items-center focus:outline-none ${ |
|
||||
open ? "nc-hero-field-focused--2" : "" |
|
||||
}`}
|
|
||||
> |
|
||||
<Popover.Button |
|
||||
className={`flex-1 z-10 flex relative ${fieldClassName} items-center space-x-3 focus:outline-none `} |
|
||||
> |
|
||||
{renderInput()} |
|
||||
|
|
||||
{startDate && open && ( |
|
||||
<ClearDataButton onClick={() => onChangeDate([null, null])} /> |
|
||||
)} |
|
||||
</Popover.Button> |
|
||||
|
|
||||
{/* BUTTON SUBMIT OF FORM */} |
|
||||
{hasButtonSubmit && ( |
|
||||
<div className="pr-2 xl:pr-4"> |
|
||||
<ButtonSubmit href="/listing-car-detail" /> |
|
||||
</div> |
|
||||
)} |
|
||||
</div> |
|
||||
|
|
||||
{open && ( |
|
||||
<div className="h-8 absolute self-center top-1/2 -translate-y-1/2 z-0 -inset-x-0.5 bg-white dark:bg-neutral-800"></div> |
|
||||
)} |
|
||||
|
|
||||
<Transition |
|
||||
as={Fragment} |
|
||||
enter="transition ease-out duration-200" |
|
||||
enterFrom="opacity-0 translate-y-1" |
|
||||
enterTo="opacity-100 translate-y-0" |
|
||||
leave="transition ease-in duration-150" |
|
||||
leaveFrom="opacity-100 translate-y-0" |
|
||||
leaveTo="opacity-0 translate-y-1" |
|
||||
> |
|
||||
<Popover.Panel className="absolute left-1/2 z-10 mt-3 top-full w-screen max-w-sm -translate-x-1/2 transform px-4 sm:px-0 lg:max-w-3xl"> |
|
||||
<div className="overflow-hidden rounded-3xl shadow-lg ring-1 ring-black ring-opacity-5 bg-white dark:bg-neutral-800 p-8"> |
|
||||
<DatePicker |
|
||||
selected={startDate} |
|
||||
onChange={onChangeDate} |
|
||||
startDate={startDate} |
|
||||
endDate={endDate} |
|
||||
selectsRange |
|
||||
monthsShown={2} |
|
||||
showPopperArrow={false} |
|
||||
inline |
|
||||
renderCustomHeader={(p) => ( |
|
||||
<DatePickerCustomHeaderTwoMonth {...p} /> |
|
||||
)} |
|
||||
renderDayContents={(day, date) => ( |
|
||||
<DatePickerCustomDay dayOfMonth={day} date={date} /> |
|
||||
)} |
|
||||
/> |
|
||||
</div> |
|
||||
</Popover.Panel> |
|
||||
</Transition> |
|
||||
</> |
|
||||
)} |
|
||||
</Popover> |
|
||||
</> |
|
||||
); |
|
||||
}; |
|
||||
|
|
||||
export default RentalCarDatesRangeInput; |
|
@ -1,72 +0,0 @@ |
|||||
"use client"; |
|
||||
|
|
||||
import React, { FC, useState } from "react"; |
|
||||
import LocationInput from "../LocationInput"; |
|
||||
import RentalCarDatesRangeInput from "./RentalCarDatesRangeInput"; |
|
||||
|
|
||||
export interface RentalCarSearchFormProps {} |
|
||||
|
|
||||
const RentalCarSearchForm: FC<RentalCarSearchFormProps> = ({}) => { |
|
||||
const [dropOffLocationType, setDropOffLocationType] = useState< |
|
||||
"same" | "different" |
|
||||
>("different"); |
|
||||
|
|
||||
const renderRadioBtn = () => { |
|
||||
return ( |
|
||||
<div className="pb-3 flex justify-center items-center space-x-3"> |
|
||||
<div |
|
||||
className={`py-1.5 px-4 flex items-center rounded-full font-medium text-xs cursor-pointer ${ |
|
||||
dropOffLocationType === "same" |
|
||||
? "bg-black text-white shadow-black/10 shadow-lg" |
|
||||
: "border border-neutral-300 dark:border-neutral-700" |
|
||||
}`}
|
|
||||
onClick={(e) => setDropOffLocationType("same")} |
|
||||
> |
|
||||
Same drop off |
|
||||
</div> |
|
||||
<div |
|
||||
className={`py-1.5 px-4 flex items-center rounded-full font-medium text-xs cursor-pointer ${ |
|
||||
dropOffLocationType === "different" |
|
||||
? "bg-black text-white shadow-black/10 shadow-lg" |
|
||||
: "border border-neutral-300 dark:border-neutral-700" |
|
||||
}`}
|
|
||||
onClick={(e) => setDropOffLocationType("different")} |
|
||||
> |
|
||||
Different drop off |
|
||||
</div> |
|
||||
</div> |
|
||||
); |
|
||||
}; |
|
||||
|
|
||||
const renderForm = () => { |
|
||||
return ( |
|
||||
<form className="w-full relative "> |
|
||||
{renderRadioBtn()} |
|
||||
<div className="flex flex-row w-full rounded-full border border-neutral-200 dark:border-neutral-700 bg-white dark:bg-neutral-800"> |
|
||||
<LocationInput |
|
||||
placeHolder="City or Airport" |
|
||||
desc="Pick up location" |
|
||||
className="flex-1" |
|
||||
/> |
|
||||
{dropOffLocationType === "different" && ( |
|
||||
<> |
|
||||
<div className="self-center border-r border-slate-200 dark:border-slate-700 h-8"></div> |
|
||||
<LocationInput |
|
||||
placeHolder="City or Airport" |
|
||||
desc="Drop off location" |
|
||||
className="flex-1" |
|
||||
divHideVerticalLineClass="-inset-x-0.5" |
|
||||
/> |
|
||||
</> |
|
||||
)} |
|
||||
<div className="self-center border-r border-slate-200 dark:border-slate-700 h-8"></div> |
|
||||
<RentalCarDatesRangeInput className="flex-1" /> |
|
||||
</div> |
|
||||
</form> |
|
||||
); |
|
||||
}; |
|
||||
|
|
||||
return renderForm(); |
|
||||
}; |
|
||||
|
|
||||
export default RentalCarSearchForm; |
|
@ -1,114 +0,0 @@ |
|||||
"use client"; |
|
||||
|
|
||||
import React, { Fragment, useState, FC } from "react"; |
|
||||
import DatePicker from "react-datepicker"; |
|
||||
import { Popover, Transition } from "@headlessui/react"; |
|
||||
import DatePickerCustomHeaderTwoMonth from "@/components/DatePickerCustomHeaderTwoMonth"; |
|
||||
import DatePickerCustomDay from "@/components/DatePickerCustomDay"; |
|
||||
import ClearDataButton from "../ClearDataButton"; |
|
||||
|
|
||||
export interface ExperiencesDateSingleInputProps { |
|
||||
className?: string; |
|
||||
fieldClassName?: string; |
|
||||
} |
|
||||
|
|
||||
const ExperiencesDateSingleInput: FC<ExperiencesDateSingleInputProps> = ({ |
|
||||
className = "", |
|
||||
fieldClassName = "[ nc-hero-field-padding--small ]", |
|
||||
}) => { |
|
||||
const [startDate, setStartDate] = useState<Date | null>( |
|
||||
new Date("2023/03/01") |
|
||||
); |
|
||||
const [endDate, setEndDate] = useState<Date | null>(new Date("2023/03/16")); |
|
||||
|
|
||||
const onChangeDate = (dates: [Date | null, Date | null]) => { |
|
||||
const [start, end] = dates; |
|
||||
setStartDate(start); |
|
||||
setEndDate(end); |
|
||||
}; |
|
||||
|
|
||||
const renderInput = () => { |
|
||||
return ( |
|
||||
<> |
|
||||
<div className="flex-grow text-left"> |
|
||||
<span className="block xl:text-base font-semibold"> |
|
||||
{startDate?.toLocaleDateString("en-US", { |
|
||||
month: "short", |
|
||||
day: "2-digit", |
|
||||
}) || "Date"} |
|
||||
{endDate |
|
||||
? " - " + |
|
||||
endDate?.toLocaleDateString("en-US", { |
|
||||
month: "short", |
|
||||
day: "2-digit", |
|
||||
}) |
|
||||
: ""} |
|
||||
</span> |
|
||||
<span className="block mt-1 text-sm text-neutral-400 leading-none font-light"> |
|
||||
{startDate ? "Date" : `Add dates`} |
|
||||
</span> |
|
||||
</div> |
|
||||
</> |
|
||||
); |
|
||||
}; |
|
||||
|
|
||||
return ( |
|
||||
<> |
|
||||
<Popover |
|
||||
className={`ExperiencesDateSingleInput relative flex ${className}`} |
|
||||
> |
|
||||
{({ open }) => ( |
|
||||
<> |
|
||||
<Popover.Button |
|
||||
className={`flex-1 z-10 flex relative ${fieldClassName} items-center space-x-3 focus:outline-none ${ |
|
||||
open ? "nc-hero-field-focused--2" : "" |
|
||||
}`}
|
|
||||
> |
|
||||
{renderInput()} |
|
||||
{startDate && open && ( |
|
||||
<ClearDataButton onClick={() => onChangeDate([null, null])} /> |
|
||||
)} |
|
||||
</Popover.Button> |
|
||||
|
|
||||
{open && ( |
|
||||
<div className="h-8 absolute self-center top-1/2 -translate-y-1/2 z-0 -inset-x-0.5 bg-white dark:bg-neutral-800"></div> |
|
||||
)} |
|
||||
|
|
||||
<Transition |
|
||||
as={Fragment} |
|
||||
enter="transition ease-out duration-200" |
|
||||
enterFrom="opacity-0 translate-y-1" |
|
||||
enterTo="opacity-100 translate-y-0" |
|
||||
leave="transition ease-in duration-150" |
|
||||
leaveFrom="opacity-100 translate-y-0" |
|
||||
leaveTo="opacity-0 translate-y-1" |
|
||||
> |
|
||||
<Popover.Panel className="absolute left-1/2 z-10 mt-3 top-full w-screen max-w-sm -translate-x-1/2 transform px-4 sm:px-0 lg:max-w-3xl"> |
|
||||
<div className="overflow-hidden rounded-3xl shadow-lg ring-1 ring-black ring-opacity-5 bg-white dark:bg-neutral-800 p-8"> |
|
||||
<DatePicker |
|
||||
selected={startDate} |
|
||||
onChange={onChangeDate} |
|
||||
startDate={startDate} |
|
||||
endDate={endDate} |
|
||||
selectsRange |
|
||||
monthsShown={2} |
|
||||
showPopperArrow={false} |
|
||||
inline |
|
||||
renderCustomHeader={(p) => ( |
|
||||
<DatePickerCustomHeaderTwoMonth {...p} /> |
|
||||
)} |
|
||||
renderDayContents={(day, date) => ( |
|
||||
<DatePickerCustomDay dayOfMonth={day} date={date} /> |
|
||||
)} |
|
||||
/> |
|
||||
</div> |
|
||||
</Popover.Panel> |
|
||||
</Transition> |
|
||||
</> |
|
||||
)} |
|
||||
</Popover> |
|
||||
</> |
|
||||
); |
|
||||
}; |
|
||||
|
|
||||
export default ExperiencesDateSingleInput; |
|
@ -1,29 +0,0 @@ |
|||||
"use client"; |
|
||||
|
|
||||
import React, { FC } from "react"; |
|
||||
import LocationInput from "../LocationInput"; |
|
||||
import GuestsInput from "../GuestsInput"; |
|
||||
import ExperiencesDateSingleInput from "./ExperiencesDateSingleInput"; |
|
||||
|
|
||||
export interface ExperiencesSearchFormProps {} |
|
||||
|
|
||||
const ExperiencesSearchForm: FC<ExperiencesSearchFormProps> = ({}) => { |
|
||||
const renderForm = () => { |
|
||||
return ( |
|
||||
<form className="w-full relative flex flex-row rounded-full border border-neutral-200 dark:border-neutral-700 bg-white dark:bg-neutral-800"> |
|
||||
<LocationInput |
|
||||
// onInputDone={() => setDateFocused(true)}
|
|
||||
className="flex-[1.5]" |
|
||||
/> |
|
||||
<div className="self-center border-r border-slate-200 dark:border-slate-700 h-8"></div> |
|
||||
<ExperiencesDateSingleInput className="flex-[1.2]" /> |
|
||||
<div className="self-center border-r border-slate-200 dark:border-slate-700 h-8"></div> |
|
||||
<GuestsInput className="flex-1" submitLink="/listing-experiences" /> |
|
||||
</form> |
|
||||
); |
|
||||
}; |
|
||||
|
|
||||
return renderForm(); |
|
||||
}; |
|
||||
|
|
||||
export default ExperiencesSearchForm; |
|
@ -1,148 +0,0 @@ |
|||||
"use client"; |
|
||||
|
|
||||
import React, { Fragment, useState } from "react"; |
|
||||
import { FC } from "react"; |
|
||||
import DatePicker from "react-datepicker"; |
|
||||
import { Popover, Transition } from "@headlessui/react"; |
|
||||
import DatePickerCustomHeaderTwoMonth from "@/components/DatePickerCustomHeaderTwoMonth"; |
|
||||
import DatePickerCustomDay from "@/components/DatePickerCustomDay"; |
|
||||
import ClearDataButton from "../ClearDataButton"; |
|
||||
import ButtonSubmit from "../ButtonSubmit"; |
|
||||
|
|
||||
export interface FlightDateRangeInputProps { |
|
||||
className?: string; |
|
||||
fieldClassName?: string; |
|
||||
hasButtonSubmit?: boolean; |
|
||||
selectsRange?: boolean; |
|
||||
} |
|
||||
|
|
||||
const FlightDateRangeInput: FC<FlightDateRangeInputProps> = ({ |
|
||||
className = "", |
|
||||
fieldClassName = "[ nc-hero-field-padding--small ]", |
|
||||
hasButtonSubmit = true, |
|
||||
selectsRange = true, |
|
||||
}) => { |
|
||||
const [startDate, setStartDate] = useState<Date | null>( |
|
||||
new Date("2023/05/01") |
|
||||
); |
|
||||
const [endDate, setEndDate] = useState<Date | null>(new Date("2023/05/16")); |
|
||||
|
|
||||
const onChangeRangeDate = (dates: [Date | null, Date | null]) => { |
|
||||
const [start, end] = dates; |
|
||||
setStartDate(start); |
|
||||
setEndDate(end); |
|
||||
}; |
|
||||
|
|
||||
const renderInput = () => { |
|
||||
return ( |
|
||||
<> |
|
||||
<div className="flex-grow text-left"> |
|
||||
<span className="block xl:text-base font-semibold"> |
|
||||
{startDate?.toLocaleDateString("en-US", { |
|
||||
month: "short", |
|
||||
day: "2-digit", |
|
||||
}) || "Add dates"} |
|
||||
{selectsRange && endDate |
|
||||
? " - " + |
|
||||
endDate?.toLocaleDateString("en-US", { |
|
||||
month: "short", |
|
||||
day: "2-digit", |
|
||||
}) |
|
||||
: ""} |
|
||||
</span> |
|
||||
<span className="block mt-1 text-sm text-neutral-400 leading-none font-light"> |
|
||||
{selectsRange ? "Pick up - Drop off" : "Pick up date"} |
|
||||
</span> |
|
||||
</div> |
|
||||
</> |
|
||||
); |
|
||||
}; |
|
||||
|
|
||||
return ( |
|
||||
<> |
|
||||
<Popover className={`FlightDateRangeInput relative flex ${className}`}> |
|
||||
{({ open }) => ( |
|
||||
<> |
|
||||
<div |
|
||||
className={`flex-1 z-10 flex items-center focus:outline-none ${ |
|
||||
open ? "nc-hero-field-focused--2" : "" |
|
||||
}`}
|
|
||||
> |
|
||||
<Popover.Button |
|
||||
className={`flex-1 z-10 flex relative ${fieldClassName} items-center space-x-3 focus:outline-none `} |
|
||||
> |
|
||||
{renderInput()} |
|
||||
|
|
||||
{startDate && open && ( |
|
||||
<ClearDataButton |
|
||||
onClick={() => onChangeRangeDate([null, null])} |
|
||||
/> |
|
||||
)} |
|
||||
</Popover.Button> |
|
||||
|
|
||||
{/* BUTTON SUBMIT OF FORM */} |
|
||||
{hasButtonSubmit && ( |
|
||||
<div className="pr-2 xl:pr-4"> |
|
||||
<ButtonSubmit href="/listing-car-detail" /> |
|
||||
</div> |
|
||||
)} |
|
||||
</div> |
|
||||
|
|
||||
{open && ( |
|
||||
<div className="h-8 absolute self-center top-1/2 -translate-y-1/2 z-0 -left-0.5 right-10 bg-white dark:bg-neutral-800"></div> |
|
||||
)} |
|
||||
|
|
||||
<Transition |
|
||||
as={Fragment} |
|
||||
enter="transition ease-out duration-200" |
|
||||
enterFrom="opacity-0 translate-y-1" |
|
||||
enterTo="opacity-100 translate-y-0" |
|
||||
leave="transition ease-in duration-150" |
|
||||
leaveFrom="opacity-100 translate-y-0" |
|
||||
leaveTo="opacity-0 translate-y-1" |
|
||||
> |
|
||||
<Popover.Panel className="absolute left-1/2 z-20 mt-3 top-full w-screen max-w-sm -translate-x-1/2 transform px-4 sm:px-0 lg:max-w-3xl"> |
|
||||
<div className="overflow-hidden rounded-3xl shadow-lg ring-1 ring-black ring-opacity-5 bg-white dark:bg-neutral-800 p-8"> |
|
||||
{selectsRange ? ( |
|
||||
<DatePicker |
|
||||
selected={startDate} |
|
||||
onChange={onChangeRangeDate} |
|
||||
startDate={startDate} |
|
||||
endDate={endDate} |
|
||||
selectsRange |
|
||||
monthsShown={2} |
|
||||
showPopperArrow={false} |
|
||||
inline |
|
||||
renderCustomHeader={(p) => ( |
|
||||
<DatePickerCustomHeaderTwoMonth {...p} /> |
|
||||
)} |
|
||||
renderDayContents={(day, date) => ( |
|
||||
<DatePickerCustomDay dayOfMonth={day} date={date} /> |
|
||||
)} |
|
||||
/> |
|
||||
) : ( |
|
||||
<DatePicker |
|
||||
selected={startDate} |
|
||||
onChange={(date) => setStartDate(date)} |
|
||||
monthsShown={2} |
|
||||
showPopperArrow={false} |
|
||||
inline |
|
||||
renderCustomHeader={(p) => ( |
|
||||
<DatePickerCustomHeaderTwoMonth {...p} /> |
|
||||
)} |
|
||||
renderDayContents={(day, date) => ( |
|
||||
<DatePickerCustomDay dayOfMonth={day} date={date} /> |
|
||||
)} |
|
||||
/> |
|
||||
)} |
|
||||
</div> |
|
||||
</Popover.Panel> |
|
||||
</Transition> |
|
||||
</> |
|
||||
)} |
|
||||
</Popover> |
|
||||
</> |
|
||||
); |
|
||||
}; |
|
||||
|
|
||||
export default FlightDateRangeInput; |
|
@ -1,247 +0,0 @@ |
|||||
"use client"; |
|
||||
|
|
||||
import React, { FC, useState } from "react"; |
|
||||
import LocationInput from "../LocationInput"; |
|
||||
import { Popover, Transition } from "@headlessui/react"; |
|
||||
import { ChevronDownIcon } from "@heroicons/react/24/solid"; |
|
||||
import { Fragment } from "react"; |
|
||||
import NcInputNumber from "@/components/NcInputNumber"; |
|
||||
import FlightDateRangeInput from "./FlightDateRangeInput"; |
|
||||
import { GuestsObject } from "../../type"; |
|
||||
|
|
||||
export interface FlightSearchFormProps {} |
|
||||
|
|
||||
const flightClass = [ |
|
||||
{ |
|
||||
name: "Economy", |
|
||||
href: "##", |
|
||||
}, |
|
||||
{ |
|
||||
name: "Business", |
|
||||
href: "##", |
|
||||
}, |
|
||||
{ |
|
||||
name: "Multiple", |
|
||||
href: "##", |
|
||||
}, |
|
||||
]; |
|
||||
|
|
||||
const FlightSearchForm: FC<FlightSearchFormProps> = ({}) => { |
|
||||
const [dropOffLocationType, setDropOffLocationType] = useState< |
|
||||
"roundTrip" | "oneWay" | "" |
|
||||
>("roundTrip"); |
|
||||
const [flightClassState, setFlightClassState] = useState("Economy"); |
|
||||
const [guestAdultsInputValue, setGuestAdultsInputValue] = useState(2); |
|
||||
const [guestChildrenInputValue, setGuestChildrenInputValue] = useState(1); |
|
||||
const [guestInfantsInputValue, setGuestInfantsInputValue] = useState(1); |
|
||||
|
|
||||
const handleChangeData = (value: number, type: keyof GuestsObject) => { |
|
||||
let newValue = { |
|
||||
guestAdults: guestAdultsInputValue, |
|
||||
guestChildren: guestChildrenInputValue, |
|
||||
guestInfants: guestInfantsInputValue, |
|
||||
}; |
|
||||
if (type === "guestAdults") { |
|
||||
setGuestAdultsInputValue(value); |
|
||||
newValue.guestAdults = value; |
|
||||
} |
|
||||
if (type === "guestChildren") { |
|
||||
setGuestChildrenInputValue(value); |
|
||||
newValue.guestChildren = value; |
|
||||
} |
|
||||
if (type === "guestInfants") { |
|
||||
setGuestInfantsInputValue(value); |
|
||||
newValue.guestInfants = value; |
|
||||
} |
|
||||
}; |
|
||||
|
|
||||
const totalGuests = |
|
||||
guestChildrenInputValue + guestAdultsInputValue + guestInfantsInputValue; |
|
||||
|
|
||||
const renderGuest = () => { |
|
||||
return ( |
|
||||
<div className=""> |
|
||||
<Popover className="relative"> |
|
||||
{({ open }) => ( |
|
||||
<> |
|
||||
<Popover.Button |
|
||||
className={`px-4 py-1.5 rounded-md inline-flex items-center font-medium hover:text-opacity-100 focus:outline-none focus-visible:ring-0 text-xs`} |
|
||||
> |
|
||||
<span>{`${totalGuests || ""} Guests`}</span> |
|
||||
<ChevronDownIcon |
|
||||
className={`${ |
|
||||
open ? "" : "text-opacity-70" |
|
||||
} ml-2 h-4 w-4 group-hover:text-opacity-80 transition ease-in-out duration-150`}
|
|
||||
aria-hidden="true" |
|
||||
/> |
|
||||
</Popover.Button> |
|
||||
<Transition |
|
||||
as={Fragment} |
|
||||
enter="transition ease-out duration-200" |
|
||||
enterFrom="opacity-0 translate-y-1" |
|
||||
enterTo="opacity-100 translate-y-0" |
|
||||
leave="transition ease-in duration-150" |
|
||||
leaveFrom="opacity-100 translate-y-0" |
|
||||
leaveTo="opacity-0 translate-y-1" |
|
||||
> |
|
||||
<Popover.Panel className="absolute z-20 w-full sm:min-w-[340px] max-w-sm bg-white dark:bg-neutral-800 top-full mt-3 left-1/2 -translate-x-1/2 py-5 sm:py-6 px-4 sm:px-8 rounded-3xl shadow-xl ring-1 ring-black/5 dark:ring-white/10"> |
|
||||
<NcInputNumber |
|
||||
className="w-full" |
|
||||
defaultValue={guestAdultsInputValue} |
|
||||
onChange={(value) => handleChangeData(value, "guestAdults")} |
|
||||
max={10} |
|
||||
min={1} |
|
||||
label="Adults" |
|
||||
desc="Ages 13 or above" |
|
||||
/> |
|
||||
<NcInputNumber |
|
||||
className="w-full mt-6" |
|
||||
defaultValue={guestChildrenInputValue} |
|
||||
onChange={(value) => |
|
||||
handleChangeData(value, "guestChildren") |
|
||||
} |
|
||||
max={4} |
|
||||
label="Children" |
|
||||
desc="Ages 2–12" |
|
||||
/> |
|
||||
|
|
||||
<NcInputNumber |
|
||||
className="w-full mt-6" |
|
||||
defaultValue={guestInfantsInputValue} |
|
||||
onChange={(value) => |
|
||||
handleChangeData(value, "guestInfants") |
|
||||
} |
|
||||
max={4} |
|
||||
label="Infants" |
|
||||
desc="Ages 0–2" |
|
||||
/> |
|
||||
</Popover.Panel> |
|
||||
</Transition> |
|
||||
</> |
|
||||
)} |
|
||||
</Popover> |
|
||||
</div> |
|
||||
); |
|
||||
}; |
|
||||
|
|
||||
const renderSelectClass = () => { |
|
||||
return ( |
|
||||
<div className=""> |
|
||||
<Popover className="relative"> |
|
||||
{({ open, close }) => ( |
|
||||
<> |
|
||||
<Popover.Button |
|
||||
className={`px-4 py-1.5 rounded-md inline-flex items-center font-medium hover:text-opacity-100 focus:outline-none focus-visible:ring-0 text-xs`} |
|
||||
> |
|
||||
<span>{`${flightClassState}`}</span> |
|
||||
<ChevronDownIcon |
|
||||
className={`${ |
|
||||
open ? "" : "text-opacity-70" |
|
||||
} ml-2 h-4 w-4 group-hover:text-opacity-80 transition ease-in-out duration-150`}
|
|
||||
aria-hidden="true" |
|
||||
/> |
|
||||
</Popover.Button> |
|
||||
<Transition |
|
||||
as={Fragment} |
|
||||
enter="transition ease-out duration-200" |
|
||||
enterFrom="opacity-0 translate-y-1" |
|
||||
enterTo="opacity-100 translate-y-0" |
|
||||
leave="transition ease-in duration-150" |
|
||||
leaveFrom="opacity-100 translate-y-0" |
|
||||
leaveTo="opacity-0 translate-y-1" |
|
||||
> |
|
||||
<Popover.Panel className="absolute z-30 w-screen max-w-[200px] sm:max-w-[220px] px-4 mt-3 transform -translate-x-1/2 left-1/2 sm:px-0 "> |
|
||||
<div className="overflow-hidden rounded-2xl shadow-lg ring-1 ring-black/5 dark:ring-white/10 "> |
|
||||
<div className="relative grid bg-white dark:bg-neutral-800 p-3"> |
|
||||
{flightClass.map((item) => ( |
|
||||
<a |
|
||||
key={item.name} |
|
||||
href={item.href} |
|
||||
onClick={(e) => { |
|
||||
e.preventDefault(); |
|
||||
setFlightClassState(item.name); |
|
||||
close(); |
|
||||
}} |
|
||||
className="flex items-center p-2 transition duration-150 ease-in-out rounded-lg hover:bg-neutral-100 dark:hover:bg-gray-700 focus:outline-none focus-visible:ring focus-visible:ring-orange-500 focus-visible:ring-opacity-50" |
|
||||
> |
|
||||
<p className="text-sm font-medium ">{item.name}</p> |
|
||||
</a> |
|
||||
))} |
|
||||
</div> |
|
||||
</div> |
|
||||
</Popover.Panel> |
|
||||
</Transition> |
|
||||
</> |
|
||||
)} |
|
||||
</Popover> |
|
||||
</div> |
|
||||
); |
|
||||
}; |
|
||||
|
|
||||
const renderRadioBtn = () => { |
|
||||
return ( |
|
||||
<div className="pb-3 flex justify-center space-x-3"> |
|
||||
<div |
|
||||
className={`py-1.5 px-4 flex items-center rounded-full font-medium text-xs cursor-pointer select-none ${ |
|
||||
dropOffLocationType === "roundTrip" |
|
||||
? "bg-black shadow-black/10 shadow-lg text-white" |
|
||||
: "border border-neutral-300 dark:border-neutral-700" |
|
||||
}`}
|
|
||||
onClick={(e) => setDropOffLocationType("roundTrip")} |
|
||||
> |
|
||||
Round-trip |
|
||||
</div> |
|
||||
<div |
|
||||
className={`py-1.5 px-4 flex items-center rounded-full font-medium text-xs cursor-pointer select-none ${ |
|
||||
dropOffLocationType === "oneWay" |
|
||||
? "bg-black text-white shadow-black/10 shadow-lg" |
|
||||
: "border border-neutral-300 dark:border-neutral-700" |
|
||||
}`}
|
|
||||
onClick={(e) => setDropOffLocationType("oneWay")} |
|
||||
> |
|
||||
One-way |
|
||||
</div> |
|
||||
|
|
||||
<div className=" border-r border-slate-200 dark:border-slate-700 "></div> |
|
||||
|
|
||||
<div className="border border-neutral-300 dark:border-neutral-700 rounded-full"> |
|
||||
{renderSelectClass()} |
|
||||
</div> |
|
||||
<div className="border border-neutral-300 dark:border-neutral-700 rounded-full"> |
|
||||
{renderGuest()} |
|
||||
</div> |
|
||||
</div> |
|
||||
); |
|
||||
}; |
|
||||
|
|
||||
const renderForm = () => { |
|
||||
return ( |
|
||||
<form className="w-full relative "> |
|
||||
{renderRadioBtn()} |
|
||||
<div className="flex w-full rounded-full border border-neutral-200 dark:border-neutral-700 bg-white dark:bg-neutral-800"> |
|
||||
<LocationInput |
|
||||
placeHolder="Flying from" |
|
||||
desc="Where do you want to fly from?" |
|
||||
className="flex-1" |
|
||||
/> |
|
||||
<div className="self-center border-r border-slate-200 dark:border-slate-700 h-8"></div> |
|
||||
<LocationInput |
|
||||
placeHolder="Flying to" |
|
||||
desc="Where you want to fly to?" |
|
||||
className="flex-1" |
|
||||
divHideVerticalLineClass=" -inset-x-0.5" |
|
||||
/> |
|
||||
<div className="self-center border-r border-slate-200 dark:border-slate-700 h-8"></div> |
|
||||
<FlightDateRangeInput |
|
||||
selectsRange={dropOffLocationType !== "oneWay"} |
|
||||
className="flex-1" |
|
||||
/> |
|
||||
</div> |
|
||||
</form> |
|
||||
); |
|
||||
}; |
|
||||
|
|
||||
return renderForm(); |
|
||||
}; |
|
||||
|
|
||||
export default FlightSearchForm; |
|
@ -1,51 +0,0 @@ |
|||||
import React, { FC } from "react"; |
|
||||
import { DEMO_EXPERIENCES_LISTINGS } from "@/data/listings"; |
|
||||
import { ExperiencesDataType, StayDataType } from "@/data/types"; |
|
||||
import Pagination from "@/shared/Pagination"; |
|
||||
import TabFilters from "./TabFilters"; |
|
||||
import Heading2 from "@/shared/Heading2"; |
|
||||
import ExperiencesCard from "@/components/ExperiencesCard"; |
|
||||
|
|
||||
export interface SectionGridFilterCardProps { |
|
||||
className?: string; |
|
||||
data?: StayDataType[]; |
|
||||
} |
|
||||
|
|
||||
const DEMO_DATA: ExperiencesDataType[] = DEMO_EXPERIENCES_LISTINGS.filter( |
|
||||
(_, i) => i < 8 |
|
||||
); |
|
||||
|
|
||||
const SectionGridFilterCard: FC<SectionGridFilterCardProps> = ({ |
|
||||
className = "", |
|
||||
data = DEMO_DATA, |
|
||||
}) => { |
|
||||
return ( |
|
||||
<div className={`nc-SectionGridFilterCard ${className}`}> |
|
||||
<Heading2 |
|
||||
heading="Experiences in Tokyo" |
|
||||
subHeading={ |
|
||||
<span className="block text-neutral-500 dark:text-neutral-400 mt-3"> |
|
||||
233 experiences |
|
||||
<span className="mx-2">·</span> |
|
||||
Aug 12 - 18 |
|
||||
<span className="mx-2">·</span>2 Guests |
|
||||
</span> |
|
||||
} |
|
||||
/> |
|
||||
|
|
||||
<div className="mb-8 lg:mb-11"> |
|
||||
<TabFilters /> |
|
||||
</div> |
|
||||
<div className="grid grid-cols-1 gap-6 md:gap-8 sm:grid-cols-2 lg:grid-cols-3 2xl:grid-cols-4"> |
|
||||
{data.map((stay) => ( |
|
||||
<ExperiencesCard key={stay.id} data={stay} /> |
|
||||
))} |
|
||||
</div> |
|
||||
<div className="flex mt-16 justify-center items-center"> |
|
||||
<Pagination /> |
|
||||
</div> |
|
||||
</div> |
|
||||
); |
|
||||
}; |
|
||||
|
|
||||
export default SectionGridFilterCard; |
|
@ -1,113 +0,0 @@ |
|||||
"use client"; |
|
||||
|
|
||||
import React, { FC, useState } from "react"; |
|
||||
import AnyReactComponent from "@/components/AnyReactComponent/AnyReactComponent"; |
|
||||
import GoogleMapReact from "google-map-react"; |
|
||||
import { DEMO_EXPERIENCES_LISTINGS } from "@/data/listings"; |
|
||||
import ButtonClose from "@/shared/ButtonClose"; |
|
||||
import Checkbox from "@/shared/Checkbox"; |
|
||||
import Pagination from "@/shared/Pagination"; |
|
||||
import TabFilters from "./TabFilters"; |
|
||||
import Heading2 from "@/shared/Heading2"; |
|
||||
import ExperiencesCardH from "@/components/ExperiencesCardH"; |
|
||||
|
|
||||
const DEMO_EXPERIENCES = DEMO_EXPERIENCES_LISTINGS.filter((_, i) => i < 12); |
|
||||
|
|
||||
export interface SectionGridHasMapProps {} |
|
||||
|
|
||||
const SectionGridHasMap: FC<SectionGridHasMapProps> = () => { |
|
||||
const [currentHoverID, setCurrentHoverID] = useState<string | number>(-1); |
|
||||
const [showFullMapFixed, setShowFullMapFixed] = useState(false); |
|
||||
|
|
||||
return ( |
|
||||
<div> |
|
||||
<div className="relative flex min-h-screen"> |
|
||||
{/* CARDSSSS */} |
|
||||
<div className="min-h-screen w-full xl:w-[780px] 2xl:w-[880px] flex-shrink-0 xl:px-8 "> |
|
||||
<Heading2 |
|
||||
heading="Experiences in Tokyo" |
|
||||
subHeading={ |
|
||||
<span className="block text-neutral-500 dark:text-neutral-400 mt-3"> |
|
||||
233 experiences |
|
||||
<span className="mx-2">·</span> |
|
||||
Aug 12 - 18 |
|
||||
<span className="mx-2">·</span>2 Guests |
|
||||
</span> |
|
||||
} |
|
||||
/> |
|
||||
<div className="mb-8 lg:mb-11"> |
|
||||
<TabFilters /> |
|
||||
</div> |
|
||||
<div className="grid grid-cols-1 gap-8"> |
|
||||
{DEMO_EXPERIENCES.map((item) => ( |
|
||||
<div |
|
||||
key={item.id} |
|
||||
onMouseEnter={() => setCurrentHoverID((_) => item.id)} |
|
||||
onMouseLeave={() => setCurrentHoverID((_) => -1)} |
|
||||
> |
|
||||
<ExperiencesCardH data={item} /> |
|
||||
</div> |
|
||||
))} |
|
||||
</div> |
|
||||
<div className="flex mt-16 justify-center items-center"> |
|
||||
<Pagination /> |
|
||||
</div> |
|
||||
</div> |
|
||||
|
|
||||
<div |
|
||||
className="flex xl:hidden items-center justify-center fixed bottom-8 left-1/2 transform -translate-x-1/2 px-6 py-2 bg-neutral-900 text-white shadow-2xl rounded-full z-30 space-x-3 text-sm cursor-pointer" |
|
||||
onClick={() => setShowFullMapFixed(true)} |
|
||||
> |
|
||||
<i className="text-lg las la-map"></i> |
|
||||
<span>Show map</span> |
|
||||
</div> |
|
||||
|
|
||||
{/* MAPPPPP */} |
|
||||
<div |
|
||||
className={`xl:flex-grow xl:static xl:block ${ |
|
||||
showFullMapFixed ? "fixed inset-0 z-50" : "hidden" |
|
||||
}`}
|
|
||||
> |
|
||||
{showFullMapFixed && ( |
|
||||
<ButtonClose |
|
||||
onClick={() => setShowFullMapFixed(false)} |
|
||||
className="bg-white absolute z-50 left-3 top-3 shadow-lg rounded-xl w-10 h-10" |
|
||||
/> |
|
||||
)} |
|
||||
|
|
||||
<div className="fixed xl:sticky top-0 xl:top-[88px] left-0 w-full h-full xl:h-[calc(100vh-88px)] rounded-md overflow-hidden"> |
|
||||
<div className="absolute bottom-5 left-3 lg:bottom-auto lg:top-2.5 lg:left-1/2 transform lg:-translate-x-1/2 py-2 px-4 bg-white shadow-xl z-10 rounded-2xl min-w-max"> |
|
||||
<Checkbox |
|
||||
className="text-xs xl:text-sm text-neutral-800" |
|
||||
name="xx" |
|
||||
label="Search as I move the map" |
|
||||
/> |
|
||||
</div> |
|
||||
{/* BELLOW IS MY GOOGLE API KEY -- PLEASE DELETE AND TYPE YOUR API KEY */} |
|
||||
|
|
||||
<GoogleMapReact |
|
||||
bootstrapURLKeys={{ |
|
||||
key: "AIzaSyAGVJfZMAKYfZ71nzL_v5i3LjTTWnCYwTY", |
|
||||
}} |
|
||||
yesIWantToUseGoogleMapApiInternals |
|
||||
defaultZoom={12} |
|
||||
defaultCenter={DEMO_EXPERIENCES[0].map} |
|
||||
> |
|
||||
{DEMO_EXPERIENCES.map((item) => ( |
|
||||
<AnyReactComponent |
|
||||
isSelected={currentHoverID === item.id} |
|
||||
key={item.id} |
|
||||
lat={item.map.lat} |
|
||||
lng={item.map.lng} |
|
||||
experiences={item} |
|
||||
/> |
|
||||
))} |
|
||||
</GoogleMapReact> |
|
||||
</div> |
|
||||
</div> |
|
||||
</div> |
|
||||
</div> |
|
||||
); |
|
||||
}; |
|
||||
|
|
||||
export default SectionGridHasMap; |
|
@ -1,538 +0,0 @@ |
|||||
"use client"; |
|
||||
|
|
||||
import React, { Fragment, useState } from "react"; |
|
||||
import { Dialog, Popover, Transition } from "@headlessui/react"; |
|
||||
import ButtonPrimary from "@/shared/ButtonPrimary"; |
|
||||
import ButtonThird from "@/shared/ButtonThird"; |
|
||||
import ButtonClose from "@/shared/ButtonClose"; |
|
||||
import Checkbox from "@/shared/Checkbox"; |
|
||||
import convertNumbThousand from "@/utils/convertNumbThousand"; |
|
||||
import Slider from "rc-slider"; |
|
||||
|
|
||||
// DEMO DATA
|
|
||||
const typeOfExpriences = [ |
|
||||
{ |
|
||||
name: "Food & drink", |
|
||||
description: "Short description for the experience", |
|
||||
}, |
|
||||
{ |
|
||||
name: "Art and culture", |
|
||||
description: "Short description for the experience", |
|
||||
}, |
|
||||
{ |
|
||||
name: "Nature and outdoors", |
|
||||
description: "Short description for the experience", |
|
||||
}, |
|
||||
{ |
|
||||
name: "Sports", |
|
||||
description: "Short description for the experience", |
|
||||
}, |
|
||||
]; |
|
||||
|
|
||||
const timeOfdays = [ |
|
||||
{ |
|
||||
name: "Morning", |
|
||||
description: "Start before 12pm", |
|
||||
}, |
|
||||
{ |
|
||||
name: "Afternoon", |
|
||||
description: "Start after 12pm", |
|
||||
}, |
|
||||
{ |
|
||||
name: "Evening", |
|
||||
description: "Start after 5pm", |
|
||||
}, |
|
||||
]; |
|
||||
|
|
||||
//
|
|
||||
const moreFilter1 = typeOfExpriences; |
|
||||
const moreFilter2 = timeOfdays; |
|
||||
|
|
||||
const TabFilters = () => { |
|
||||
const [isOpenMoreFilter, setisOpenMoreFilter] = useState(false); |
|
||||
//
|
|
||||
const [isOnSale, setIsOnSale] = useState(true); |
|
||||
const [rangePrices, setRangePrices] = useState([0, 1000]); |
|
||||
//
|
|
||||
const closeModalMoreFilter = () => setisOpenMoreFilter(false); |
|
||||
const openModalMoreFilter = () => setisOpenMoreFilter(true); |
|
||||
|
|
||||
const renderXClear = () => { |
|
||||
return ( |
|
||||
<span className="w-4 h-4 rounded-full bg-primary-500 text-white flex items-center justify-center ml-3 cursor-pointer"> |
|
||||
<svg |
|
||||
xmlns="http://www.w3.org/2000/svg" |
|
||||
className="h-3 w-3" |
|
||||
viewBox="0 0 20 20" |
|
||||
fill="currentColor" |
|
||||
> |
|
||||
<path |
|
||||
fillRule="evenodd" |
|
||||
d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z" |
|
||||
clipRule="evenodd" |
|
||||
/> |
|
||||
</svg> |
|
||||
</span> |
|
||||
); |
|
||||
}; |
|
||||
|
|
||||
const renderTabsTypeOfPlace = () => { |
|
||||
return ( |
|
||||
<Popover className="relative"> |
|
||||
{({ open, close }) => ( |
|
||||
<> |
|
||||
<Popover.Button |
|
||||
className={`flex items-center justify-center px-4 py-2 text-sm rounded-full border border-neutral-300 dark:border-neutral-700 hover:border-neutral-400 dark:hover:border-neutral-6000 focus:outline-none ${ |
|
||||
open ? "!border-primary-500 " : "" |
|
||||
}`}
|
|
||||
> |
|
||||
<span>Type of experiences</span> |
|
||||
<i className="las la-angle-down ml-2"></i> |
|
||||
</Popover.Button> |
|
||||
<Transition |
|
||||
as={Fragment} |
|
||||
enter="transition ease-out duration-200" |
|
||||
enterFrom="opacity-0 translate-y-1" |
|
||||
enterTo="opacity-100 translate-y-0" |
|
||||
leave="transition ease-in duration-150" |
|
||||
leaveFrom="opacity-100 translate-y-0" |
|
||||
leaveTo="opacity-0 translate-y-1" |
|
||||
> |
|
||||
<Popover.Panel className="absolute z-10 w-screen max-w-sm px-4 mt-3 left-0 sm:px-0 lg:max-w-md"> |
|
||||
<div className="overflow-hidden rounded-2xl shadow-xl bg-white dark:bg-neutral-900 border border-neutral-200 dark:border-neutral-700"> |
|
||||
<div className="relative flex flex-col px-5 py-6 space-y-5"> |
|
||||
{typeOfExpriences.map((item) => ( |
|
||||
<div key={item.name} className=""> |
|
||||
<Checkbox |
|
||||
name={item.name} |
|
||||
label={item.name} |
|
||||
subLabel={item.description} |
|
||||
/> |
|
||||
</div> |
|
||||
))} |
|
||||
</div> |
|
||||
<div className="p-5 bg-neutral-50 dark:bg-neutral-900 dark:border-t dark:border-neutral-800 flex items-center justify-between"> |
|
||||
<ButtonThird onClick={close} sizeClass="px-4 py-2 sm:px-5"> |
|
||||
Clear |
|
||||
</ButtonThird> |
|
||||
<ButtonPrimary |
|
||||
onClick={close} |
|
||||
sizeClass="px-4 py-2 sm:px-5" |
|
||||
> |
|
||||
Apply |
|
||||
</ButtonPrimary> |
|
||||
</div> |
|
||||
</div> |
|
||||
</Popover.Panel> |
|
||||
</Transition> |
|
||||
</> |
|
||||
)} |
|
||||
</Popover> |
|
||||
); |
|
||||
}; |
|
||||
|
|
||||
const renderTabsTimeOfDay = () => { |
|
||||
return ( |
|
||||
<Popover className="relative"> |
|
||||
{({ open, close }) => ( |
|
||||
<> |
|
||||
<Popover.Button |
|
||||
className={`flex items-center justify-center px-4 py-2 text-sm rounded-full border border-neutral-300 dark:border-neutral-700 hover:border-neutral-400 dark:hover:border-neutral-6000 focus:outline-none ${ |
|
||||
open ? "!border-primary-500 " : "" |
|
||||
}`}
|
|
||||
> |
|
||||
<span>Time of day</span> |
|
||||
<i className="las la-angle-down ml-2"></i> |
|
||||
</Popover.Button> |
|
||||
<Transition |
|
||||
as={Fragment} |
|
||||
enter="transition ease-out duration-200" |
|
||||
enterFrom="opacity-0 translate-y-1" |
|
||||
enterTo="opacity-100 translate-y-0" |
|
||||
leave="transition ease-in duration-150" |
|
||||
leaveFrom="opacity-100 translate-y-0" |
|
||||
leaveTo="opacity-0 translate-y-1" |
|
||||
> |
|
||||
<Popover.Panel className="absolute z-10 w-screen max-w-sm px-4 mt-3 left-0 sm:px-0 lg:max-w-md"> |
|
||||
<div className="overflow-hidden rounded-2xl shadow-xl bg-white dark:bg-neutral-900 border border-neutral-200 dark:border-neutral-700"> |
|
||||
<div className="relative flex flex-col px-5 py-6 space-y-5"> |
|
||||
{timeOfdays.map((item) => ( |
|
||||
<div key={item.name} className=""> |
|
||||
<Checkbox |
|
||||
name={item.name} |
|
||||
label={item.name} |
|
||||
subLabel={item.description} |
|
||||
/> |
|
||||
</div> |
|
||||
))} |
|
||||
</div> |
|
||||
<div className="p-5 bg-neutral-50 dark:bg-neutral-900 dark:border-t dark:border-neutral-800 flex items-center justify-between"> |
|
||||
<ButtonThird onClick={close} sizeClass="px-4 py-2 sm:px-5"> |
|
||||
Clear |
|
||||
</ButtonThird> |
|
||||
<ButtonPrimary |
|
||||
onClick={close} |
|
||||
sizeClass="px-4 py-2 sm:px-5" |
|
||||
> |
|
||||
Apply |
|
||||
</ButtonPrimary> |
|
||||
</div> |
|
||||
</div> |
|
||||
</Popover.Panel> |
|
||||
</Transition> |
|
||||
</> |
|
||||
)} |
|
||||
</Popover> |
|
||||
); |
|
||||
}; |
|
||||
|
|
||||
const renderTabsPriceRage = () => { |
|
||||
return ( |
|
||||
<Popover className="relative"> |
|
||||
{({ open, close }) => ( |
|
||||
<> |
|
||||
<Popover.Button |
|
||||
className={`flex items-center justify-center px-4 py-2 text-sm rounded-full border border-primary-500 bg-primary-50 text-primary-700 focus:outline-none `} |
|
||||
> |
|
||||
<span> |
|
||||
{`$${convertNumbThousand( |
|
||||
rangePrices[0] |
|
||||
)} - $${convertNumbThousand(rangePrices[1])}`}{" "}
|
|
||||
</span> |
|
||||
{renderXClear()} |
|
||||
</Popover.Button> |
|
||||
<Transition |
|
||||
as={Fragment} |
|
||||
enter="transition ease-out duration-200" |
|
||||
enterFrom="opacity-0 translate-y-1" |
|
||||
enterTo="opacity-100 translate-y-0" |
|
||||
leave="transition ease-in duration-150" |
|
||||
leaveFrom="opacity-100 translate-y-0" |
|
||||
leaveTo="opacity-0 translate-y-1" |
|
||||
> |
|
||||
<Popover.Panel className="absolute z-10 w-screen max-w-sm px-4 mt-3 left-0 sm:px-0 "> |
|
||||
<div className="overflow-hidden rounded-2xl shadow-xl bg-white dark:bg-neutral-900 border border-neutral-200 dark:border-neutral-700"> |
|
||||
<div className="relative flex flex-col px-5 py-6 space-y-8"> |
|
||||
<div className="space-y-5"> |
|
||||
<span className="font-medium">Price per day</span> |
|
||||
<Slider |
|
||||
range |
|
||||
min={0} |
|
||||
max={2000} |
|
||||
defaultValue={[rangePrices[0], rangePrices[1]]} |
|
||||
allowCross={false} |
|
||||
onChange={(e) => setRangePrices(e as number[])} |
|
||||
/> |
|
||||
</div> |
|
||||
|
|
||||
<div className="flex justify-between space-x-5"> |
|
||||
<div> |
|
||||
<label |
|
||||
htmlFor="minPrice" |
|
||||
className="block text-sm font-medium text-neutral-700 dark:text-neutral-300" |
|
||||
> |
|
||||
Min price |
|
||||
</label> |
|
||||
<div className="mt-1 relative rounded-md"> |
|
||||
<div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none"> |
|
||||
<span className="text-neutral-500 sm:text-sm"> |
|
||||
$ |
|
||||
</span> |
|
||||
</div> |
|
||||
<input |
|
||||
type="text" |
|
||||
name="minPrice" |
|
||||
disabled |
|
||||
id="minPrice" |
|
||||
className="focus:ring-indigo-500 focus:border-indigo-500 block w-full pl-7 pr-3 sm:text-sm border-neutral-200 rounded-full text-neutral-900" |
|
||||
value={rangePrices[0]} |
|
||||
/> |
|
||||
</div> |
|
||||
</div> |
|
||||
<div> |
|
||||
<label |
|
||||
htmlFor="maxPrice" |
|
||||
className="block text-sm font-medium text-neutral-700 dark:text-neutral-300" |
|
||||
> |
|
||||
Max price |
|
||||
</label> |
|
||||
<div className="mt-1 relative rounded-md"> |
|
||||
<div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none"> |
|
||||
<span className="text-neutral-500 sm:text-sm"> |
|
||||
$ |
|
||||
</span> |
|
||||
</div> |
|
||||
<input |
|
||||
type="text" |
|
||||
disabled |
|
||||
name="maxPrice" |
|
||||
id="maxPrice" |
|
||||
className="focus:ring-indigo-500 focus:border-indigo-500 block w-full pl-7 pr-3 sm:text-sm border-neutral-200 rounded-full text-neutral-900" |
|
||||
value={rangePrices[1]} |
|
||||
/> |
|
||||
</div> |
|
||||
</div> |
|
||||
</div> |
|
||||
</div> |
|
||||
<div className="p-5 bg-neutral-50 dark:bg-neutral-900 dark:border-t dark:border-neutral-800 flex items-center justify-between"> |
|
||||
<ButtonThird onClick={close} sizeClass="px-4 py-2 sm:px-5"> |
|
||||
Clear |
|
||||
</ButtonThird> |
|
||||
<ButtonPrimary |
|
||||
onClick={close} |
|
||||
sizeClass="px-4 py-2 sm:px-5" |
|
||||
> |
|
||||
Apply |
|
||||
</ButtonPrimary> |
|
||||
</div> |
|
||||
</div> |
|
||||
</Popover.Panel> |
|
||||
</Transition> |
|
||||
</> |
|
||||
)} |
|
||||
</Popover> |
|
||||
); |
|
||||
}; |
|
||||
|
|
||||
const renderTabOnSale = () => { |
|
||||
return ( |
|
||||
<div |
|
||||
className={`flex items-center justify-center px-4 py-2 text-sm rounded-full border focus:outline-none cursor-pointer transition-all ${ |
|
||||
isOnSale |
|
||||
? "border-primary-500 bg-primary-50 text-primary-700" |
|
||||
: "border-neutral-300 dark:border-neutral-700" |
|
||||
}`}
|
|
||||
onClick={() => setIsOnSale(!isOnSale)} |
|
||||
> |
|
||||
<span>On sale</span> |
|
||||
{isOnSale && renderXClear()} |
|
||||
</div> |
|
||||
); |
|
||||
}; |
|
||||
|
|
||||
const renderMoreFilterItem = ( |
|
||||
data: { |
|
||||
name: string; |
|
||||
description?: string; |
|
||||
defaultChecked?: boolean; |
|
||||
}[] |
|
||||
) => { |
|
||||
const list1 = data.filter((_, i) => i < data.length / 2); |
|
||||
const list2 = data.filter((_, i) => i >= data.length / 2); |
|
||||
return ( |
|
||||
<div className="grid sm:grid-cols-2 gap-8"> |
|
||||
<div className="flex flex-col space-y-5"> |
|
||||
{list1.map((item) => ( |
|
||||
<Checkbox |
|
||||
key={item.name} |
|
||||
name={item.name} |
|
||||
subLabel={item.description} |
|
||||
label={item.name} |
|
||||
defaultChecked={!!item.defaultChecked} |
|
||||
/> |
|
||||
))} |
|
||||
</div> |
|
||||
<div className="flex flex-col space-y-5"> |
|
||||
{list2.map((item) => ( |
|
||||
<Checkbox |
|
||||
key={item.name} |
|
||||
name={item.name} |
|
||||
subLabel={item.description} |
|
||||
label={item.name} |
|
||||
defaultChecked={!!item.defaultChecked} |
|
||||
/> |
|
||||
))} |
|
||||
</div> |
|
||||
</div> |
|
||||
); |
|
||||
}; |
|
||||
|
|
||||
const renderTabMobileFilter = () => { |
|
||||
return ( |
|
||||
<div> |
|
||||
<div |
|
||||
className={`flex items-center justify-center px-4 py-2 text-sm rounded-full border border-primary-500 bg-primary-50 text-primary-700 focus:outline-none cursor-pointer`} |
|
||||
onClick={openModalMoreFilter} |
|
||||
> |
|
||||
<span> |
|
||||
<span className="hidden sm:inline">Experiences</span> filters (3) |
|
||||
</span> |
|
||||
{renderXClear()} |
|
||||
</div> |
|
||||
|
|
||||
<Transition appear show={isOpenMoreFilter} as={Fragment}> |
|
||||
<Dialog |
|
||||
as="div" |
|
||||
className="fixed inset-0 z-50 overflow-y-auto" |
|
||||
onClose={closeModalMoreFilter} |
|
||||
> |
|
||||
<div className="min-h-screen text-center"> |
|
||||
<Transition.Child |
|
||||
as={Fragment} |
|
||||
enter="ease-out duration-300" |
|
||||
enterFrom="opacity-0" |
|
||||
enterTo="opacity-100" |
|
||||
leave="ease-in duration-200" |
|
||||
leaveFrom="opacity-100" |
|
||||
leaveTo="opacity-0" |
|
||||
> |
|
||||
<Dialog.Overlay className="fixed inset-0 bg-black bg-opacity-40 dark:bg-opacity-60" /> |
|
||||
</Transition.Child> |
|
||||
|
|
||||
{/* This element is to trick the browser into centering the modal contents. */} |
|
||||
<span |
|
||||
className="inline-block h-screen align-middle" |
|
||||
aria-hidden="true" |
|
||||
> |
|
||||
​ |
|
||||
</span> |
|
||||
<Transition.Child |
|
||||
className="inline-block py-8 px-2 h-screen w-full max-w-4xl" |
|
||||
enter="ease-out duration-300" |
|
||||
enterFrom="opacity-0 scale-95" |
|
||||
enterTo="opacity-100 scale-100" |
|
||||
leave="ease-in duration-200" |
|
||||
leaveFrom="opacity-100 scale-100" |
|
||||
leaveTo="opacity-0 scale-95" |
|
||||
> |
|
||||
<div className="inline-flex flex-col w-full max-w-4xl text-left align-middle transition-all transform overflow-hidden rounded-2xl bg-white dark:bg-neutral-900 dark:border dark:border-neutral-700 dark:text-neutral-100 shadow-xl h-full"> |
|
||||
<div className="relative flex-shrink-0 px-6 py-4 border-b border-neutral-200 dark:border-neutral-800 text-center"> |
|
||||
<Dialog.Title |
|
||||
as="h3" |
|
||||
className="text-lg font-medium leading-6 text-gray-900" |
|
||||
> |
|
||||
Experiences filters |
|
||||
</Dialog.Title> |
|
||||
<span className="absolute left-3 top-3"> |
|
||||
<ButtonClose onClick={closeModalMoreFilter} /> |
|
||||
</span> |
|
||||
</div> |
|
||||
|
|
||||
<div className="flex-grow overflow-y-auto"> |
|
||||
<div className="px-4 sm:px-6 divide-y divide-neutral-200 dark:divide-neutral-800"> |
|
||||
<div className="py-7"> |
|
||||
<h3 className="text-xl font-medium"> |
|
||||
Type of experiences |
|
||||
</h3> |
|
||||
<div className="mt-6 relative "> |
|
||||
{renderMoreFilterItem(moreFilter1)} |
|
||||
</div> |
|
||||
</div> |
|
||||
<div className="py-7"> |
|
||||
<h3 className="text-xl font-medium">Time of day</h3> |
|
||||
<div className="mt-6 relative "> |
|
||||
{renderMoreFilterItem(moreFilter2)} |
|
||||
</div> |
|
||||
</div> |
|
||||
|
|
||||
{/* --------- */} |
|
||||
{/* ---- */} |
|
||||
<div className="py-7"> |
|
||||
<h3 className="text-xl font-medium">Range Prices</h3> |
|
||||
<div className="mt-6 relative "> |
|
||||
<div className="relative flex flex-col space-y-8"> |
|
||||
<div className="space-y-5"> |
|
||||
<Slider |
|
||||
className="text-red-400" |
|
||||
min={0} |
|
||||
max={2000} |
|
||||
defaultValue={[0, 1000]} |
|
||||
allowCross={false} |
|
||||
onChange={(e) => setRangePrices(e as number[])} |
|
||||
/> |
|
||||
</div> |
|
||||
|
|
||||
<div className="flex justify-between space-x-5"> |
|
||||
<div> |
|
||||
<label |
|
||||
htmlFor="minPrice" |
|
||||
className="block text-sm font-medium text-neutral-700 dark:text-neutral-300" |
|
||||
> |
|
||||
Min price |
|
||||
</label> |
|
||||
<div className="mt-1 relative rounded-md"> |
|
||||
<div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none"> |
|
||||
<span className="text-neutral-500 sm:text-sm"> |
|
||||
$ |
|
||||
</span> |
|
||||
</div> |
|
||||
<input |
|
||||
type="text" |
|
||||
name="minPrice" |
|
||||
disabled |
|
||||
id="minPrice" |
|
||||
className="focus:ring-indigo-500 focus:border-indigo-500 block w-full pl-7 pr-3 sm:text-sm border-neutral-200 rounded-full text-neutral-900" |
|
||||
value={rangePrices[0]} |
|
||||
/> |
|
||||
</div> |
|
||||
</div> |
|
||||
<div> |
|
||||
<label |
|
||||
htmlFor="maxPrice" |
|
||||
className="block text-sm font-medium text-neutral-700 dark:text-neutral-300" |
|
||||
> |
|
||||
Max price |
|
||||
</label> |
|
||||
<div className="mt-1 relative rounded-md"> |
|
||||
<div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none"> |
|
||||
<span className="text-neutral-500 sm:text-sm"> |
|
||||
$ |
|
||||
</span> |
|
||||
</div> |
|
||||
<input |
|
||||
type="text" |
|
||||
disabled |
|
||||
name="maxPrice" |
|
||||
id="maxPrice" |
|
||||
className="focus:ring-indigo-500 focus:border-indigo-500 block w-full pl-7 pr-3 sm:text-sm border-neutral-200 rounded-full text-neutral-900" |
|
||||
value={rangePrices[1]} |
|
||||
/> |
|
||||
</div> |
|
||||
</div> |
|
||||
</div> |
|
||||
</div> |
|
||||
</div> |
|
||||
</div> |
|
||||
</div> |
|
||||
</div> |
|
||||
|
|
||||
<div className="p-4 sm:p-6 flex-shrink-0 bg-neutral-50 dark:bg-neutral-900 dark:border-t dark:border-neutral-800 flex items-center justify-between"> |
|
||||
<ButtonThird |
|
||||
onClick={closeModalMoreFilter} |
|
||||
sizeClass="px-4 py-2 sm:px-5" |
|
||||
> |
|
||||
Clear |
|
||||
</ButtonThird> |
|
||||
<ButtonPrimary |
|
||||
onClick={closeModalMoreFilter} |
|
||||
sizeClass="px-4 py-2 sm:px-5" |
|
||||
> |
|
||||
Apply |
|
||||
</ButtonPrimary> |
|
||||
</div> |
|
||||
</div> |
|
||||
</Transition.Child> |
|
||||
</div> |
|
||||
</Dialog> |
|
||||
</Transition> |
|
||||
</div> |
|
||||
); |
|
||||
}; |
|
||||
|
|
||||
return ( |
|
||||
<div className="flex lg:space-x-4"> |
|
||||
<div className="hidden lg:flex space-x-4"> |
|
||||
{renderTabsTypeOfPlace()} |
|
||||
{renderTabsPriceRage()} |
|
||||
{renderTabsTimeOfDay()} |
|
||||
{renderTabOnSale()} |
|
||||
</div> |
|
||||
<div className="flex lg:hidden space-x-4"> |
|
||||
{renderTabMobileFilter()} |
|
||||
{renderTabOnSale()} |
|
||||
</div> |
|
||||
</div> |
|
||||
); |
|
||||
}; |
|
||||
|
|
||||
export default TabFilters; |
|
@ -1,56 +0,0 @@ |
|||||
import BackgroundSection from "@/components/BackgroundSection"; |
|
||||
import BgGlassmorphism from "@/components/BgGlassmorphism"; |
|
||||
import SectionGridAuthorBox from "@/components/SectionGridAuthorBox"; |
|
||||
import SectionSliderNewCategories from "@/components/SectionSliderNewCategories"; |
|
||||
import SectionSubscribe2 from "@/components/SectionSubscribe2"; |
|
||||
import React, { ReactNode } from "react"; |
|
||||
import SectionHeroArchivePage from "../(server-components)/SectionHeroArchivePage"; |
|
||||
|
|
||||
const Layout = ({ children }: { children: ReactNode }) => { |
|
||||
return ( |
|
||||
<div className={`nc-ListingStayPage relative `}> |
|
||||
<BgGlassmorphism /> |
|
||||
|
|
||||
{/* SECTION HERO */} |
|
||||
<div className="container pt-10 pb-24 lg:pt-16 lg:pb-28"> |
|
||||
<SectionHeroArchivePage |
|
||||
currentPage="Experiences" |
|
||||
currentTab="Experiences" |
|
||||
listingType={ |
|
||||
<> |
|
||||
<i className="text-2xl las la-umbrella-beach"></i> |
|
||||
<span className="ml-2.5">1599 experiences</span> |
|
||||
</> |
|
||||
} |
|
||||
/> |
|
||||
</div> |
|
||||
|
|
||||
{children} |
|
||||
|
|
||||
<div className="container overflow-hidden"> |
|
||||
{/* SECTION 1 */} |
|
||||
<div className="relative py-16"> |
|
||||
<BackgroundSection /> |
|
||||
<SectionSliderNewCategories |
|
||||
heading="Explore by types of stays" |
|
||||
subHeading="Explore houses based on 10 types of stays" |
|
||||
categoryCardType="card5" |
|
||||
itemPerRow={5} |
|
||||
sliderStyle="style2" |
|
||||
/> |
|
||||
</div> |
|
||||
|
|
||||
{/* SECTION */} |
|
||||
<SectionSubscribe2 className="py-24 lg:py-28" /> |
|
||||
|
|
||||
{/* SECTION */} |
|
||||
<div className="relative py-16 mb-24 lg:mb-28"> |
|
||||
<BackgroundSection className="bg-orange-50 dark:bg-black dark:bg-opacity-20 " /> |
|
||||
<SectionGridAuthorBox /> |
|
||||
</div> |
|
||||
</div> |
|
||||
</div> |
|
||||
); |
|
||||
}; |
|
||||
|
|
||||
export default Layout; |
|
@ -1,14 +0,0 @@ |
|||||
import React, { FC } from "react"; |
|
||||
import SectionGridHasMap from "../SectionGridHasMap"; |
|
||||
|
|
||||
export interface ListingExperiencesMapPageProps {} |
|
||||
|
|
||||
const ListingExperiencesMapPage: FC<ListingExperiencesMapPageProps> = ({}) => { |
|
||||
return ( |
|
||||
<div className="container pb-24 lg:pb-28 2xl:pl-10 xl:pr-0 xl:max-w-none"> |
|
||||
<SectionGridHasMap /> |
|
||||
</div> |
|
||||
); |
|
||||
}; |
|
||||
|
|
||||
export default ListingExperiencesMapPage; |
|
@ -1,14 +0,0 @@ |
|||||
import React, { FC } from "react"; |
|
||||
import SectionGridFilterCard from "../SectionGridFilterCard"; |
|
||||
|
|
||||
export interface ListingExperiencesPageProps {} |
|
||||
|
|
||||
const ListingExperiencesPage: FC<ListingExperiencesPageProps> = ({}) => { |
|
||||
return ( |
|
||||
<div className="container relative"> |
|
||||
<SectionGridFilterCard className="pb-24 lg:pb-28" /> |
|
||||
</div> |
|
||||
); |
|
||||
}; |
|
||||
|
|
||||
export default ListingExperiencesPage; |
|
@ -1,195 +0,0 @@ |
|||||
import SectionSliderNewCategories from "@/components/SectionSliderNewCategories"; |
|
||||
import React from "react"; |
|
||||
import SectionSubscribe2 from "@/components/SectionSubscribe2"; |
|
||||
import SectionOurFeatures from "@/components/SectionOurFeatures"; |
|
||||
import SectionHowItWork from "@/components/SectionHowItWork"; |
|
||||
import BackgroundSection from "@/components/BackgroundSection"; |
|
||||
import { TaxonomyType } from "@/data/types"; |
|
||||
import SectionGridAuthorBox from "@/components/SectionGridAuthorBox"; |
|
||||
//
|
|
||||
import logo1 from "@/images/logos/nomal/1.png"; |
|
||||
import logo1Dark from "@/images/logos/dark/1.png"; |
|
||||
//
|
|
||||
import logo2 from "@/images/logos/nomal/2.png"; |
|
||||
import logo2Dark from "@/images/logos/dark/2.png"; |
|
||||
//
|
|
||||
import logo3 from "@/images/logos/nomal/3.png"; |
|
||||
import logo3Dark from "@/images/logos/dark/3.png"; |
|
||||
//
|
|
||||
import logo4 from "@/images/logos/nomal/4.png"; |
|
||||
import logo4Dark from "@/images/logos/dark/4.png"; |
|
||||
//
|
|
||||
import logo5 from "@/images/logos/nomal/5.png"; |
|
||||
import logo5Dark from "@/images/logos/dark/5.png"; |
|
||||
//
|
|
||||
import HIW1img from "@/images/HIW2-1.png"; |
|
||||
import HIW2img from "@/images/HIW2-2.png"; |
|
||||
import HIW3img from "@/images/HIW2-3.png"; |
|
||||
import HIW1imgDark from "@/images/HIW2-1-dark.png"; |
|
||||
import HIW2imgDark from "@/images/HIW2-2-dark.png"; |
|
||||
import HIW3imgDark from "@/images/HIW2-3-dark.png"; |
|
||||
import rightImgPng from "@/images/our-features-2.png"; |
|
||||
|
|
||||
import SectionGridFeatureProperty from "../SectionGridFeatureProperty"; |
|
||||
import SectionDowloadApp from "../SectionDowloadApp"; |
|
||||
import SectionHero2 from "@/app/(server-components)/SectionHero2"; |
|
||||
import Image from "next/image"; |
|
||||
|
|
||||
const DEMO_CATS_2: TaxonomyType[] = [ |
|
||||
{ |
|
||||
id: "1", |
|
||||
href: "/listing-real-estate", |
|
||||
name: "Enjoy the great cold", |
|
||||
taxonomy: "category", |
|
||||
count: 188288, |
|
||||
thumbnail: |
|
||||
"https://images.pexels.com/photos/5764100/pexels-photo-5764100.jpeg?auto=compress&cs=tinysrgb&dpr=3&h=750&w=1260", |
|
||||
}, |
|
||||
{ |
|
||||
id: "2", |
|
||||
href: "/listing-real-estate", |
|
||||
name: "Sleep in a floating way", |
|
||||
taxonomy: "category", |
|
||||
count: 188288, |
|
||||
thumbnail: |
|
||||
"https://images.pexels.com/photos/2869499/pexels-photo-2869499.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=750&w=1260", |
|
||||
}, |
|
||||
{ |
|
||||
id: "3", |
|
||||
href: "/listing-real-estate", |
|
||||
name: "In the billionaire's house", |
|
||||
taxonomy: "category", |
|
||||
count: 188288, |
|
||||
thumbnail: |
|
||||
"https://images.pexels.com/photos/7031413/pexels-photo-7031413.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=750&w=1260", |
|
||||
}, |
|
||||
{ |
|
||||
id: "4", |
|
||||
href: "/listing-real-estate", |
|
||||
name: "Cool in the deep forest", |
|
||||
taxonomy: "category", |
|
||||
count: 188288, |
|
||||
thumbnail: |
|
||||
"https://images.pexels.com/photos/247532/pexels-photo-247532.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=750&w=1260", |
|
||||
}, |
|
||||
{ |
|
||||
id: "5", |
|
||||
href: "/listing-real-estate", |
|
||||
name: "In the billionaire's house", |
|
||||
taxonomy: "category", |
|
||||
count: 188288, |
|
||||
thumbnail: |
|
||||
"https://images.pexels.com/photos/7031413/pexels-photo-7031413.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=750&w=1260", |
|
||||
}, |
|
||||
{ |
|
||||
id: "6", |
|
||||
href: "/listing-real-estate", |
|
||||
name: "Sleep in a floating way", |
|
||||
taxonomy: "category", |
|
||||
count: 188288, |
|
||||
thumbnail: |
|
||||
"https://images.pexels.com/photos/2869499/pexels-photo-2869499.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=750&w=1260", |
|
||||
}, |
|
||||
{ |
|
||||
id: "7", |
|
||||
href: "/listing-real-estate", |
|
||||
name: "In the billionaire's house", |
|
||||
taxonomy: "category", |
|
||||
count: 188288, |
|
||||
thumbnail: |
|
||||
"https://images.pexels.com/photos/7031413/pexels-photo-7031413.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=750&w=1260", |
|
||||
}, |
|
||||
]; |
|
||||
|
|
||||
function PageHome2() { |
|
||||
return ( |
|
||||
<main className="nc-PageHome2 relative overflow-hidden"> |
|
||||
<div className="container relative space-y-24 mb-24 lg:space-y-28 lg:mb-28"> |
|
||||
<SectionHero2 className="" /> |
|
||||
|
|
||||
<div className="ncSectionLogos grid grid-cols-3 lg:grid-cols-5 gap-5 sm:gap-16"> |
|
||||
<div className="flex items-end justify-center"> |
|
||||
<Image className="block dark:hidden" src={logo1} alt="logo1" /> |
|
||||
<Image className="hidden dark:block" src={logo1Dark} alt="logo1" /> |
|
||||
</div> |
|
||||
<div className="flex items-end justify-center"> |
|
||||
<Image className="block dark:hidden" src={logo4} alt="logo4" /> |
|
||||
<Image className="hidden dark:block" src={logo4Dark} alt="logo4" /> |
|
||||
</div> |
|
||||
<div className="flex items-end justify-center"> |
|
||||
<Image className="block dark:hidden" src={logo2} alt="logo2" /> |
|
||||
<Image className="hidden dark:block" src={logo2Dark} alt="logo2" /> |
|
||||
</div> |
|
||||
<div className="flex items-end justify-center"> |
|
||||
<Image className="block dark:hidden" src={logo3} alt="logo3" /> |
|
||||
<Image className="hidden dark:block" src={logo3Dark} alt="logo3" /> |
|
||||
</div> |
|
||||
|
|
||||
<div className="flex items-end justify-center"> |
|
||||
<Image className="block dark:hidden" src={logo5} alt="logo5" /> |
|
||||
<Image className="hidden dark:block" src={logo5Dark} alt="logo5" /> |
|
||||
</div> |
|
||||
</div> |
|
||||
|
|
||||
<SectionHowItWork |
|
||||
data={[ |
|
||||
{ |
|
||||
id: 1, |
|
||||
img: HIW1img, |
|
||||
imgDark: HIW1imgDark, |
|
||||
title: "Smart search", |
|
||||
desc: "Name the area or type of home you are looking for the search bar. Our app will find you the perfect match.", |
|
||||
}, |
|
||||
{ |
|
||||
id: 2, |
|
||||
img: HIW2img, |
|
||||
imgDark: HIW2imgDark, |
|
||||
title: "Choose property", |
|
||||
desc: "From the number of options our app will provide, you can select any property that you like to explore.", |
|
||||
}, |
|
||||
{ |
|
||||
id: 3, |
|
||||
img: HIW3img, |
|
||||
imgDark: HIW3imgDark, |
|
||||
title: "Book you property", |
|
||||
desc: "Find a home or space from our search bar. Enter your specific location, property type and price range.", |
|
||||
}, |
|
||||
]} |
|
||||
/> |
|
||||
|
|
||||
<div className="relative py-16"> |
|
||||
<BackgroundSection /> |
|
||||
<SectionGridFeatureProperty /> |
|
||||
</div> |
|
||||
|
|
||||
<SectionOurFeatures type="type2" rightImg={rightImgPng} /> |
|
||||
|
|
||||
<SectionDowloadApp /> |
|
||||
|
|
||||
<SectionSliderNewCategories |
|
||||
categories={DEMO_CATS_2} |
|
||||
categoryCardType="card4" |
|
||||
itemPerRow={4} |
|
||||
heading="Suggestions for discovery" |
|
||||
subHeading="Popular places to stay that Chisfis recommends for you" |
|
||||
/> |
|
||||
|
|
||||
<div className="relative py-16"> |
|
||||
<BackgroundSection className="bg-neutral-100 dark:bg-black dark:bg-opacity-20 " /> |
|
||||
<SectionGridAuthorBox boxCard="box2" /> |
|
||||
</div> |
|
||||
|
|
||||
<SectionSliderNewCategories |
|
||||
heading="Explore by types of stays" |
|
||||
subHeading="Explore houses based on 10 types of stays" |
|
||||
categoryCardType="card5" |
|
||||
itemPerRow={5} |
|
||||
/> |
|
||||
|
|
||||
<SectionSubscribe2 /> |
|
||||
</div> |
|
||||
</main> |
|
||||
); |
|
||||
} |
|
||||
|
|
||||
export default PageHome2; |
|
@ -1,104 +0,0 @@ |
|||||
import React from "react"; |
|
||||
import SectionSubscribe2 from "@/components/SectionSubscribe2"; |
|
||||
import BackgroundSection from "@/components/BackgroundSection"; |
|
||||
import BgGlassmorphism from "@/components/BgGlassmorphism"; |
|
||||
import { TaxonomyType } from "@/data/types"; |
|
||||
import SectionGridAuthorBox from "@/components/SectionGridAuthorBox"; |
|
||||
import SectionGridCategoryBox from "@/components/SectionGridCategoryBox"; |
|
||||
import SectionHero3 from "@/app/(server-components)/SectionHero3"; |
|
||||
import CardCategory6 from "@/components/CardCategory6"; |
|
||||
import SectionGridFeaturePlaces from "@/components/SectionGridFeaturePlaces"; |
|
||||
|
|
||||
const DEMO_CATS_2: TaxonomyType[] = [ |
|
||||
{ |
|
||||
id: "1", |
|
||||
href: "/listing-stay", |
|
||||
name: "Enjoy the great cold", |
|
||||
taxonomy: "category", |
|
||||
count: 188288, |
|
||||
thumbnail: |
|
||||
"https://images.pexels.com/photos/5764100/pexels-photo-5764100.jpeg?auto=compress&cs=tinysrgb&dpr=3&h=750&w=1260", |
|
||||
}, |
|
||||
{ |
|
||||
id: "222", |
|
||||
href: "/listing-stay", |
|
||||
name: "Sleep in a floating way", |
|
||||
taxonomy: "category", |
|
||||
count: 188288, |
|
||||
thumbnail: |
|
||||
"https://images.pexels.com/photos/2869499/pexels-photo-2869499.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=750&w=1260", |
|
||||
}, |
|
||||
{ |
|
||||
id: "3", |
|
||||
href: "/listing-stay", |
|
||||
name: "In the billionaire's house", |
|
||||
taxonomy: "category", |
|
||||
count: 188288, |
|
||||
thumbnail: |
|
||||
"https://images.pexels.com/photos/7031413/pexels-photo-7031413.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=750&w=1260", |
|
||||
}, |
|
||||
{ |
|
||||
id: "4", |
|
||||
href: "/listing-stay", |
|
||||
name: "Cool in the deep forest", |
|
||||
taxonomy: "category", |
|
||||
count: 188288, |
|
||||
thumbnail: |
|
||||
"https://images.pexels.com/photos/247532/pexels-photo-247532.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=750&w=1260", |
|
||||
}, |
|
||||
{ |
|
||||
id: "5", |
|
||||
href: "/listing-stay", |
|
||||
name: "In the billionaire's house", |
|
||||
taxonomy: "category", |
|
||||
count: 188288, |
|
||||
thumbnail: |
|
||||
"https://images.pexels.com/photos/7031413/pexels-photo-7031413.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=750&w=1260", |
|
||||
}, |
|
||||
]; |
|
||||
|
|
||||
function PageHome3() { |
|
||||
return ( |
|
||||
<main className="nc-PageHome3 relative overflow-hidden"> |
|
||||
{/* GLASSMOPHIN */} |
|
||||
<BgGlassmorphism /> |
|
||||
|
|
||||
{/* SECTION HERO */} |
|
||||
<div className="container px-1 sm:px-4 mb-24 "> |
|
||||
<SectionHero3 className="" /> |
|
||||
</div> |
|
||||
|
|
||||
<div className="container relative space-y-24 mb-24 "> |
|
||||
{/* SECTION 1 */} |
|
||||
<div className="grid grid-cols-12 gap-6"> |
|
||||
<div className="col-span-12 sm:col-span-6 lg:col-span-4 flex"> |
|
||||
<CardCategory6 taxonomy={DEMO_CATS_2[0]} /> |
|
||||
</div> |
|
||||
<div className="col-span-12 sm:col-span-6 lg:col-span-4 grid grid-rows-2 gap-6"> |
|
||||
<CardCategory6 taxonomy={DEMO_CATS_2[3]} /> |
|
||||
<CardCategory6 taxonomy={DEMO_CATS_2[1]} /> |
|
||||
</div> |
|
||||
<div className="col-span-12 sm:col-span-6 lg:col-span-4 flex"> |
|
||||
<CardCategory6 taxonomy={DEMO_CATS_2[4]} /> |
|
||||
</div> |
|
||||
</div> |
|
||||
|
|
||||
{/* SECTION */} |
|
||||
<SectionGridCategoryBox /> |
|
||||
|
|
||||
{/* SECTION */} |
|
||||
<div className="relative py-16"> |
|
||||
<BackgroundSection /> |
|
||||
<SectionGridAuthorBox boxCard="box2" /> |
|
||||
</div> |
|
||||
|
|
||||
<SectionGridFeaturePlaces /> |
|
||||
|
|
||||
{/* SECTION */} |
|
||||
<SectionSubscribe2 /> |
|
||||
</div> |
|
||||
</main> |
|
||||
); |
|
||||
} |
|
||||
|
|
||||
export default PageHome3; |
|
@ -1,55 +0,0 @@ |
|||||
import React, { FC, Fragment, useState } from "react"; |
|
||||
import DatePicker from "react-datepicker"; |
|
||||
import DatePickerCustomHeaderTwoMonth from "@/components/DatePickerCustomHeaderTwoMonth"; |
|
||||
import DatePickerCustomDay from "@/components/DatePickerCustomDay"; |
|
||||
|
|
||||
const SectionDateRange = () => { |
|
||||
const [startDate, setStartDate] = useState<Date | null>( |
|
||||
new Date("2023/02/06") |
|
||||
); |
|
||||
const [endDate, setEndDate] = useState<Date | null>(new Date("2023/02/23")); |
|
||||
const onChangeDate = (dates: [Date | null, Date | null]) => { |
|
||||
const [start, end] = dates; |
|
||||
setStartDate(start); |
|
||||
setEndDate(end); |
|
||||
}; |
|
||||
|
|
||||
const renderSectionCheckIndate = () => { |
|
||||
return ( |
|
||||
<div className="listingSection__wrap overflow-hidden"> |
|
||||
{/* HEADING */} |
|
||||
<div> |
|
||||
<h2 className="text-2xl font-semibold">Availability</h2> |
|
||||
<span className="block mt-2 text-neutral-500 dark:text-neutral-400"> |
|
||||
Prices may increase on weekends or holidays |
|
||||
</span> |
|
||||
</div> |
|
||||
<div className="w-14 border-b border-neutral-200 dark:border-neutral-700"></div> |
|
||||
{/* CONTENT */} |
|
||||
|
|
||||
<div className=""> |
|
||||
<DatePicker |
|
||||
selected={startDate} |
|
||||
onChange={onChangeDate} |
|
||||
startDate={startDate} |
|
||||
endDate={endDate} |
|
||||
selectsRange |
|
||||
monthsShown={2} |
|
||||
showPopperArrow={false} |
|
||||
inline |
|
||||
renderCustomHeader={(p) => ( |
|
||||
<DatePickerCustomHeaderTwoMonth {...p} /> |
|
||||
)} |
|
||||
renderDayContents={(day, date) => ( |
|
||||
<DatePickerCustomDay dayOfMonth={day} date={date} /> |
|
||||
)} |
|
||||
/> |
|
||||
</div> |
|
||||
</div> |
|
||||
); |
|
||||
}; |
|
||||
|
|
||||
// return renderSectionCheckIndate();
|
|
||||
}; |
|
||||
|
|
||||
export default SectionDateRange; |
|
@ -1,77 +0,0 @@ |
|||||
"use client"; |
|
||||
|
|
||||
import BackgroundSection from "@/components/BackgroundSection"; |
|
||||
import ListingImageGallery from "@/components/listing-image-gallery/ListingImageGallery"; |
|
||||
import SectionSliderNewCategories from "@/components/SectionSliderNewCategories"; |
|
||||
import SectionSubscribe2 from "@/components/SectionSubscribe2"; |
|
||||
import { usePathname, useRouter, useSearchParams } from "next/navigation"; |
|
||||
import React, { ReactNode, useEffect, useState } from "react"; |
|
||||
import MobileFooterSticky from "./(components)/MobileFooterSticky"; |
|
||||
import { imageGallery as listingStayImageGallery } from "./listing-stay-detail/constant"; |
|
||||
import { imageGallery as listingCarImageGallery } from "./listing-car-detail/constant"; |
|
||||
import { imageGallery as listingExperienceImageGallery } from "./listing-experiences-detail/constant"; |
|
||||
import { Route } from "next"; |
|
||||
import axiosInstance from "@/components/api/axios"; |
|
||||
import axios from "axios"; |
|
||||
|
|
||||
const DetailtLayout = ({ children }: { children: ReactNode }) => { |
|
||||
const router = useRouter(); |
|
||||
const thisPathname = usePathname(); |
|
||||
const searchParams = useSearchParams(); |
|
||||
const modal = searchParams?.get("modal"); |
|
||||
|
|
||||
const handleCloseModalImageGallery = () => { |
|
||||
let params = new URLSearchParams(document.location.search); |
|
||||
params.delete("modal"); |
|
||||
router.push(`${thisPathname}/?${params.toString()}` as Route); |
|
||||
}; |
|
||||
|
|
||||
const getImageGalleryListing = () => { |
|
||||
|
|
||||
|
|
||||
if (thisPathname?.includes("/listing-stay-detail")) { |
|
||||
return listingStayImageGallery; |
|
||||
} |
|
||||
if (thisPathname?.includes("/listing-car-detail")) { |
|
||||
return listingCarImageGallery; |
|
||||
} |
|
||||
if (thisPathname?.includes("/listing-experiences-detail")) { |
|
||||
return listingExperienceImageGallery; |
|
||||
} |
|
||||
|
|
||||
return []; |
|
||||
}; |
|
||||
|
|
||||
|
|
||||
return ( |
|
||||
<div className="ListingDetailPage"> |
|
||||
<ListingImageGallery |
|
||||
isShowModal={modal === "PHOTO_TOUR_SCROLLABLE"} |
|
||||
onClose={handleCloseModalImageGallery} |
|
||||
images={getImageGalleryListing()} |
|
||||
/> |
|
||||
|
|
||||
<div className="container ListingDetailPage__content">{children}</div> |
|
||||
|
|
||||
{/* OTHER SECTION */} |
|
||||
<div className="container py-24 lg:py-32"> |
|
||||
<div className="relative py-16"> |
|
||||
<BackgroundSection /> |
|
||||
<SectionSliderNewCategories |
|
||||
heading="Explore by types of stays" |
|
||||
subHeading="Explore houses based on 10 types of stays" |
|
||||
categoryCardType="card5" |
|
||||
itemPerRow={5} |
|
||||
sliderStyle="style2" |
|
||||
/> |
|
||||
</div> |
|
||||
<SectionSubscribe2 className="pt-24 lg:pt-32" /> |
|
||||
</div> |
|
||||
|
|
||||
{/* STICKY FOOTER MOBILE */} |
|
||||
<MobileFooterSticky /> |
|
||||
</div> |
|
||||
); |
|
||||
}; |
|
||||
|
|
||||
export default DetailtLayout; |
|
@ -1,118 +0,0 @@ |
|||||
"use client"; |
|
||||
|
|
||||
import React, { Fragment, useState } from "react"; |
|
||||
import { FC } from "react"; |
|
||||
import DatePicker from "react-datepicker"; |
|
||||
import { Popover, Transition } from "@headlessui/react"; |
|
||||
import { CalendarIcon } from "@heroicons/react/24/outline"; |
|
||||
import DatePickerCustomHeaderTwoMonth from "@/components/DatePickerCustomHeaderTwoMonth"; |
|
||||
import DatePickerCustomDay from "@/components/DatePickerCustomDay"; |
|
||||
import ClearDataButton from "@/app/(client-components)/(HeroSearchForm)/ClearDataButton"; |
|
||||
|
|
||||
export interface RentalCarDatesRangeInputProps { |
|
||||
className?: string; |
|
||||
} |
|
||||
|
|
||||
const RentalCarDatesRangeInput: FC<RentalCarDatesRangeInputProps> = ({ |
|
||||
className = "", |
|
||||
}) => { |
|
||||
const [startDate, setStartDate] = useState<Date | null>( |
|
||||
new Date("2023/03/01") |
|
||||
); |
|
||||
const [endDate, setEndDate] = useState<Date | null>(new Date("2023/03/16")); |
|
||||
|
|
||||
const onChangeDate = (dates: [Date | null, Date | null]) => { |
|
||||
const [start, end] = dates; |
|
||||
setStartDate(start); |
|
||||
setEndDate(end); |
|
||||
}; |
|
||||
|
|
||||
const renderInput = () => { |
|
||||
return ( |
|
||||
<> |
|
||||
<div className="text-neutral-300 dark:text-neutral-400"> |
|
||||
<CalendarIcon className="w-5 h-5 lg:w-7 lg:h-7" /> |
|
||||
</div> |
|
||||
<div className="flex-grow text-left"> |
|
||||
<span className="block xl:text-lg font-semibold"> |
|
||||
{startDate?.toLocaleDateString("en-US", { |
|
||||
month: "short", |
|
||||
day: "2-digit", |
|
||||
}) || "Add dates"} |
|
||||
{endDate |
|
||||
? " - " + |
|
||||
endDate?.toLocaleDateString("en-US", { |
|
||||
month: "short", |
|
||||
day: "2-digit", |
|
||||
}) |
|
||||
: ""} |
|
||||
</span> |
|
||||
<span className="block mt-1 text-sm text-neutral-400 leading-none font-light"> |
|
||||
{"Pick up - Drop off"} |
|
||||
</span> |
|
||||
</div> |
|
||||
</> |
|
||||
); |
|
||||
}; |
|
||||
|
|
||||
return ( |
|
||||
<> |
|
||||
<Popover |
|
||||
className={`RentalCarDatesRangeInput relative flex w-full ${className}`} |
|
||||
> |
|
||||
{({ open }) => ( |
|
||||
<> |
|
||||
<div |
|
||||
className={`flex-1 flex items-center focus:outline-none rounded-2xl ${ |
|
||||
open ? "shadow-lg" : "" |
|
||||
}`}
|
|
||||
> |
|
||||
<Popover.Button |
|
||||
className={`flex-1 flex relative p-3 items-center space-x-3 focus:outline-none `} |
|
||||
> |
|
||||
{renderInput()} |
|
||||
|
|
||||
{startDate && open && ( |
|
||||
<ClearDataButton onClick={() => onChangeDate([null, null])} /> |
|
||||
)} |
|
||||
</Popover.Button> |
|
||||
</div> |
|
||||
|
|
||||
<Transition |
|
||||
as={Fragment} |
|
||||
enter="transition ease-out duration-200" |
|
||||
enterFrom="opacity-0 translate-y-1" |
|
||||
enterTo="opacity-100 translate-y-0" |
|
||||
leave="transition ease-in duration-150" |
|
||||
leaveFrom="opacity-100 translate-y-0" |
|
||||
leaveTo="opacity-0 translate-y-1" |
|
||||
> |
|
||||
<Popover.Panel className="absolute z-10 mt-3 top-full right-0 xl:-right-10 w-screen max-w-sm px-4 sm:px-0 lg:max-w-3xl"> |
|
||||
<div className="overflow-hidden rounded-3xl shadow-lg ring-1 ring-black ring-opacity-5 bg-white dark:bg-neutral-800 p-8"> |
|
||||
<DatePicker |
|
||||
selected={startDate} |
|
||||
onChange={onChangeDate} |
|
||||
startDate={startDate} |
|
||||
endDate={endDate} |
|
||||
selectsRange |
|
||||
monthsShown={2} |
|
||||
showPopperArrow={false} |
|
||||
inline |
|
||||
renderCustomHeader={(p) => ( |
|
||||
<DatePickerCustomHeaderTwoMonth {...p} /> |
|
||||
)} |
|
||||
renderDayContents={(day, date) => ( |
|
||||
<DatePickerCustomDay dayOfMonth={day} date={date} /> |
|
||||
)} |
|
||||
/> |
|
||||
</div> |
|
||||
</Popover.Panel> |
|
||||
</Transition> |
|
||||
</> |
|
||||
)} |
|
||||
</Popover> |
|
||||
</> |
|
||||
); |
|
||||
}; |
|
||||
|
|
||||
export default RentalCarDatesRangeInput; |
|
@ -1,68 +0,0 @@ |
|||||
import { ListingGalleryImage } from "@/components/listing-image-gallery/utils/types"; |
|
||||
import carUtilities1 from "@/images/carUtilities/1.png"; |
|
||||
import carUtilities2 from "@/images/carUtilities/2.png"; |
|
||||
import carUtilities3 from "@/images/carUtilities/3.png"; |
|
||||
import carUtilities4 from "@/images/carUtilities/4.png"; |
|
||||
import carUtilities5 from "@/images/carUtilities/5.png"; |
|
||||
import carUtilities6 from "@/images/carUtilities/6.png"; |
|
||||
import carUtilities7 from "@/images/carUtilities/7.png"; |
|
||||
import carUtilities8 from "@/images/carUtilities/8.png"; |
|
||||
|
|
||||
export const PHOTOS: string[] = [ |
|
||||
"https://images.pexels.com/photos/381292/pexels-photo-381292.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=750&w=1260", |
|
||||
"https://images.pexels.com/photos/2526128/pexels-photo-2526128.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=750&w=1260", |
|
||||
"https://images.pexels.com/photos/2827753/pexels-photo-2827753.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=750&w=1260", |
|
||||
"https://images.pexels.com/photos/1637859/pexels-photo-1637859.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=750&w=1260", |
|
||||
"https://images.pexels.com/photos/257851/pexels-photo-257851.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=750&w=1260", |
|
||||
"https://images.pexels.com/photos/457418/pexels-photo-457418.jpeg?auto=compress&cs=tinysrgb&w=1600", |
|
||||
"https://images.pexels.com/photos/1707820/pexels-photo-1707820.jpeg?auto=compress&cs=tinysrgb&w=1600", |
|
||||
"https://images.pexels.com/photos/712618/pexels-photo-712618.jpeg?auto=compress&cs=tinysrgb&w=1600", |
|
||||
"https://images.pexels.com/photos/3802508/pexels-photo-3802508.jpeg?auto=compress&cs=tinysrgb&w=1600", |
|
||||
"https://images.pexels.com/photos/945443/pexels-photo-945443.jpeg?auto=compress&cs=tinysrgb&w=1600", |
|
||||
"https://images.pexels.com/photos/1054211/pexels-photo-1054211.jpeg?auto=compress&cs=tinysrgb&w=1600", |
|
||||
"https://images.pexels.com/photos/189454/pexels-photo-189454.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=750&w=1260", |
|
||||
"https://images.pexels.com/photos/193995/pexels-photo-193995.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=750&w=1260", |
|
||||
"https://images.pexels.com/photos/575386/pexels-photo-575386.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=750&w=1260", |
|
||||
"https://images.pexels.com/photos/248687/pexels-photo-248687.jpeg?auto=compress&cs=tinysrgb&w=1600", |
|
||||
"https://images.pexels.com/photos/326259/pexels-photo-326259.jpeg?auto=compress&cs=tinysrgb&w=1600", |
|
||||
"https://images.pexels.com/photos/2127733/pexels-photo-2127733.jpeg?auto=compress&cs=tinysrgb&w=1600", |
|
||||
"https://images.pexels.com/photos/2882234/pexels-photo-2882234.jpeg?auto=compress&cs=tinysrgb&w=1600", |
|
||||
"https://images.pexels.com/photos/752615/pexels-photo-752615.jpeg?auto=compress&cs=tinysrgb&w=1600", |
|
||||
"https://images.pexels.com/photos/1210622/pexels-photo-1210622.jpeg?auto=compress&cs=tinysrgb&w=1600", |
|
||||
"https://images.pexels.com/photos/303316/pexels-photo-303316.jpeg?auto=compress&cs=tinysrgb&w=1600", |
|
||||
"https://images.pexels.com/photos/136872/pexels-photo-136872.jpeg?auto=compress&cs=tinysrgb&w=1600", |
|
||||
]; |
|
||||
|
|
||||
export const includes_demo = [ |
|
||||
{ name: "Free cancellation up to 48 hours before pick-up" }, |
|
||||
{ name: "Collision Damage Waiver with $214 deductible" }, |
|
||||
{ name: "Theft Protection with $19,999 excess" }, |
|
||||
{ name: "Unlimited mileage" }, |
|
||||
{ |
|
||||
name: "Car interiors and exteriors cleaned with disinfectant before pick-up", |
|
||||
}, |
|
||||
{ name: "Masks are required at the pick-up location" }, |
|
||||
]; |
|
||||
|
|
||||
export const Amenities_demos = [ |
|
||||
{ name: "59 MPG Combined, 58 City/60 Hwy", icon: carUtilities1 }, |
|
||||
{ |
|
||||
name: "Forward Collision-Avoidance Assist with Pedestrian Detection (FCA-Ped)", |
|
||||
icon: carUtilities2, |
|
||||
}, |
|
||||
{ name: "139-hp gas/electric combined", icon: carUtilities3 }, |
|
||||
{ name: "Proximity Key with push button start", icon: carUtilities4 }, |
|
||||
{ name: "8-inch color touchscreen display audio", icon: carUtilities5 }, |
|
||||
{ name: "Smart Cruise Control with Stop & Go (SCC)", icon: carUtilities6 }, |
|
||||
{ name: "LED Daytime Running Lights (DRL)", icon: carUtilities7 }, |
|
||||
{ name: "Blind-Spot Collision Warning (BCW)", icon: carUtilities8 }, |
|
||||
]; |
|
||||
|
|
||||
export const imageGallery: ListingGalleryImage[] = [...PHOTOS].map( |
|
||||
(item, index): ListingGalleryImage => { |
|
||||
return { |
|
||||
id: index, |
|
||||
url: item, |
|
||||
}; |
|
||||
} |
|
||||
); |
|
@ -1,542 +0,0 @@ |
|||||
"use client"; |
|
||||
|
|
||||
import React, { FC, useState } from "react"; |
|
||||
import { ArrowRightIcon, Squares2X2Icon } from "@heroicons/react/24/outline"; |
|
||||
import CommentListing from "@/components/CommentListing"; |
|
||||
import FiveStartIconForRate from "@/components/FiveStartIconForRate"; |
|
||||
import StartRating from "@/components/StartRating"; |
|
||||
import Avatar from "@/shared/Avatar"; |
|
||||
import Badge from "@/shared/Badge"; |
|
||||
import ButtonCircle from "@/shared/ButtonCircle"; |
|
||||
import ButtonPrimary from "@/shared/ButtonPrimary"; |
|
||||
import ButtonSecondary from "@/shared/ButtonSecondary"; |
|
||||
import Input from "@/shared/Input"; |
|
||||
import Image from "next/image"; |
|
||||
import { Amenities_demos, includes_demo, PHOTOS } from "./constant"; |
|
||||
import LikeSaveBtns from "@/components/LikeSaveBtns"; |
|
||||
import { usePathname, useRouter } from "next/navigation"; |
|
||||
import SectionDateRange from "../SectionDateRange"; |
|
||||
import RentalCarDatesRangeInput from "./RentalCarDatesRangeInput"; |
|
||||
import { Route } from "next"; |
|
||||
|
|
||||
export interface ListingCarDetailPageProps {} |
|
||||
|
|
||||
const ListingCarDetailPage: FC<ListingCarDetailPageProps> = ({}) => { |
|
||||
// USE STATE
|
|
||||
|
|
||||
const thisPathname = usePathname(); |
|
||||
const router = useRouter(); |
|
||||
|
|
||||
const handleOpenModalImageGallery = () => { |
|
||||
router.push(`${thisPathname}/?modal=PHOTO_TOUR_SCROLLABLE` as Route); |
|
||||
}; |
|
||||
|
|
||||
const renderSection1 = () => { |
|
||||
return ( |
|
||||
<div className="listingSection__wrap !space-y-6"> |
|
||||
{/* 1 */} |
|
||||
<div className="flex justify-between items-center"> |
|
||||
<Badge color="pink" name="BMW car" /> |
|
||||
<LikeSaveBtns /> |
|
||||
</div> |
|
||||
|
|
||||
{/* 2 */} |
|
||||
<h2 className="text-2xl sm:text-3xl lg:text-4xl font-semibold"> |
|
||||
BMW 3 Series Sedan |
|
||||
</h2> |
|
||||
|
|
||||
{/* 3 */} |
|
||||
<div className="flex items-center space-x-4"> |
|
||||
<StartRating /> |
|
||||
<span>·</span> |
|
||||
<span> |
|
||||
<i className="las la-map-marker-alt"></i> |
|
||||
<span className="ml-1"> Tokyo, Jappan</span> |
|
||||
</span> |
|
||||
</div> |
|
||||
|
|
||||
{/* 4 */} |
|
||||
<div className="flex items-center"> |
|
||||
<Avatar hasChecked sizeClass="h-10 w-10" radius="rounded-full" /> |
|
||||
<span className="ml-2.5 text-neutral-500 dark:text-neutral-400"> |
|
||||
Car owner{" "} |
|
||||
<span className="text-neutral-900 dark:text-neutral-200 font-medium"> |
|
||||
Kevin Francis |
|
||||
</span> |
|
||||
</span> |
|
||||
</div> |
|
||||
|
|
||||
{/* 5 */} |
|
||||
<div className="w-full border-b border-neutral-100 dark:border-neutral-700" /> |
|
||||
|
|
||||
{/* 6 */} |
|
||||
<div className="flex items-center justify-between xl:justify-start space-x-8 xl:space-x-12 text-sm text-neutral-700 dark:text-neutral-300"> |
|
||||
<div className="flex flex-col sm:flex-row items-center space-y-3 sm:space-y-0 text-center sm:text-left sm:space-x-3 "> |
|
||||
<i className="las la-user-friends text-2xl"></i> |
|
||||
<span className="">4 seats</span> |
|
||||
</div> |
|
||||
<div className="flex flex-col sm:flex-row items-center space-y-3 sm:space-y-0 text-center sm:text-left sm:space-x-3 "> |
|
||||
<i className="las la-dharmachakra text-2xl"></i> |
|
||||
<span className=""> Auto gearbox</span> |
|
||||
</div> |
|
||||
<div className="flex flex-col sm:flex-row items-center space-y-3 sm:space-y-0 text-center sm:text-left sm:space-x-3 "> |
|
||||
<i className="las la-suitcase text-2xl"></i> |
|
||||
<span className=""> 2 bags</span> |
|
||||
</div> |
|
||||
</div> |
|
||||
</div> |
|
||||
); |
|
||||
}; |
|
||||
|
|
||||
//
|
|
||||
const renderSectionTienIch = () => { |
|
||||
return ( |
|
||||
<div className="listingSection__wrap"> |
|
||||
<div> |
|
||||
<h2 className="text-2xl font-semibold"> |
|
||||
Vehicle parameters & utilities{" "} |
|
||||
</h2> |
|
||||
<span className="block mt-2 text-neutral-500 dark:text-neutral-400"> |
|
||||
Questions are at the heart of making things great. |
|
||||
</span> |
|
||||
</div> |
|
||||
<div className="w-14 border-b border-neutral-200 dark:border-neutral-700"></div> |
|
||||
{/* 6 */} |
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-y-6 gap-x-10 text-sm text-neutral-700 dark:text-neutral-300 "> |
|
||||
{/* TIEN ICH 1 */} |
|
||||
{Amenities_demos.map((item, index) => ( |
|
||||
<div key={index} className="flex items-center space-x-4 "> |
|
||||
<div className="w-10 flex-shrink-0"> |
|
||||
<Image src={item.icon} alt="" /> |
|
||||
</div> |
|
||||
<span>{item.name}</span> |
|
||||
</div> |
|
||||
))} |
|
||||
</div> |
|
||||
</div> |
|
||||
); |
|
||||
}; |
|
||||
|
|
||||
const renderSection2 = () => { |
|
||||
return ( |
|
||||
<div className="listingSection__wrap"> |
|
||||
<h2 className="text-2xl font-semibold">Car descriptions</h2> |
|
||||
<div className="w-14 border-b border-neutral-200 dark:border-neutral-700"></div> |
|
||||
<div className="text-neutral-6000 dark:text-neutral-300"> |
|
||||
<p> |
|
||||
Until the all-new TUCSON hits the dealer showrooms you can check it |
|
||||
out in our Showroom Walkaround video. Watch the video and join our |
|
||||
product specialist as he gives you an up-close look of our latest |
|
||||
SUV |
|
||||
<br /> |
|
||||
<br /> |
|
||||
Questions are at the heart of making things great. Watch our |
|
||||
celebrity-filled TV ad and you’ll see that when we say “everything,” |
|
||||
we mean everything. |
|
||||
</p> |
|
||||
</div> |
|
||||
</div> |
|
||||
); |
|
||||
}; |
|
||||
|
|
||||
const renderSection3 = () => { |
|
||||
return ( |
|
||||
<div className="listingSection__wrap"> |
|
||||
<div> |
|
||||
<h2 className="text-2xl font-semibold">Include </h2> |
|
||||
<span className="block mt-2 text-neutral-500 dark:text-neutral-400"> |
|
||||
Included in the price |
|
||||
</span> |
|
||||
</div> |
|
||||
<div className="w-14 border-b border-neutral-200 dark:border-neutral-700"></div> |
|
||||
{/* 6 */} |
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6 text-sm text-neutral-700 dark:text-neutral-300 "> |
|
||||
{includes_demo |
|
||||
.filter((_, i) => i < 12) |
|
||||
.map((item) => ( |
|
||||
<div key={item.name} className="flex items-center space-x-3"> |
|
||||
<i className="las la-check-circle text-2xl"></i> |
|
||||
<span>{item.name}</span> |
|
||||
</div> |
|
||||
))} |
|
||||
</div> |
|
||||
</div> |
|
||||
); |
|
||||
}; |
|
||||
|
|
||||
const renderSection5 = () => { |
|
||||
return ( |
|
||||
<div className="listingSection__wrap"> |
|
||||
{/* HEADING */} |
|
||||
<h2 className="text-2xl font-semibold">Car Owner</h2> |
|
||||
<div className="w-14 border-b border-neutral-200 dark:border-neutral-700"></div> |
|
||||
|
|
||||
{/* host */} |
|
||||
<div className="flex items-center space-x-4"> |
|
||||
<Avatar |
|
||||
hasChecked |
|
||||
hasCheckedClass="w-4 h-4 -top-0.5 right-0.5" |
|
||||
sizeClass="h-14 w-14" |
|
||||
radius="rounded-full" |
|
||||
/> |
|
||||
<div> |
|
||||
<a className="block text-xl font-medium" href="##"> |
|
||||
Kevin Francis |
|
||||
</a> |
|
||||
<div className="mt-1.5 flex items-center text-sm text-neutral-500 dark:text-neutral-400"> |
|
||||
<StartRating /> |
|
||||
<span className="mx-2">·</span> |
|
||||
<span> 12 places</span> |
|
||||
</div> |
|
||||
</div> |
|
||||
</div> |
|
||||
|
|
||||
{/* desc */} |
|
||||
<span className="block text-neutral-6000 dark:text-neutral-300"> |
|
||||
Providing lake views, The Symphony 9 Tam Coc in Ninh Binh provides |
|
||||
accommodation, an outdoor swimming pool, a bar, a shared lounge, a |
|
||||
garden and barbecue facilities... |
|
||||
</span> |
|
||||
|
|
||||
{/* info */} |
|
||||
<div className="block text-neutral-500 dark:text-neutral-400 space-y-2.5"> |
|
||||
<div className="flex items-center space-x-3"> |
|
||||
<svg |
|
||||
xmlns="http://www.w3.org/2000/svg" |
|
||||
className="h-6 w-6" |
|
||||
fill="none" |
|
||||
viewBox="0 0 24 24" |
|
||||
stroke="currentColor" |
|
||||
> |
|
||||
<path |
|
||||
strokeLinecap="round" |
|
||||
strokeLinejoin="round" |
|
||||
strokeWidth={1.5} |
|
||||
d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z" |
|
||||
/> |
|
||||
</svg> |
|
||||
<span>Joined in March 2016</span> |
|
||||
</div> |
|
||||
<div className="flex items-center space-x-3"> |
|
||||
<svg |
|
||||
xmlns="http://www.w3.org/2000/svg" |
|
||||
className="h-6 w-6" |
|
||||
fill="none" |
|
||||
viewBox="0 0 24 24" |
|
||||
stroke="currentColor" |
|
||||
> |
|
||||
<path |
|
||||
strokeLinecap="round" |
|
||||
strokeLinejoin="round" |
|
||||
strokeWidth={1.5} |
|
||||
d="M7 8h10M7 12h4m1 8l-4-4H5a2 2 0 01-2-2V6a2 2 0 012-2h14a2 2 0 012 2v8a2 2 0 01-2 2h-3l-4 4z" |
|
||||
/> |
|
||||
</svg> |
|
||||
<span>Response rate - 100%</span> |
|
||||
</div> |
|
||||
<div className="flex items-center space-x-3"> |
|
||||
<svg |
|
||||
xmlns="http://www.w3.org/2000/svg" |
|
||||
className="h-6 w-6" |
|
||||
fill="none" |
|
||||
viewBox="0 0 24 24" |
|
||||
stroke="currentColor" |
|
||||
> |
|
||||
<path |
|
||||
strokeLinecap="round" |
|
||||
strokeLinejoin="round" |
|
||||
strokeWidth={1.5} |
|
||||
d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z" |
|
||||
/> |
|
||||
</svg> |
|
||||
|
|
||||
<span>Fast response - within a few hours</span> |
|
||||
</div> |
|
||||
</div> |
|
||||
|
|
||||
{/* == */} |
|
||||
<div className="w-14 border-b border-neutral-200 dark:border-neutral-700"></div> |
|
||||
<div> |
|
||||
<ButtonSecondary href="/author">See host profile</ButtonSecondary> |
|
||||
</div> |
|
||||
</div> |
|
||||
); |
|
||||
}; |
|
||||
|
|
||||
const renderSection6 = () => { |
|
||||
return ( |
|
||||
<div className="listingSection__wrap"> |
|
||||
{/* HEADING */} |
|
||||
<h2 className="text-2xl font-semibold">Reviews (23 reviews)</h2> |
|
||||
<div className="w-14 border-b border-neutral-200 dark:border-neutral-700"></div> |
|
||||
|
|
||||
{/* Content */} |
|
||||
<div className="space-y-5"> |
|
||||
<FiveStartIconForRate iconClass="w-6 h-6" className="space-x-0.5" /> |
|
||||
<div className="relative"> |
|
||||
<Input |
|
||||
fontClass="" |
|
||||
sizeClass="h-16 px-4 py-3" |
|
||||
rounded="rounded-3xl" |
|
||||
placeholder="Share your thoughts ..." |
|
||||
/> |
|
||||
<ButtonCircle |
|
||||
className="absolute right-2 top-1/2 transform -translate-y-1/2" |
|
||||
size=" w-12 h-12 " |
|
||||
> |
|
||||
<ArrowRightIcon className="w-5 h-5" /> |
|
||||
</ButtonCircle> |
|
||||
</div> |
|
||||
</div> |
|
||||
|
|
||||
{/* comment */} |
|
||||
<div className="divide-y divide-neutral-100 dark:divide-neutral-800"> |
|
||||
<CommentListing className="py-8" /> |
|
||||
<CommentListing className="py-8" /> |
|
||||
<CommentListing className="py-8" /> |
|
||||
<CommentListing className="py-8" /> |
|
||||
<div className="pt-8"> |
|
||||
<ButtonSecondary>View more 20 reviews</ButtonSecondary> |
|
||||
</div> |
|
||||
</div> |
|
||||
</div> |
|
||||
); |
|
||||
}; |
|
||||
|
|
||||
const renderSection7 = () => { |
|
||||
return ( |
|
||||
<div className="listingSection__wrap"> |
|
||||
{/* HEADING */} |
|
||||
<div> |
|
||||
<h2 className="text-2xl font-semibold">Location</h2> |
|
||||
<span className="block mt-2 text-neutral-500 dark:text-neutral-400"> |
|
||||
San Diego, CA, United States of America (SAN-San Diego Intl.) |
|
||||
</span> |
|
||||
</div> |
|
||||
<div className="w-14 border-b border-neutral-200 dark:border-neutral-700" /> |
|
||||
|
|
||||
{/* MAP */} |
|
||||
<div className="aspect-w-5 aspect-h-5 sm:aspect-h-3 ring-1 ring-black/10 rounded-xl z-0"> |
|
||||
<div className="rounded-xl overflow-hidden z-0"> |
|
||||
<iframe |
|
||||
width="100%" |
|
||||
height="100%" |
|
||||
loading="lazy" |
|
||||
allowFullScreen |
|
||||
referrerPolicy="no-referrer-when-downgrade" |
|
||||
src="https://www.google.com/maps/embed/v1/place?key=AIzaSyAGVJfZMAKYfZ71nzL_v5i3LjTTWnCYwTY&q=Eiffel+Tower,Paris+France" |
|
||||
></iframe> |
|
||||
</div> |
|
||||
</div> |
|
||||
</div> |
|
||||
); |
|
||||
}; |
|
||||
|
|
||||
const renderSection8 = () => { |
|
||||
return ( |
|
||||
<div className="listingSection__wrap"> |
|
||||
{/* HEADING */} |
|
||||
<h2 className="text-2xl font-semibold">Things to know</h2> |
|
||||
<div className="w-14 border-b border-neutral-200 dark:border-neutral-700" /> |
|
||||
|
|
||||
{/* CONTENT */} |
|
||||
<div> |
|
||||
<h4 className="text-lg font-semibold">Cancellation policy</h4> |
|
||||
<span className="block mt-3 text-neutral-500 dark:text-neutral-400"> |
|
||||
Lock in this fantastic price today, cancel free of charge anytime. |
|
||||
Reserve now and pay at pick-up. |
|
||||
</span> |
|
||||
</div> |
|
||||
<div className="w-14 border-b border-neutral-200 dark:border-neutral-700" /> |
|
||||
|
|
||||
{/* CONTENT */} |
|
||||
<div> |
|
||||
<h4 className="text-lg font-semibold">Special Note</h4> |
|
||||
<span className="block mt-3 text-neutral-500 dark:text-neutral-400"> |
|
||||
We asked ourselves, “How can we make the dash not only look better, |
|
||||
but also give the driver a better look outside?” The unexpected |
|
||||
answer is having no hood above the available 10.25-inch digital |
|
||||
instrument cluster... |
|
||||
</span> |
|
||||
</div> |
|
||||
</div> |
|
||||
); |
|
||||
}; |
|
||||
|
|
||||
const renderSidebarPrice = () => { |
|
||||
return ( |
|
||||
<div className="listingSectionSidebar__wrap shadow-xl"> |
|
||||
{/* PRICE */} |
|
||||
<div className="flex justify-between"> |
|
||||
<span className="text-3xl font-semibold"> |
|
||||
$19 |
|
||||
<span className="ml-1 text-base font-normal text-neutral-500 dark:text-neutral-400"> |
|
||||
/day |
|
||||
</span> |
|
||||
</span> |
|
||||
<StartRating /> |
|
||||
</div> |
|
||||
|
|
||||
{/* FORM */} |
|
||||
<form className="border border-neutral-200 dark:border-neutral-700 rounded-2xl"> |
|
||||
<RentalCarDatesRangeInput /> |
|
||||
</form> |
|
||||
|
|
||||
{/* SUM */} |
|
||||
<div className="flex flex-col space-y-4 "> |
|
||||
<div className="flex justify-between text-neutral-6000 dark:text-neutral-300"> |
|
||||
<span>$19 x 3 day</span> |
|
||||
<span>$57</span> |
|
||||
</div> |
|
||||
|
|
||||
<div className="border-b border-neutral-200 dark:border-neutral-700"></div> |
|
||||
<div className="flex justify-between font-semibold"> |
|
||||
<span>Total</span> |
|
||||
<span>$199</span> |
|
||||
</div> |
|
||||
</div> |
|
||||
|
|
||||
{/* SUBMIT */} |
|
||||
<ButtonPrimary href={"/checkout"}>Reserve</ButtonPrimary> |
|
||||
</div> |
|
||||
); |
|
||||
}; |
|
||||
|
|
||||
const renderSidebarDetail = () => { |
|
||||
return ( |
|
||||
<div className="listingSection__wrap lg:shadow-xl"> |
|
||||
<span className="text-2xl font-semibold block"> |
|
||||
Pick up and drop off |
|
||||
</span> |
|
||||
<div className="mt-8 flex"> |
|
||||
<div className="flex-shrink-0 flex flex-col items-center py-2"> |
|
||||
<span className="block w-6 h-6 rounded-full border border-neutral-400"></span> |
|
||||
<span className="block flex-grow border-l border-neutral-400 border-dashed my-1"></span> |
|
||||
<span className="block w-6 h-6 rounded-full border border-neutral-400"></span> |
|
||||
</div> |
|
||||
<div className="ml-4 space-y-14 text-sm"> |
|
||||
<div className="flex flex-col space-y-2"> |
|
||||
<span className=" text-neutral-500 dark:text-neutral-400"> |
|
||||
Monday, August 12 · 10:00 |
|
||||
</span> |
|
||||
<span className=" font-semibold"> |
|
||||
Saint Petersburg City Center |
|
||||
</span> |
|
||||
</div> |
|
||||
<div className="flex flex-col space-y-2"> |
|
||||
<span className=" text-neutral-500 dark:text-neutral-400"> |
|
||||
Monday, August 16 · 10:00 |
|
||||
</span> |
|
||||
<span className=" font-semibold"> |
|
||||
Saint Petersburg City Center |
|
||||
</span> |
|
||||
</div> |
|
||||
</div> |
|
||||
</div> |
|
||||
</div> |
|
||||
); |
|
||||
}; |
|
||||
|
|
||||
return ( |
|
||||
<div className={` nc-ListingCarDetailPage `}> |
|
||||
{/* SINGLE HEADER */} |
|
||||
<header className="rounded-md sm:rounded-xl"> |
|
||||
<div className="relative grid grid-cols-4 gap-1 sm:gap-2"> |
|
||||
<div |
|
||||
className="col-span-2 row-span-2 relative rounded-md sm:rounded-xl overflow-hidden cursor-pointer" |
|
||||
onClick={handleOpenModalImageGallery} |
|
||||
> |
|
||||
<Image |
|
||||
fill |
|
||||
src={PHOTOS[0]} |
|
||||
alt="photo 0" |
|
||||
className="object-cover rounded-md sm:rounded-xl" |
|
||||
sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 50vw" |
|
||||
/> |
|
||||
<div className="absolute inset-0 bg-neutral-900 bg-opacity-20 opacity-0 hover:opacity-100 transition-opacity"></div> |
|
||||
</div> |
|
||||
|
|
||||
{/* */} |
|
||||
<div |
|
||||
className="col-span-1 row-span-2 relative rounded-md sm:rounded-xl overflow-hidden cursor-pointer" |
|
||||
onClick={handleOpenModalImageGallery} |
|
||||
> |
|
||||
<Image |
|
||||
fill |
|
||||
className="object-cover rounded-md sm:rounded-xl" |
|
||||
src={PHOTOS[1]} |
|
||||
alt="photo 1" |
|
||||
sizes="400px" |
|
||||
/> |
|
||||
<div className="absolute inset-0 bg-neutral-900 bg-opacity-20 opacity-0 hover:opacity-100 transition-opacity"></div> |
|
||||
</div> |
|
||||
|
|
||||
{/* */} |
|
||||
{PHOTOS.filter((_, i) => i >= 2 && i < 4).map((item, index) => ( |
|
||||
<div |
|
||||
key={index} |
|
||||
className={`relative rounded-md sm:rounded-xl overflow-hidden ${ |
|
||||
index >= 2 ? "block" : "" |
|
||||
}`}
|
|
||||
> |
|
||||
<div className="aspect-w-4 aspect-h-3"> |
|
||||
<Image |
|
||||
fill |
|
||||
className="object-cover w-full h-full rounded-md sm:rounded-xl " |
|
||||
src={item || ""} |
|
||||
alt="photos" |
|
||||
sizes="400px" |
|
||||
/> |
|
||||
</div> |
|
||||
|
|
||||
{/* OVERLAY */} |
|
||||
<div |
|
||||
className="absolute inset-0 bg-neutral-900 bg-opacity-20 opacity-0 hover:opacity-100 transition-opacity cursor-pointer" |
|
||||
onClick={handleOpenModalImageGallery} |
|
||||
/> |
|
||||
</div> |
|
||||
))} |
|
||||
|
|
||||
<div |
|
||||
className="absolute hidden md:flex md:items-center md:justify-center left-3 bottom-3 px-4 py-2 rounded-xl bg-neutral-100 text-neutral-500 cursor-pointer hover:bg-neutral-200 z-10" |
|
||||
onClick={handleOpenModalImageGallery} |
|
||||
> |
|
||||
<Squares2X2Icon className="h-5 w-5" /> |
|
||||
|
|
||||
<span className="ml-2 text-neutral-800 text-sm font-medium"> |
|
||||
Show all photos |
|
||||
</span> |
|
||||
</div> |
|
||||
</div> |
|
||||
</header> |
|
||||
|
|
||||
{/* MAIn */} |
|
||||
<main className=" relative z-10 mt-11 flex flex-col lg:flex-row "> |
|
||||
{/* CONTENT */} |
|
||||
<div className="w-full lg:w-3/5 xl:w-2/3 space-y-8 lg:pr-10 lg:space-y-10"> |
|
||||
{renderSection1()} |
|
||||
<div className="block lg:hidden">{renderSidebarDetail()}</div> |
|
||||
{renderSectionTienIch()} |
|
||||
{renderSection2()} |
|
||||
{renderSection3()} |
|
||||
<SectionDateRange /> |
|
||||
|
|
||||
{renderSection5()} |
|
||||
{renderSection6()} |
|
||||
{renderSection7()} |
|
||||
{renderSection8()} |
|
||||
</div> |
|
||||
|
|
||||
{/* SIDEBAR */} |
|
||||
<div className="block flex-grow mt-14 lg:mt-0"> |
|
||||
{renderSidebarDetail()} |
|
||||
<div className="hidden lg:block mt-10 sticky top-28"> |
|
||||
{renderSidebarPrice()} |
|
||||
</div> |
|
||||
</div> |
|
||||
</main> |
|
||||
</div> |
|
||||
); |
|
||||
}; |
|
||||
|
|
||||
export default ListingCarDetailPage; |
|
@ -1,122 +0,0 @@ |
|||||
"use client"; |
|
||||
|
|
||||
import React, { Fragment, FC, useState } from "react"; |
|
||||
import { Popover, Transition } from "@headlessui/react"; |
|
||||
import NcInputNumber from "@/components/NcInputNumber"; |
|
||||
import { UserPlusIcon } from "@heroicons/react/24/outline"; |
|
||||
import ClearDataButton from "@/app/(client-components)/(HeroSearchForm)/ClearDataButton"; |
|
||||
import { GuestsObject } from "@/app/(client-components)/type"; |
|
||||
|
|
||||
export interface GuestsInputProps { |
|
||||
className?: string; |
|
||||
} |
|
||||
|
|
||||
const GuestsInput: FC<GuestsInputProps> = ({ className = "flex-1" }) => { |
|
||||
const [guestAdultsInputValue, setGuestAdultsInputValue] = useState(2); |
|
||||
const [guestChildrenInputValue, setGuestChildrenInputValue] = useState(1); |
|
||||
const [guestInfantsInputValue, setGuestInfantsInputValue] = useState(1); |
|
||||
|
|
||||
const handleChangeData = (value: number, type: keyof GuestsObject) => { |
|
||||
let newValue = { |
|
||||
guestAdults: guestAdultsInputValue, |
|
||||
guestChildren: guestChildrenInputValue, |
|
||||
guestInfants: guestInfantsInputValue, |
|
||||
}; |
|
||||
if (type === "guestAdults") { |
|
||||
setGuestAdultsInputValue(value); |
|
||||
newValue.guestAdults = value; |
|
||||
} |
|
||||
if (type === "guestChildren") { |
|
||||
setGuestChildrenInputValue(value); |
|
||||
newValue.guestChildren = value; |
|
||||
} |
|
||||
if (type === "guestInfants") { |
|
||||
setGuestInfantsInputValue(value); |
|
||||
newValue.guestInfants = value; |
|
||||
} |
|
||||
}; |
|
||||
|
|
||||
const totalGuests = |
|
||||
guestChildrenInputValue + guestAdultsInputValue + guestInfantsInputValue; |
|
||||
|
|
||||
return ( |
|
||||
<Popover className={`flex relative ${className}`}> |
|
||||
{({ open }) => ( |
|
||||
<> |
|
||||
<div |
|
||||
className={`flex-1 flex items-center focus:outline-none rounded-b-3xl ${ |
|
||||
open ? "shadow-lg" : "" |
|
||||
}`}
|
|
||||
> |
|
||||
<Popover.Button |
|
||||
className={`relative z-10 flex-1 flex text-left items-center p-3 space-x-3 focus:outline-none`} |
|
||||
> |
|
||||
<div className="text-neutral-300 dark:text-neutral-400"> |
|
||||
<UserPlusIcon className="w-5 h-5 lg:w-7 lg:h-7" /> |
|
||||
</div> |
|
||||
<div className="flex-grow"> |
|
||||
<span className="block xl:text-lg font-semibold"> |
|
||||
{totalGuests || ""} Guests |
|
||||
</span> |
|
||||
<span className="block mt-1 text-sm text-neutral-400 leading-none font-light"> |
|
||||
{totalGuests ? "Guests" : "Add guests"} |
|
||||
</span> |
|
||||
</div> |
|
||||
|
|
||||
{!!totalGuests && open && ( |
|
||||
<ClearDataButton |
|
||||
onClick={() => { |
|
||||
setGuestAdultsInputValue(0); |
|
||||
setGuestChildrenInputValue(0); |
|
||||
setGuestInfantsInputValue(0); |
|
||||
}} |
|
||||
/> |
|
||||
)} |
|
||||
</Popover.Button> |
|
||||
</div> |
|
||||
|
|
||||
<Transition |
|
||||
as={Fragment} |
|
||||
enter="transition ease-out duration-200" |
|
||||
enterFrom="opacity-0 translate-y-1" |
|
||||
enterTo="opacity-100 translate-y-0" |
|
||||
leave="transition ease-in duration-150" |
|
||||
leaveFrom="opacity-100 translate-y-0" |
|
||||
leaveTo="opacity-0 translate-y-1" |
|
||||
> |
|
||||
<Popover.Panel className="absolute right-0 z-10 w-full sm:min-w-[340px] max-w-sm bg-white dark:bg-neutral-800 top-full mt-3 py-5 sm:py-6 px-4 sm:px-8 rounded-3xl shadow-xl ring-1 ring-black ring-opacity-5 "> |
|
||||
<NcInputNumber |
|
||||
className="w-full" |
|
||||
defaultValue={guestAdultsInputValue} |
|
||||
onChange={(value) => handleChangeData(value, "guestAdults")} |
|
||||
max={10} |
|
||||
min={1} |
|
||||
label="Adults" |
|
||||
desc="Ages 13 or above" |
|
||||
/> |
|
||||
<NcInputNumber |
|
||||
className="w-full mt-6" |
|
||||
defaultValue={guestChildrenInputValue} |
|
||||
onChange={(value) => handleChangeData(value, "guestChildren")} |
|
||||
max={4} |
|
||||
label="Children" |
|
||||
desc="Ages 2–12" |
|
||||
/> |
|
||||
|
|
||||
<NcInputNumber |
|
||||
className="w-full mt-6" |
|
||||
defaultValue={guestInfantsInputValue} |
|
||||
onChange={(value) => handleChangeData(value, "guestInfants")} |
|
||||
max={4} |
|
||||
label="Infants" |
|
||||
desc="Ages 0–2" |
|
||||
/> |
|
||||
</Popover.Panel> |
|
||||
</Transition> |
|
||||
</> |
|
||||
)} |
|
||||
</Popover> |
|
||||
); |
|
||||
}; |
|
||||
|
|
||||
export default GuestsInput; |
|
@ -1,109 +0,0 @@ |
|||||
"use client"; |
|
||||
|
|
||||
import React, { Fragment, useState, FC } from "react"; |
|
||||
import { Popover, Transition } from "@headlessui/react"; |
|
||||
import { CalendarIcon } from "@heroicons/react/24/outline"; |
|
||||
import DatePickerCustomHeaderTwoMonth from "@/components/DatePickerCustomHeaderTwoMonth"; |
|
||||
import DatePickerCustomDay from "@/components/DatePickerCustomDay"; |
|
||||
import DatePicker from "react-datepicker"; |
|
||||
import ClearDataButton from "@/app/(client-components)/(HeroSearchForm)/ClearDataButton"; |
|
||||
|
|
||||
export interface StayDatesRangeInputProps { |
|
||||
className?: string; |
|
||||
} |
|
||||
|
|
||||
const StayDatesRangeInput: FC<StayDatesRangeInputProps> = ({ |
|
||||
className = "flex-1", |
|
||||
}) => { |
|
||||
const [startDate, setStartDate] = useState<Date | null>( |
|
||||
new Date("2023/02/06") |
|
||||
); |
|
||||
const [endDate, setEndDate] = useState<Date | null>(new Date("2023/02/23")); |
|
||||
//
|
|
||||
|
|
||||
const onChangeDate = (dates: [Date | null, Date | null]) => { |
|
||||
const [start, end] = dates; |
|
||||
setStartDate(start); |
|
||||
setEndDate(end); |
|
||||
}; |
|
||||
|
|
||||
const renderInput = () => { |
|
||||
return ( |
|
||||
<> |
|
||||
<div className="text-neutral-300 dark:text-neutral-400"> |
|
||||
<CalendarIcon className="w-5 h-5 lg:w-7 lg:h-7" /> |
|
||||
</div> |
|
||||
<div className="flex-grow text-left"> |
|
||||
<span className="block xl:text-lg font-semibold"> |
|
||||
{startDate?.toLocaleDateString("en-US", { |
|
||||
month: "short", |
|
||||
day: "2-digit", |
|
||||
}) || "Add dates"} |
|
||||
{endDate |
|
||||
? " - " + |
|
||||
endDate?.toLocaleDateString("en-US", { |
|
||||
month: "short", |
|
||||
day: "2-digit", |
|
||||
}) |
|
||||
: ""} |
|
||||
</span> |
|
||||
<span className="block mt-1 text-sm text-neutral-400 leading-none font-light"> |
|
||||
{"Check in - Check out"} |
|
||||
</span> |
|
||||
</div> |
|
||||
</> |
|
||||
); |
|
||||
}; |
|
||||
|
|
||||
return ( |
|
||||
<Popover className={`StayDatesRangeInput z-10 relative flex ${className}`}> |
|
||||
{({ open }) => ( |
|
||||
<> |
|
||||
<Popover.Button |
|
||||
className={`flex-1 flex relative p-3 items-center space-x-3 focus:outline-none ${ |
|
||||
open ? "shadow-lg" : "" |
|
||||
}`}
|
|
||||
> |
|
||||
{renderInput()} |
|
||||
{startDate && open && ( |
|
||||
<ClearDataButton onClick={() => onChangeDate([null, null])} /> |
|
||||
)} |
|
||||
</Popover.Button> |
|
||||
|
|
||||
<Transition |
|
||||
as={Fragment} |
|
||||
enter="transition ease-out duration-200" |
|
||||
enterFrom="opacity-0 translate-y-1" |
|
||||
enterTo="opacity-100 translate-y-0" |
|
||||
leave="transition ease-in duration-150" |
|
||||
leaveFrom="opacity-100 translate-y-0" |
|
||||
leaveTo="opacity-0 translate-y-1" |
|
||||
> |
|
||||
<Popover.Panel className="absolute left-auto xl:-right-10 right-0 z-10 mt-3 top-full w-screen max-w-sm px-4 sm:px-0 lg:max-w-3xl"> |
|
||||
<div className="overflow-hidden rounded-3xl shadow-lg ring-1 ring-black ring-opacity-5 bg-white dark:bg-neutral-800 p-8"> |
|
||||
<DatePicker |
|
||||
selected={startDate} |
|
||||
onChange={onChangeDate} |
|
||||
startDate={startDate} |
|
||||
endDate={endDate} |
|
||||
selectsRange |
|
||||
monthsShown={2} |
|
||||
showPopperArrow={false} |
|
||||
inline |
|
||||
renderCustomHeader={(p) => ( |
|
||||
<DatePickerCustomHeaderTwoMonth {...p} /> |
|
||||
)} |
|
||||
renderDayContents={(day, date) => ( |
|
||||
<DatePickerCustomDay dayOfMonth={day} date={date} /> |
|
||||
)} |
|
||||
/> |
|
||||
</div> |
|
||||
</Popover.Panel> |
|
||||
</Transition> |
|
||||
</> |
|
||||
)} |
|
||||
</Popover> |
|
||||
); |
|
||||
}; |
|
||||
|
|
||||
export default StayDatesRangeInput; |
|
@ -1,39 +0,0 @@ |
|||||
import { ListingGalleryImage } from "@/components/listing-image-gallery/utils/types"; |
|
||||
|
|
||||
export const PHOTOS: string[] = [ |
|
||||
"https://images.pexels.com/photos/3225531/pexels-photo-3225531.jpeg?auto=compress&cs=tinysrgb&dpr=3&h=750&w=1260", |
|
||||
"https://images.pexels.com/photos/1154638/pexels-photo-1154638.jpeg?auto=compress&cs=tinysrgb&dpr=3&h=750&w=1260", |
|
||||
"https://images.pexels.com/photos/3851949/pexels-photo-3851949.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=650&w=940", |
|
||||
"https://images.pexels.com/photos/3019019/pexels-photo-3019019.jpeg?auto=compress&cs=tinysrgb&dpr=3&h=750&w=1260", |
|
||||
"https://images.pexels.com/photos/6438752/pexels-photo-6438752.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=750&w=1260", |
|
||||
"https://images.pexels.com/photos/1320686/pexels-photo-1320686.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=750&w=1260", |
|
||||
"https://images.pexels.com/photos/261394/pexels-photo-261394.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=750&w=1260", |
|
||||
"https://images.pexels.com/photos/2861361/pexels-photo-2861361.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=750&w=1260", |
|
||||
"https://images.pexels.com/photos/2677398/pexels-photo-2677398.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=750&w=1260", |
|
||||
"https://images.pexels.com/photos/4348078/pexels-photo-4348078.jpeg?auto=compress&cs=tinysrgb&w=1600", |
|
||||
"https://images.pexels.com/photos/3825527/pexels-photo-3825527.jpeg?auto=compress&cs=tinysrgb&w=1600", |
|
||||
"https://images.pexels.com/photos/4706134/pexels-photo-4706134.jpeg?auto=compress&cs=tinysrgb&w=1600", |
|
||||
"https://images.pexels.com/photos/3825578/pexels-photo-3825578.jpeg?auto=compress&cs=tinysrgb&w=1600", |
|
||||
"https://images.pexels.com/photos/123335/pexels-photo-123335.jpeg?auto=compress&cs=tinysrgb&w=1600", |
|
||||
"https://images.pexels.com/photos/3761124/pexels-photo-3761124.jpeg?auto=compress&cs=tinysrgb&w=1600", |
|
||||
"https://images.pexels.com/photos/8926846/pexels-photo-8926846.jpeg?auto=compress&cs=tinysrgb&w=1600", |
|
||||
"https://images.pexels.com/photos/4706139/pexels-photo-4706139.jpeg?auto=compress&cs=tinysrgb&w=1600", |
|
||||
"https://images.pexels.com/photos/7003624/pexels-photo-7003624.jpeg?auto=compress&cs=tinysrgb&w=1600", |
|
||||
]; |
|
||||
|
|
||||
export const includes_demo = [ |
|
||||
{ name: "Set Menu Lunch on boat" }, |
|
||||
{ name: "Express Bus From Hanoi To Halong and Return" }, |
|
||||
{ name: "Mineral Water On Express Bus" }, |
|
||||
{ name: "Kayak or Bamboo Boat. Life Jacket." }, |
|
||||
{ name: "Halong Bay Entrance Ticket" }, |
|
||||
]; |
|
||||
|
|
||||
export const imageGallery: ListingGalleryImage[] = [...PHOTOS].map( |
|
||||
(item, index): ListingGalleryImage => { |
|
||||
return { |
|
||||
id: index, |
|
||||
url: item, |
|
||||
}; |
|
||||
} |
|
||||
); |
|
@ -1,505 +0,0 @@ |
|||||
"use client"; |
|
||||
|
|
||||
import React, { FC, useState } from "react"; |
|
||||
import { ArrowRightIcon, Squares2X2Icon } from "@heroicons/react/24/outline"; |
|
||||
import CommentListing from "@/components/CommentListing"; |
|
||||
import FiveStartIconForRate from "@/components/FiveStartIconForRate"; |
|
||||
import Avatar from "@/shared/Avatar"; |
|
||||
import Badge from "@/shared/Badge"; |
|
||||
import ButtonCircle from "@/shared/ButtonCircle"; |
|
||||
import ButtonPrimary from "@/shared/ButtonPrimary"; |
|
||||
import ButtonSecondary from "@/shared/ButtonSecondary"; |
|
||||
import Input from "@/shared/Input"; |
|
||||
import { usePathname, useRouter } from "next/navigation"; |
|
||||
import LikeSaveBtns from "@/components/LikeSaveBtns"; |
|
||||
import StartRating from "@/components/StartRating"; |
|
||||
import { includes_demo, PHOTOS } from "./constant"; |
|
||||
import Image from "next/image"; |
|
||||
import StayDatesRangeInput from "./StayDatesRangeInput"; |
|
||||
import GuestsInput from "./GuestsInput"; |
|
||||
import SectionDateRange from "../SectionDateRange"; |
|
||||
import { Route } from "next"; |
|
||||
|
|
||||
export interface ListingExperiencesDetailPageProps {} |
|
||||
|
|
||||
const ListingExperiencesDetailPage: FC< |
|
||||
ListingExperiencesDetailPageProps |
|
||||
> = ({}) => { |
|
||||
const thisPathname = usePathname(); |
|
||||
const router = useRouter(); |
|
||||
|
|
||||
const handleOpenModalImageGallery = () => { |
|
||||
router.push(`${thisPathname}/?modal=PHOTO_TOUR_SCROLLABLE` as Route); |
|
||||
}; |
|
||||
|
|
||||
const renderSection1 = () => { |
|
||||
return ( |
|
||||
<div className="listingSection__wrap !space-y-6"> |
|
||||
{/* 1 */} |
|
||||
<div className="flex justify-between items-center"> |
|
||||
<Badge color="pink" name="Specific Tour" /> |
|
||||
<LikeSaveBtns /> |
|
||||
</div> |
|
||||
|
|
||||
{/* 2 */} |
|
||||
<h2 className="text-2xl sm:text-3xl lg:text-4xl font-semibold"> |
|
||||
Trang An Boat Tour & Mua Cave |
|
||||
</h2> |
|
||||
|
|
||||
{/* 3 */} |
|
||||
<div className="flex items-center space-x-4"> |
|
||||
<StartRating /> |
|
||||
<span>·</span> |
|
||||
<span> |
|
||||
<i className="las la-map-marker-alt"></i> |
|
||||
<span className="ml-1"> Tokyo, Jappan</span> |
|
||||
</span> |
|
||||
</div> |
|
||||
|
|
||||
{/* 4 */} |
|
||||
<div className="flex items-center"> |
|
||||
<Avatar hasChecked sizeClass="h-10 w-10" radius="rounded-full" /> |
|
||||
<span className="ml-2.5 text-neutral-500 dark:text-neutral-400"> |
|
||||
Hosted by{" "} |
|
||||
<span className="text-neutral-900 dark:text-neutral-200 font-medium"> |
|
||||
Kevin Francis |
|
||||
</span> |
|
||||
</span> |
|
||||
</div> |
|
||||
|
|
||||
{/* 5 */} |
|
||||
<div className="w-full border-b border-neutral-100 dark:border-neutral-700" /> |
|
||||
|
|
||||
{/* 6 */} |
|
||||
<div className="flex items-center justify-between xl:justify-start space-x-8 xl:space-x-12 text-sm text-neutral-700 dark:text-neutral-300"> |
|
||||
<div className="flex flex-col sm:flex-row items-center space-y-3 sm:space-y-0 text-center sm:text-left sm:space-x-3 "> |
|
||||
<i className="las la-clock text-2xl"></i> |
|
||||
<span className="">3.5 hours</span> |
|
||||
</div> |
|
||||
<div className="flex flex-col sm:flex-row items-center space-y-3 sm:space-y-0 text-center sm:text-left sm:space-x-3 "> |
|
||||
<i className="las la-user-friends text-2xl"></i> |
|
||||
<span className="">Up to 10 people</span> |
|
||||
</div> |
|
||||
<div className="flex flex-col sm:flex-row items-center space-y-3 sm:space-y-0 text-center sm:text-left sm:space-x-3 "> |
|
||||
<i className="las la-language text-2xl"></i> |
|
||||
<span className="">English, VietNames</span> |
|
||||
</div> |
|
||||
</div> |
|
||||
</div> |
|
||||
); |
|
||||
}; |
|
||||
|
|
||||
const renderSection2 = () => { |
|
||||
return ( |
|
||||
<div className="listingSection__wrap"> |
|
||||
<h2 className="text-2xl font-semibold">Experiences descriptions</h2> |
|
||||
<div className="w-14 border-b border-neutral-200 dark:border-neutral-700"></div> |
|
||||
<div className="text-neutral-6000 dark:text-neutral-300"> |
|
||||
<p> |
|
||||
TRANG AN BOAT TOUR & MUA CAVE CLIMBING TOUR FROM HANOI |
|
||||
<br /> |
|
||||
<br /> |
|
||||
07:30 – 08:00 – Our guide will meet you at your hotel/stay and start |
|
||||
a 120km comfortable Limousine bus journey through the verdant |
|
||||
landscape. Stopover for a rest on the way. |
|
||||
<br /> |
|
||||
<br /> |
|
||||
BAI DINH PAGODA EXPLORER. |
|
||||
<br /> |
|
||||
<br /> |
|
||||
10:30 – Arrive Bai Dinh pagoda complex, get on electric cars to |
|
||||
visit massive architecture. |
|
||||
<br /> |
|
||||
<br /> |
|
||||
12:15 – Enjoy the buffet lunch at our restaurant, a great place to |
|
||||
savor the flavours of Vietnamese food. |
|
||||
<br /> |
|
||||
<br /> |
|
||||
TRANG AN TOUR ON BOAT. |
|
||||
<br /> |
|
||||
<br /> |
|
||||
13:30 – Visit Trang An Grottoes, get on a rowing boat traveling |
|
||||
along the river with scenic mountain and green fields landscape. |
|
||||
<br /> |
|
||||
<br /> |
|
||||
MUA CAVE HIKING. TAKE PICTURE |
|
||||
<br /> |
|
||||
<br /> |
|
||||
15:45 – Arrive at Mua Cave and start an amazing trek up to the top |
|
||||
of Ngoa Long mountain. |
|
||||
<br /> |
|
||||
<br /> |
|
||||
17:30 – 20:00 – Return to our Limousine bus and then come back to |
|
||||
Hanoi. Drop you off at your hotel/stay. Other things to note |
|
||||
<br /> |
|
||||
<br /> |
|
||||
It is one full day tour. Start from 07.30 AM and finish at 20.00. We |
|
||||
just put one hour and default departure time because we have many |
|
||||
other tours. IF you need any further details |
|
||||
</p> |
|
||||
</div> |
|
||||
</div> |
|
||||
); |
|
||||
}; |
|
||||
|
|
||||
const renderSection3 = () => { |
|
||||
return ( |
|
||||
<div className="listingSection__wrap"> |
|
||||
<div> |
|
||||
<h2 className="text-2xl font-semibold">Include </h2> |
|
||||
<span className="block mt-2 text-neutral-500 dark:text-neutral-400"> |
|
||||
Included in the price |
|
||||
</span> |
|
||||
</div> |
|
||||
<div className="w-14 border-b border-neutral-200 dark:border-neutral-700"></div> |
|
||||
{/* 6 */} |
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6 text-sm text-neutral-700 dark:text-neutral-300 "> |
|
||||
{includes_demo |
|
||||
.filter((_, i) => i < 12) |
|
||||
.map((item) => ( |
|
||||
<div key={item.name} className="flex items-center space-x-3"> |
|
||||
<i className="las la-check-circle text-2xl"></i> |
|
||||
<span>{item.name}</span> |
|
||||
</div> |
|
||||
))} |
|
||||
</div> |
|
||||
</div> |
|
||||
); |
|
||||
}; |
|
||||
|
|
||||
const renderSection5 = () => { |
|
||||
return ( |
|
||||
<div className="listingSection__wrap"> |
|
||||
{/* HEADING */} |
|
||||
<h2 className="text-2xl font-semibold">Host Information</h2> |
|
||||
<div className="w-14 border-b border-neutral-200 dark:border-neutral-700"></div> |
|
||||
|
|
||||
{/* host */} |
|
||||
<div className="flex items-center space-x-4"> |
|
||||
<Avatar |
|
||||
hasChecked |
|
||||
hasCheckedClass="w-4 h-4 -top-0.5 right-0.5" |
|
||||
sizeClass="h-14 w-14" |
|
||||
radius="rounded-full" |
|
||||
/> |
|
||||
<div> |
|
||||
<a className="block text-xl font-medium" href="##"> |
|
||||
Kevin Francis |
|
||||
</a> |
|
||||
<div className="mt-1.5 flex items-center text-sm text-neutral-500 dark:text-neutral-400"> |
|
||||
<StartRating /> |
|
||||
<span className="mx-2">·</span> |
|
||||
<span> 12 places</span> |
|
||||
</div> |
|
||||
</div> |
|
||||
</div> |
|
||||
|
|
||||
{/* desc */} |
|
||||
<span className="block text-neutral-6000 dark:text-neutral-300"> |
|
||||
Providing lake views, The Symphony 9 Tam Coc in Ninh Binh provides |
|
||||
accommodation, an outdoor swimming pool, a bar, a shared lounge, a |
|
||||
garden and barbecue facilities... |
|
||||
</span> |
|
||||
|
|
||||
{/* info */} |
|
||||
<div className="block text-neutral-500 dark:text-neutral-400 space-y-2.5"> |
|
||||
<div className="flex items-center space-x-3"> |
|
||||
<svg |
|
||||
xmlns="http://www.w3.org/2000/svg" |
|
||||
className="h-6 w-6" |
|
||||
fill="none" |
|
||||
viewBox="0 0 24 24" |
|
||||
stroke="currentColor" |
|
||||
> |
|
||||
<path |
|
||||
strokeLinecap="round" |
|
||||
strokeLinejoin="round" |
|
||||
strokeWidth={1.5} |
|
||||
d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z" |
|
||||
/> |
|
||||
</svg> |
|
||||
<span>Joined in March 2016</span> |
|
||||
</div> |
|
||||
<div className="flex items-center space-x-3"> |
|
||||
<svg |
|
||||
xmlns="http://www.w3.org/2000/svg" |
|
||||
className="h-6 w-6" |
|
||||
fill="none" |
|
||||
viewBox="0 0 24 24" |
|
||||
stroke="currentColor" |
|
||||
> |
|
||||
<path |
|
||||
strokeLinecap="round" |
|
||||
strokeLinejoin="round" |
|
||||
strokeWidth={1.5} |
|
||||
d="M7 8h10M7 12h4m1 8l-4-4H5a2 2 0 01-2-2V6a2 2 0 012-2h14a2 2 0 012 2v8a2 2 0 01-2 2h-3l-4 4z" |
|
||||
/> |
|
||||
</svg> |
|
||||
<span>Response rate - 100%</span> |
|
||||
</div> |
|
||||
<div className="flex items-center space-x-3"> |
|
||||
<svg |
|
||||
xmlns="http://www.w3.org/2000/svg" |
|
||||
className="h-6 w-6" |
|
||||
fill="none" |
|
||||
viewBox="0 0 24 24" |
|
||||
stroke="currentColor" |
|
||||
> |
|
||||
<path |
|
||||
strokeLinecap="round" |
|
||||
strokeLinejoin="round" |
|
||||
strokeWidth={1.5} |
|
||||
d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z" |
|
||||
/> |
|
||||
</svg> |
|
||||
|
|
||||
<span>Fast response - within a few hours</span> |
|
||||
</div> |
|
||||
</div> |
|
||||
|
|
||||
{/* == */} |
|
||||
<div className="w-14 border-b border-neutral-200 dark:border-neutral-700"></div> |
|
||||
<div> |
|
||||
<ButtonSecondary href="/author">See host profile</ButtonSecondary> |
|
||||
</div> |
|
||||
</div> |
|
||||
); |
|
||||
}; |
|
||||
|
|
||||
const renderSection6 = () => { |
|
||||
return ( |
|
||||
<div className="listingSection__wrap"> |
|
||||
{/* HEADING */} |
|
||||
<h2 className="text-2xl font-semibold">Reviews (23 reviews)</h2> |
|
||||
<div className="w-14 border-b border-neutral-200 dark:border-neutral-700"></div> |
|
||||
|
|
||||
{/* Content */} |
|
||||
<div className="space-y-5"> |
|
||||
<FiveStartIconForRate iconClass="w-6 h-6" className="space-x-0.5" /> |
|
||||
<div className="relative"> |
|
||||
<Input |
|
||||
fontClass="" |
|
||||
sizeClass="h-16 px-4 py-3" |
|
||||
rounded="rounded-3xl" |
|
||||
placeholder="Share your thoughts ..." |
|
||||
/> |
|
||||
<ButtonCircle |
|
||||
className="absolute right-2 top-1/2 transform -translate-y-1/2" |
|
||||
size=" w-12 h-12 " |
|
||||
> |
|
||||
<ArrowRightIcon className="w-5 h-5" /> |
|
||||
</ButtonCircle> |
|
||||
</div> |
|
||||
</div> |
|
||||
|
|
||||
{/* comment */} |
|
||||
<div className="divide-y divide-neutral-100 dark:divide-neutral-800"> |
|
||||
<CommentListing className="py-8" /> |
|
||||
<CommentListing className="py-8" /> |
|
||||
<CommentListing className="py-8" /> |
|
||||
<CommentListing className="py-8" /> |
|
||||
<div className="pt-8"> |
|
||||
<ButtonSecondary>View more 20 reviews</ButtonSecondary> |
|
||||
</div> |
|
||||
</div> |
|
||||
</div> |
|
||||
); |
|
||||
}; |
|
||||
|
|
||||
const renderSection7 = () => { |
|
||||
return ( |
|
||||
<div className="listingSection__wrap"> |
|
||||
{/* HEADING */} |
|
||||
<div> |
|
||||
<h2 className="text-2xl font-semibold">Location</h2> |
|
||||
<span className="block mt-2 text-neutral-500 dark:text-neutral-400"> |
|
||||
San Diego, CA, United States of America (SAN-San Diego Intl.) |
|
||||
</span> |
|
||||
</div> |
|
||||
<div className="w-14 border-b border-neutral-200 dark:border-neutral-700" /> |
|
||||
|
|
||||
{/* MAP */} |
|
||||
<div className="aspect-w-5 aspect-h-5 sm:aspect-h-3 ring-1 ring-black/10 rounded-xl z-0"> |
|
||||
<div className="rounded-xl overflow-hidden z-0"> |
|
||||
<iframe |
|
||||
width="100%" |
|
||||
height="100%" |
|
||||
loading="lazy" |
|
||||
allowFullScreen |
|
||||
referrerPolicy="no-referrer-when-downgrade" |
|
||||
src="https://www.google.com/maps/embed/v1/place?key=AIzaSyAGVJfZMAKYfZ71nzL_v5i3LjTTWnCYwTY&q=Eiffel+Tower,Paris+France" |
|
||||
></iframe> |
|
||||
</div> |
|
||||
</div> |
|
||||
</div> |
|
||||
); |
|
||||
}; |
|
||||
|
|
||||
const renderSection8 = () => { |
|
||||
return ( |
|
||||
<div className="listingSection__wrap"> |
|
||||
{/* HEADING */} |
|
||||
<h2 className="text-2xl font-semibold">Things to know</h2> |
|
||||
<div className="w-14 border-b border-neutral-200 dark:border-neutral-700" /> |
|
||||
|
|
||||
{/* CONTENT */} |
|
||||
<div> |
|
||||
<h4 className="text-lg font-semibold">Cancellation policy</h4> |
|
||||
<span className="block mt-3 text-neutral-500 dark:text-neutral-400"> |
|
||||
Any experience can be canceled and fully refunded within 24 hours of |
|
||||
purchase, or at least 7 days before the experience starts. |
|
||||
</span> |
|
||||
</div> |
|
||||
<div className="w-14 border-b border-neutral-200 dark:border-neutral-700" /> |
|
||||
|
|
||||
{/* CONTENT */} |
|
||||
<div> |
|
||||
<h4 className="text-lg font-semibold">Guest requirements</h4> |
|
||||
<span className="block mt-3 text-neutral-500 dark:text-neutral-400"> |
|
||||
Up to 10 guests ages 4 and up can attend. Parents may also bring |
|
||||
children under 2 years of age. |
|
||||
</span> |
|
||||
</div> |
|
||||
<div className="w-14 border-b border-neutral-200 dark:border-neutral-700" /> |
|
||||
|
|
||||
{/* CONTENT */} |
|
||||
<div> |
|
||||
<h4 className="text-lg font-semibold">What to bring</h4> |
|
||||
<div className="prose sm:prose"> |
|
||||
<ul className="mt-3 text-neutral-500 dark:text-neutral-400 space-y-2"> |
|
||||
<li> |
|
||||
Formal Wear To Visit Bai Dinh Pagoda Be ready before 7.30 Am. |
|
||||
</li> |
|
||||
<li>We will pick up from 07.30 to 08.00 AM</li> |
|
||||
</ul> |
|
||||
</div> |
|
||||
</div> |
|
||||
</div> |
|
||||
); |
|
||||
}; |
|
||||
|
|
||||
const renderSidebar = () => { |
|
||||
return ( |
|
||||
<div className="listingSectionSidebar__wrap shadow-xl"> |
|
||||
{/* PRICE */} |
|
||||
<div className="flex justify-between"> |
|
||||
<span className="text-3xl font-semibold"> |
|
||||
$19 |
|
||||
<span className="ml-1 text-base font-normal text-neutral-500 dark:text-neutral-400"> |
|
||||
/person |
|
||||
</span> |
|
||||
</span> |
|
||||
<StartRating /> |
|
||||
</div> |
|
||||
|
|
||||
{/* FORM */} |
|
||||
{/* FORM */} |
|
||||
<form className="flex flex-col border border-neutral-200 dark:border-neutral-700 rounded-3xl "> |
|
||||
<StayDatesRangeInput className="flex-1 z-[11]" /> |
|
||||
<div className="w-full border-b border-neutral-200 dark:border-neutral-700"></div> |
|
||||
<GuestsInput className="flex-1" /> |
|
||||
</form> |
|
||||
|
|
||||
{/* SUM */} |
|
||||
<div className="flex flex-col space-y-4"> |
|
||||
<div className="flex justify-between text-neutral-6000 dark:text-neutral-300"> |
|
||||
<span>$19 x 3 adults</span> |
|
||||
<span>$57</span> |
|
||||
</div> |
|
||||
<div className="flex justify-between text-neutral-6000 dark:text-neutral-300"> |
|
||||
<span>Service charge</span> |
|
||||
<span>$0</span> |
|
||||
</div> |
|
||||
<div className="border-b border-neutral-200 dark:border-neutral-700"></div> |
|
||||
<div className="flex justify-between font-semibold"> |
|
||||
<span>Total</span> |
|
||||
<span>$199</span> |
|
||||
</div> |
|
||||
</div> |
|
||||
|
|
||||
{/* SUBMIT */} |
|
||||
<ButtonPrimary href={"/checkout"}>Reserve</ButtonPrimary> |
|
||||
</div> |
|
||||
); |
|
||||
}; |
|
||||
|
|
||||
return ( |
|
||||
<div className={` nc-ListingExperiencesDetailPage `}> |
|
||||
{/* SINGLE HEADER */} |
|
||||
<header className="rounded-md sm:rounded-xl"> |
|
||||
<div className="relative grid grid-cols-4 gap-1 sm:gap-2"> |
|
||||
<div |
|
||||
className="col-span-3 row-span-3 relative rounded-md sm:rounded-xl overflow-hidden cursor-pointer" |
|
||||
onClick={handleOpenModalImageGallery} |
|
||||
> |
|
||||
<Image |
|
||||
alt="photo 1" |
|
||||
fill |
|
||||
className="object-cover rounded-md sm:rounded-xl" |
|
||||
src={PHOTOS[0]} |
|
||||
sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 50vw" |
|
||||
/> |
|
||||
<div className="absolute inset-0 bg-neutral-900 bg-opacity-20 opacity-0 hover:opacity-100 transition-opacity"></div> |
|
||||
</div> |
|
||||
{PHOTOS.filter((_, i) => i >= 1 && i < 4).map((item, index) => ( |
|
||||
<div |
|
||||
key={index} |
|
||||
className={`relative rounded-md sm:rounded-xl overflow-hidden ${ |
|
||||
index >= 2 ? "block" : "" |
|
||||
}`}
|
|
||||
> |
|
||||
<div className="aspect-w-4 aspect-h-3"> |
|
||||
<Image |
|
||||
alt="photos" |
|
||||
fill |
|
||||
className="object-cover w-full h-full rounded-md sm:rounded-xl " |
|
||||
src={item || ""} |
|
||||
sizes="400px" |
|
||||
/> |
|
||||
</div> |
|
||||
|
|
||||
{/* OVERLAY */} |
|
||||
<div |
|
||||
className="absolute inset-0 bg-neutral-900 bg-opacity-20 opacity-0 hover:opacity-100 transition-opacity cursor-pointer" |
|
||||
onClick={handleOpenModalImageGallery} |
|
||||
/> |
|
||||
</div> |
|
||||
))} |
|
||||
|
|
||||
<div |
|
||||
className="absolute hidden md:flex md:items-center md:justify-center left-3 bottom-3 px-4 py-2 rounded-xl bg-neutral-100 text-neutral-500 cursor-pointer hover:bg-neutral-200 z-10" |
|
||||
onClick={handleOpenModalImageGallery} |
|
||||
> |
|
||||
<Squares2X2Icon className="h-5 w-5" /> |
|
||||
<span className="ml-2 text-neutral-800 text-sm font-medium"> |
|
||||
Show all photos |
|
||||
</span> |
|
||||
</div> |
|
||||
</div> |
|
||||
</header> |
|
||||
|
|
||||
{/* MAIn */} |
|
||||
<main className="relative z-10 mt-11 flex flex-col lg:flex-row "> |
|
||||
{/* CONTENT */} |
|
||||
<div className="w-full lg:w-3/5 xl:w-2/3 space-y-8 lg:pr-10 lg:space-y-10"> |
|
||||
{renderSection1()} |
|
||||
{renderSection2()} |
|
||||
{renderSection3()} |
|
||||
<SectionDateRange /> |
|
||||
|
|
||||
{renderSection5()} |
|
||||
{renderSection6()} |
|
||||
{renderSection7()} |
|
||||
{renderSection8()} |
|
||||
</div> |
|
||||
|
|
||||
{/* SIDEBAR */} |
|
||||
<div className="hidden lg:block flex-grow mt-14 lg:mt-0"> |
|
||||
<div className="sticky top-28">{renderSidebar()}</div> |
|
||||
</div> |
|
||||
</main> |
|
||||
</div> |
|
||||
); |
|
||||
}; |
|
||||
|
|
||||
export default ListingExperiencesDetailPage; |
|
@ -1,122 +0,0 @@ |
|||||
"use client"; |
|
||||
|
|
||||
import React, { Fragment, FC, useState } from "react"; |
|
||||
import { Popover, Transition } from "@headlessui/react"; |
|
||||
import NcInputNumber from "@/components/NcInputNumber"; |
|
||||
import { UserPlusIcon } from "@heroicons/react/24/outline"; |
|
||||
import ClearDataButton from "@/app/(client-components)/(HeroSearchForm)/ClearDataButton"; |
|
||||
import { GuestsObject } from "@/app/(client-components)/type"; |
|
||||
|
|
||||
export interface GuestsInputProps { |
|
||||
className?: string; |
|
||||
} |
|
||||
|
|
||||
const GuestsInput: FC<GuestsInputProps> = ({ className = "flex-1" }) => { |
|
||||
const [guestAdultsInputValue, setGuestAdultsInputValue] = useState(2); |
|
||||
const [guestChildrenInputValue, setGuestChildrenInputValue] = useState(1); |
|
||||
const [guestInfantsInputValue, setGuestInfantsInputValue] = useState(1); |
|
||||
|
|
||||
const handleChangeData = (value: number, type: keyof GuestsObject) => { |
|
||||
let newValue = { |
|
||||
guestAdults: guestAdultsInputValue, |
|
||||
guestChildren: guestChildrenInputValue, |
|
||||
guestInfants: guestInfantsInputValue, |
|
||||
}; |
|
||||
if (type === "guestAdults") { |
|
||||
setGuestAdultsInputValue(value); |
|
||||
newValue.guestAdults = value; |
|
||||
} |
|
||||
if (type === "guestChildren") { |
|
||||
setGuestChildrenInputValue(value); |
|
||||
newValue.guestChildren = value; |
|
||||
} |
|
||||
if (type === "guestInfants") { |
|
||||
setGuestInfantsInputValue(value); |
|
||||
newValue.guestInfants = value; |
|
||||
} |
|
||||
}; |
|
||||
|
|
||||
const totalGuests = |
|
||||
guestChildrenInputValue + guestAdultsInputValue + guestInfantsInputValue; |
|
||||
|
|
||||
return ( |
|
||||
<Popover className={`flex relative ${className}`}> |
|
||||
{({ open }) => ( |
|
||||
<> |
|
||||
<div |
|
||||
className={`flex-1 flex items-center focus:outline-none rounded-b-3xl ${ |
|
||||
open ? "shadow-lg" : "" |
|
||||
}`}
|
|
||||
> |
|
||||
<Popover.Button |
|
||||
className={`relative z-10 flex-1 flex text-left items-center p-3 space-x-3 focus:outline-none`} |
|
||||
> |
|
||||
<div className="text-neutral-300 dark:text-neutral-400"> |
|
||||
<UserPlusIcon className="w-5 h-5 lg:w-7 lg:h-7" /> |
|
||||
</div> |
|
||||
<div className="flex-grow"> |
|
||||
<span className="block xl:text-lg font-semibold"> |
|
||||
{totalGuests || ""} Guests |
|
||||
</span> |
|
||||
<span className="block mt-1 text-sm text-neutral-400 leading-none font-light"> |
|
||||
{totalGuests ? "Guests" : "Add guests"} |
|
||||
</span> |
|
||||
</div> |
|
||||
|
|
||||
{!!totalGuests && open && ( |
|
||||
<ClearDataButton |
|
||||
onClick={() => { |
|
||||
setGuestAdultsInputValue(0); |
|
||||
setGuestChildrenInputValue(0); |
|
||||
setGuestInfantsInputValue(0); |
|
||||
}} |
|
||||
/> |
|
||||
)} |
|
||||
</Popover.Button> |
|
||||
</div> |
|
||||
|
|
||||
<Transition |
|
||||
as={Fragment} |
|
||||
enter="transition ease-out duration-200" |
|
||||
enterFrom="opacity-0 translate-y-1" |
|
||||
enterTo="opacity-100 translate-y-0" |
|
||||
leave="transition ease-in duration-150" |
|
||||
leaveFrom="opacity-100 translate-y-0" |
|
||||
leaveTo="opacity-0 translate-y-1" |
|
||||
> |
|
||||
<Popover.Panel className="absolute right-0 z-10 w-full sm:min-w-[340px] max-w-sm bg-white dark:bg-neutral-800 top-full mt-3 py-5 sm:py-6 px-4 sm:px-8 rounded-3xl shadow-xl ring-1 ring-black ring-opacity-5 "> |
|
||||
<NcInputNumber |
|
||||
className="w-full" |
|
||||
defaultValue={guestAdultsInputValue} |
|
||||
onChange={(value) => handleChangeData(value, "guestAdults")} |
|
||||
max={10} |
|
||||
min={1} |
|
||||
label="Adults" |
|
||||
desc="Ages 13 or above" |
|
||||
/> |
|
||||
<NcInputNumber |
|
||||
className="w-full mt-6" |
|
||||
defaultValue={guestChildrenInputValue} |
|
||||
onChange={(value) => handleChangeData(value, "guestChildren")} |
|
||||
max={4} |
|
||||
label="Children" |
|
||||
desc="Ages 2–12" |
|
||||
/> |
|
||||
|
|
||||
<NcInputNumber |
|
||||
className="w-full mt-6" |
|
||||
defaultValue={guestInfantsInputValue} |
|
||||
onChange={(value) => handleChangeData(value, "guestInfants")} |
|
||||
max={4} |
|
||||
label="Infants" |
|
||||
desc="Ages 0–2" |
|
||||
/> |
|
||||
</Popover.Panel> |
|
||||
</Transition> |
|
||||
</> |
|
||||
)} |
|
||||
</Popover> |
|
||||
); |
|
||||
}; |
|
||||
|
|
||||
export default GuestsInput; |
|
@ -1,109 +0,0 @@ |
|||||
"use client"; |
|
||||
|
|
||||
import React, { Fragment, useState, FC } from "react"; |
|
||||
import { Popover, Transition } from "@headlessui/react"; |
|
||||
import { CalendarIcon } from "@heroicons/react/24/outline"; |
|
||||
import DatePickerCustomHeaderTwoMonth from "@/components/DatePickerCustomHeaderTwoMonth"; |
|
||||
import DatePickerCustomDay from "@/components/DatePickerCustomDay"; |
|
||||
import DatePicker from "react-datepicker"; |
|
||||
import ClearDataButton from "@/app/(client-components)/(HeroSearchForm)/ClearDataButton"; |
|
||||
|
|
||||
export interface StayDatesRangeInputProps { |
|
||||
className?: string; |
|
||||
} |
|
||||
|
|
||||
const StayDatesRangeInput: FC<StayDatesRangeInputProps> = ({ |
|
||||
className = "flex-1", |
|
||||
}) => { |
|
||||
const [startDate, setStartDate] = useState<Date | null>( |
|
||||
new Date("2023/02/06") |
|
||||
); |
|
||||
const [endDate, setEndDate] = useState<Date | null>(new Date("2023/02/23")); |
|
||||
//
|
|
||||
|
|
||||
const onChangeDate = (dates: [Date | null, Date | null]) => { |
|
||||
const [start, end] = dates; |
|
||||
setStartDate(start); |
|
||||
setEndDate(end); |
|
||||
}; |
|
||||
|
|
||||
const renderInput = () => { |
|
||||
return ( |
|
||||
<> |
|
||||
<div className="text-neutral-300 dark:text-neutral-400"> |
|
||||
<CalendarIcon className="w-5 h-5 lg:w-7 lg:h-7" /> |
|
||||
</div> |
|
||||
<div className="flex-grow text-left"> |
|
||||
<span className="block xl:text-lg font-semibold"> |
|
||||
{startDate?.toLocaleDateString("en-US", { |
|
||||
month: "short", |
|
||||
day: "2-digit", |
|
||||
}) || "Add dates"} |
|
||||
{endDate |
|
||||
? " - " + |
|
||||
endDate?.toLocaleDateString("en-US", { |
|
||||
month: "short", |
|
||||
day: "2-digit", |
|
||||
}) |
|
||||
: ""} |
|
||||
</span> |
|
||||
<span className="block mt-1 text-sm text-neutral-400 leading-none font-light"> |
|
||||
{"Check in - Check out"} |
|
||||
</span> |
|
||||
</div> |
|
||||
</> |
|
||||
); |
|
||||
}; |
|
||||
|
|
||||
return ( |
|
||||
<Popover className={`StayDatesRangeInput z-10 relative flex ${className}`}> |
|
||||
{({ open }) => ( |
|
||||
<> |
|
||||
<Popover.Button |
|
||||
className={`flex-1 flex relative p-3 items-center space-x-3 focus:outline-none ${ |
|
||||
open ? "shadow-lg" : "" |
|
||||
}`}
|
|
||||
> |
|
||||
{renderInput()} |
|
||||
{startDate && open && ( |
|
||||
<ClearDataButton onClick={() => onChangeDate([null, null])} /> |
|
||||
)} |
|
||||
</Popover.Button> |
|
||||
|
|
||||
<Transition |
|
||||
as={Fragment} |
|
||||
enter="transition ease-out duration-200" |
|
||||
enterFrom="opacity-0 translate-y-1" |
|
||||
enterTo="opacity-100 translate-y-0" |
|
||||
leave="transition ease-in duration-150" |
|
||||
leaveFrom="opacity-100 translate-y-0" |
|
||||
leaveTo="opacity-0 translate-y-1" |
|
||||
> |
|
||||
<Popover.Panel className="absolute left-auto xl:-right-10 right-0 z-10 mt-3 top-full w-screen max-w-sm px-4 sm:px-0 lg:max-w-3xl"> |
|
||||
<div className="overflow-hidden rounded-3xl shadow-lg ring-1 ring-black ring-opacity-5 bg-white dark:bg-neutral-800 p-8"> |
|
||||
<DatePicker |
|
||||
selected={startDate} |
|
||||
onChange={onChangeDate} |
|
||||
startDate={startDate} |
|
||||
endDate={endDate} |
|
||||
selectsRange |
|
||||
monthsShown={2} |
|
||||
showPopperArrow={false} |
|
||||
inline |
|
||||
renderCustomHeader={(p) => ( |
|
||||
<DatePickerCustomHeaderTwoMonth {...p} /> |
|
||||
)} |
|
||||
renderDayContents={(day, date) => ( |
|
||||
<DatePickerCustomDay dayOfMonth={day} date={date} /> |
|
||||
)} |
|
||||
/> |
|
||||
</div> |
|
||||
</Popover.Panel> |
|
||||
</Transition> |
|
||||
</> |
|
||||
)} |
|
||||
</Popover> |
|
||||
); |
|
||||
}; |
|
||||
|
|
||||
export default StayDatesRangeInput; |
|
@ -1,71 +0,0 @@ |
|||||
import { ListingGalleryImage } from "@/components/listing-image-gallery/utils/types"; |
|
||||
|
|
||||
export const PHOTOS: string[] = [ |
|
||||
"https://images.pexels.com/photos/6129967/pexels-photo-6129967.jpeg?auto=compress&cs=tinysrgb&dpr=3&h=750&w=1260", |
|
||||
"https://images.pexels.com/photos/7163619/pexels-photo-7163619.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=750&w=1260", |
|
||||
"https://images.pexels.com/photos/6527036/pexels-photo-6527036.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=750&w=1260", |
|
||||
"https://images.pexels.com/photos/6969831/pexels-photo-6969831.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=750&w=1260", |
|
||||
"https://images.pexels.com/photos/6438752/pexels-photo-6438752.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=750&w=1260", |
|
||||
"https://images.pexels.com/photos/1320686/pexels-photo-1320686.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=750&w=1260", |
|
||||
"https://images.pexels.com/photos/261394/pexels-photo-261394.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=750&w=1260", |
|
||||
"https://images.pexels.com/photos/2861361/pexels-photo-2861361.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=750&w=1260", |
|
||||
"https://images.pexels.com/photos/2677398/pexels-photo-2677398.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=750&w=1260", |
|
||||
"https://images.pexels.com/photos/1365425/pexels-photo-1365425.jpeg?auto=compress&cs=tinysrgb&w=1600", |
|
||||
"https://images.pexels.com/photos/914128/pexels-photo-914128.jpeg?auto=compress&cs=tinysrgb&w=1600", |
|
||||
"https://images.pexels.com/photos/840667/pexels-photo-840667.jpeg?auto=compress&cs=tinysrgb&w=1600", |
|
||||
"https://images.pexels.com/photos/732632/pexels-photo-732632.jpeg?auto=compress&cs=tinysrgb&w=1600", |
|
||||
"https://images.pexels.com/photos/450062/pexels-photo-450062.jpeg?auto=compress&cs=tinysrgb&w=1600", |
|
||||
"https://images.pexels.com/photos/917510/pexels-photo-917510.jpeg?auto=compress&cs=tinysrgb&w=1600", |
|
||||
"https://images.pexels.com/photos/1194233/pexels-photo-1194233.jpeg?auto=compress&cs=tinysrgb&w=1600", |
|
||||
"https://images.pexels.com/photos/236973/pexels-photo-236973.jpeg?auto=compress&cs=tinysrgb&w=1600", |
|
||||
"https://images.pexels.com/photos/1392099/pexels-photo-1392099.jpeg?auto=compress&cs=tinysrgb&w=1600", |
|
||||
"https://images.pexels.com/photos/547116/pexels-photo-547116.jpeg?auto=compress&cs=tinysrgb&w=1600", |
|
||||
"https://images.pexels.com/photos/1002272/pexels-photo-1002272.jpeg?auto=compress&cs=tinysrgb&w=1600", |
|
||||
"https://images.pexels.com/photos/917511/pexels-photo-917511.jpeg?auto=compress&cs=tinysrgb&w=1600", |
|
||||
"https://images.pexels.com/photos/771079/pexels-photo-771079.jpeg?auto=compress&cs=tinysrgb&w=1600", |
|
||||
"https://images.pexels.com/photos/13461077/pexels-photo-13461077.jpeg?auto=compress&cs=tinysrgb&w=1600&lazy=load", |
|
||||
"https://images.pexels.com/photos/9074921/pexels-photo-9074921.jpeg?auto=compress&cs=tinysrgb&w=1600&lazy=load", |
|
||||
"https://images.pexels.com/photos/9336042/pexels-photo-9336042.jpeg?auto=compress&cs=tinysrgb&w=1600&lazy=load", |
|
||||
"https://images.pexels.com/photos/5418318/pexels-photo-5418318.jpeg?auto=compress&cs=tinysrgb&w=1600&lazy=load", |
|
||||
"https://images.pexels.com/photos/4815278/pexels-photo-4815278.jpeg?auto=compress&cs=tinysrgb&w=1600&lazy=load", |
|
||||
"https://images.pexels.com/photos/1365425/pexels-photo-1365425.jpeg?auto=compress&cs=tinysrgb&w=1600", |
|
||||
]; |
|
||||
|
|
||||
export const Amenities_demos = [ |
|
||||
{ name: "la-key", icon: "la-key" }, |
|
||||
{ name: "la-luggage-cart", icon: "la-luggage-cart" }, |
|
||||
{ name: "la-shower", icon: "la-shower" }, |
|
||||
{ name: "la-smoking", icon: "la-smoking" }, |
|
||||
{ name: "la-snowflake", icon: "la-snowflake" }, |
|
||||
{ name: "la-spa", icon: "la-spa" }, |
|
||||
{ name: "la-suitcase", icon: "la-suitcase" }, |
|
||||
{ name: "la-suitcase-rolling", icon: "la-suitcase-rolling" }, |
|
||||
{ name: "la-swimmer", icon: "la-swimmer" }, |
|
||||
{ name: "la-swimming-pool", icon: "la-swimming-pool" }, |
|
||||
{ name: "la-tv", icon: "la-tv" }, |
|
||||
{ name: "la-umbrella-beach", icon: "la-umbrella-beach" }, |
|
||||
{ name: "la-utensils", icon: "la-utensils" }, |
|
||||
{ name: "la-wheelchair", icon: "la-wheelchair" }, |
|
||||
{ name: "la-wifi", icon: "la-wifi" }, |
|
||||
{ name: "la-baby-carriage", icon: "la-baby-carriage" }, |
|
||||
{ name: "la-bath", icon: "la-bath" }, |
|
||||
{ name: "la-bed", icon: "la-bed" }, |
|
||||
{ name: "la-briefcase", icon: "la-briefcase" }, |
|
||||
{ name: "la-car", icon: "la-car" }, |
|
||||
{ name: "la-cocktail", icon: "la-cocktail" }, |
|
||||
{ name: "la-coffee", icon: "la-coffee" }, |
|
||||
{ name: "la-concierge-bell", icon: "la-concierge-bell" }, |
|
||||
{ name: "la-dice", icon: "la-dice" }, |
|
||||
{ name: "la-dumbbell", icon: "la-dumbbell" }, |
|
||||
{ name: "la-hot-tub", icon: "la-hot-tub" }, |
|
||||
{ name: "la-infinity", icon: "la-infinity" }, |
|
||||
]; |
|
||||
|
|
||||
export const imageGallery: ListingGalleryImage[] = [...PHOTOS].map( |
|
||||
(item, index): ListingGalleryImage => { |
|
||||
return { |
|
||||
id: index, |
|
||||
url: item, |
|
||||
}; |
|
||||
} |
|
||||
); |
|
@ -1,637 +0,0 @@ |
|||||
"use client"; |
|
||||
|
|
||||
import React, { FC, Fragment, useState } from "react"; |
|
||||
import { Dialog, Transition } from "@headlessui/react"; |
|
||||
import { ArrowRightIcon, Squares2X2Icon } from "@heroicons/react/24/outline"; |
|
||||
import CommentListing from "@/components/CommentListing"; |
|
||||
import FiveStartIconForRate from "@/components/FiveStartIconForRate"; |
|
||||
import StartRating from "@/components/StartRating"; |
|
||||
import Avatar from "@/shared/Avatar"; |
|
||||
import Badge from "@/shared/Badge"; |
|
||||
import ButtonCircle from "@/shared/ButtonCircle"; |
|
||||
import ButtonPrimary from "@/shared/ButtonPrimary"; |
|
||||
import ButtonSecondary from "@/shared/ButtonSecondary"; |
|
||||
import ButtonClose from "@/shared/ButtonClose"; |
|
||||
import Input from "@/shared/Input"; |
|
||||
import LikeSaveBtns from "@/components/LikeSaveBtns"; |
|
||||
import Image from "next/image"; |
|
||||
import { usePathname, useRouter } from "next/navigation"; |
|
||||
import { Amenities_demos, PHOTOS } from "./constant"; |
|
||||
import StayDatesRangeInput from "./StayDatesRangeInput"; |
|
||||
import GuestsInput from "./GuestsInput"; |
|
||||
import SectionDateRange from "../SectionDateRange"; |
|
||||
import { Route } from "next"; |
|
||||
|
|
||||
export interface ListingStayDetailPageProps {} |
|
||||
|
|
||||
const ListingStayDetailPage: FC<ListingStayDetailPageProps> = ({}) => { |
|
||||
//
|
|
||||
|
|
||||
let [isOpenModalAmenities, setIsOpenModalAmenities] = useState(false); |
|
||||
|
|
||||
const thisPathname = usePathname(); |
|
||||
const router = useRouter(); |
|
||||
|
|
||||
function closeModalAmenities() { |
|
||||
setIsOpenModalAmenities(false); |
|
||||
} |
|
||||
|
|
||||
function openModalAmenities() { |
|
||||
setIsOpenModalAmenities(true); |
|
||||
} |
|
||||
|
|
||||
const handleOpenModalImageGallery = () => { |
|
||||
router.push(`${thisPathname}/?modal=PHOTO_TOUR_SCROLLABLE` as Route); |
|
||||
}; |
|
||||
|
|
||||
const renderSection1 = () => { |
|
||||
return ( |
|
||||
<div className="listingSection__wrap !space-y-6"> |
|
||||
{/* 1 */} |
|
||||
<div className="flex justify-between items-center"> |
|
||||
<Badge name="Wooden house" /> |
|
||||
<LikeSaveBtns /> |
|
||||
</div> |
|
||||
|
|
||||
{/* 2 */} |
|
||||
<h2 className="text-2xl sm:text-3xl lg:text-4xl font-semibold"> |
|
||||
Beach House in Collingwood |
|
||||
</h2> |
|
||||
|
|
||||
{/* 3 */} |
|
||||
<div className="flex items-center space-x-4"> |
|
||||
<StartRating /> |
|
||||
<span>·</span> |
|
||||
<span> |
|
||||
<i className="las la-map-marker-alt"></i> |
|
||||
<span className="ml-1"> Tokyo, Jappan</span> |
|
||||
</span> |
|
||||
</div> |
|
||||
|
|
||||
{/* 4 */} |
|
||||
<div className="flex items-center"> |
|
||||
<Avatar hasChecked sizeClass="h-10 w-10" radius="rounded-full" /> |
|
||||
<span className="ml-2.5 text-neutral-500 dark:text-neutral-400"> |
|
||||
Hosted by{" "} |
|
||||
<span className="text-neutral-900 dark:text-neutral-200 font-medium"> |
|
||||
Kevin Francis |
|
||||
</span> |
|
||||
</span> |
|
||||
</div> |
|
||||
|
|
||||
{/* 5 */} |
|
||||
<div className="w-full border-b border-neutral-100 dark:border-neutral-700" /> |
|
||||
|
|
||||
{/* 6 */} |
|
||||
<div className="flex items-center justify-between xl:justify-start space-x-8 xl:space-x-12 text-sm text-neutral-700 dark:text-neutral-300"> |
|
||||
<div className="flex items-center space-x-3 "> |
|
||||
<i className=" las la-user text-2xl "></i> |
|
||||
<span className=""> |
|
||||
6 <span className="hidden sm:inline-block">guests</span> |
|
||||
</span> |
|
||||
</div> |
|
||||
<div className="flex items-center space-x-3"> |
|
||||
<i className=" las la-bed text-2xl"></i> |
|
||||
<span className=" "> |
|
||||
6 <span className="hidden sm:inline-block">beds</span> |
|
||||
</span> |
|
||||
</div> |
|
||||
<div className="flex items-center space-x-3"> |
|
||||
<i className=" las la-bath text-2xl"></i> |
|
||||
<span className=" "> |
|
||||
3 <span className="hidden sm:inline-block">baths</span> |
|
||||
</span> |
|
||||
</div> |
|
||||
<div className="flex items-center space-x-3"> |
|
||||
<i className=" las la-door-open text-2xl"></i> |
|
||||
<span className=" "> |
|
||||
2 <span className="hidden sm:inline-block">bedrooms</span> |
|
||||
</span> |
|
||||
</div> |
|
||||
</div> |
|
||||
</div> |
|
||||
); |
|
||||
}; |
|
||||
|
|
||||
const renderSection2 = () => { |
|
||||
return ( |
|
||||
<div className="listingSection__wrap"> |
|
||||
<h2 className="text-2xl font-semibold">Stay information</h2> |
|
||||
<div className="w-14 border-b border-neutral-200 dark:border-neutral-700"></div> |
|
||||
<div className="text-neutral-6000 dark:text-neutral-300"> |
|
||||
<span> |
|
||||
Providing lake views, The Symphony 9 Tam Coc in Ninh Binh provides |
|
||||
accommodation, an outdoor swimming pool, a bar, a shared lounge, a |
|
||||
garden and barbecue facilities. Complimentary WiFi is provided. |
|
||||
</span> |
|
||||
<br /> |
|
||||
<br /> |
|
||||
<span> |
|
||||
There is a private bathroom with bidet in all units, along with a |
|
||||
hairdryer and free toiletries. |
|
||||
</span> |
|
||||
<br /> <br /> |
|
||||
<span> |
|
||||
The Symphony 9 Tam Coc offers a terrace. Both a bicycle rental |
|
||||
service and a car rental service are available at the accommodation, |
|
||||
while cycling can be enjoyed nearby. |
|
||||
</span> |
|
||||
</div> |
|
||||
</div> |
|
||||
); |
|
||||
}; |
|
||||
|
|
||||
const renderSection3 = () => { |
|
||||
return ( |
|
||||
<div className="listingSection__wrap"> |
|
||||
<div> |
|
||||
<h2 className="text-2xl font-semibold">Amenities </h2> |
|
||||
<span className="block mt-2 text-neutral-500 dark:text-neutral-400"> |
|
||||
{` About the property's amenities and services`} |
|
||||
</span> |
|
||||
</div> |
|
||||
<div className="w-14 border-b border-neutral-200 dark:border-neutral-700"></div> |
|
||||
{/* 6 */} |
|
||||
<div className="grid grid-cols-1 xl:grid-cols-3 gap-6 text-sm text-neutral-700 dark:text-neutral-300 "> |
|
||||
{Amenities_demos.filter((_, i) => i < 12).map((item) => ( |
|
||||
<div key={item.name} className="flex items-center space-x-3"> |
|
||||
<i className={`text-3xl las ${item.icon}`}></i> |
|
||||
<span className=" ">{item.name}</span> |
|
||||
</div> |
|
||||
))} |
|
||||
</div> |
|
||||
|
|
||||
{/* ----- */} |
|
||||
<div className="w-14 border-b border-neutral-200"></div> |
|
||||
<div> |
|
||||
<ButtonSecondary onClick={openModalAmenities}> |
|
||||
View more 20 amenities |
|
||||
</ButtonSecondary> |
|
||||
</div> |
|
||||
{renderMotalAmenities()} |
|
||||
</div> |
|
||||
); |
|
||||
}; |
|
||||
|
|
||||
const renderMotalAmenities = () => { |
|
||||
return ( |
|
||||
<Transition appear show={isOpenModalAmenities} as={Fragment}> |
|
||||
<Dialog |
|
||||
as="div" |
|
||||
className="fixed inset-0 z-50 overflow-y-auto" |
|
||||
onClose={closeModalAmenities} |
|
||||
> |
|
||||
<div className="min-h-screen px-4 text-center"> |
|
||||
<Transition.Child |
|
||||
as={Fragment} |
|
||||
enter="ease-out duration-300" |
|
||||
enterFrom="opacity-0" |
|
||||
enterTo="opacity-100" |
|
||||
leave="ease-in duration-200" |
|
||||
leaveFrom="opacity-100" |
|
||||
leaveTo="opacity-0" |
|
||||
> |
|
||||
<Dialog.Overlay className="fixed inset-0 bg-black bg-opacity-40" /> |
|
||||
</Transition.Child> |
|
||||
|
|
||||
{/* This element is to trick the browser into centering the modal contents. */} |
|
||||
<span |
|
||||
className="inline-block h-screen align-middle" |
|
||||
aria-hidden="true" |
|
||||
> |
|
||||
​ |
|
||||
</span> |
|
||||
<Transition.Child |
|
||||
as={Fragment} |
|
||||
enter="ease-out duration-300" |
|
||||
enterFrom="opacity-0 scale-95" |
|
||||
enterTo="opacity-100 scale-100" |
|
||||
leave="ease-in duration-200" |
|
||||
leaveFrom="opacity-100 scale-100" |
|
||||
leaveTo="opacity-0 scale-95" |
|
||||
> |
|
||||
<div className="inline-block py-8 h-screen w-full max-w-4xl"> |
|
||||
<div className="inline-flex pb-2 flex-col w-full text-left align-middle transition-all transform overflow-hidden rounded-2xl bg-white dark:bg-neutral-900 dark:border dark:border-neutral-700 dark:text-neutral-100 shadow-xl h-full"> |
|
||||
<div className="relative flex-shrink-0 px-6 py-4 border-b border-neutral-200 dark:border-neutral-800 text-center"> |
|
||||
<h3 |
|
||||
className="text-lg font-medium leading-6 text-gray-900" |
|
||||
id="headlessui-dialog-title-70" |
|
||||
> |
|
||||
Amenities |
|
||||
</h3> |
|
||||
<span className="absolute left-3 top-3"> |
|
||||
<ButtonClose onClick={closeModalAmenities} /> |
|
||||
</span> |
|
||||
</div> |
|
||||
<div className="px-8 overflow-auto text-neutral-700 dark:text-neutral-300 divide-y divide-neutral-200"> |
|
||||
{Amenities_demos.filter((_, i) => i < 1212).map((item) => ( |
|
||||
<div |
|
||||
key={item.name} |
|
||||
className="flex items-center py-2.5 sm:py-4 lg:py-5 space-x-5 lg:space-x-8" |
|
||||
> |
|
||||
<i |
|
||||
className={`text-4xl text-neutral-6000 las ${item.icon}`} |
|
||||
></i> |
|
||||
<span>{item.name}</span> |
|
||||
</div> |
|
||||
))} |
|
||||
</div> |
|
||||
</div> |
|
||||
</div> |
|
||||
</Transition.Child> |
|
||||
</div> |
|
||||
</Dialog> |
|
||||
</Transition> |
|
||||
); |
|
||||
}; |
|
||||
|
|
||||
const renderSection4 = () => { |
|
||||
return ( |
|
||||
<div className="listingSection__wrap"> |
|
||||
{/* HEADING */} |
|
||||
<div> |
|
||||
<h2 className="text-2xl font-semibold">Room Rates </h2> |
|
||||
<span className="block mt-2 text-neutral-500 dark:text-neutral-400"> |
|
||||
Prices may increase on weekends or holidays |
|
||||
</span> |
|
||||
</div> |
|
||||
<div className="w-14 border-b border-neutral-200 dark:border-neutral-700"></div> |
|
||||
{/* CONTENT */} |
|
||||
<div className="flow-root"> |
|
||||
<div className="text-sm sm:text-base text-neutral-6000 dark:text-neutral-300 -mb-4"> |
|
||||
<div className="p-4 bg-neutral-100 dark:bg-neutral-800 flex justify-between items-center space-x-4 rounded-lg"> |
|
||||
<span>Monday - Thursday</span> |
|
||||
<span>$199</span> |
|
||||
</div> |
|
||||
<div className="p-4 flex justify-between items-center space-x-4 rounded-lg"> |
|
||||
<span>Monday - Thursday</span> |
|
||||
<span>$199</span> |
|
||||
</div> |
|
||||
<div className="p-4 bg-neutral-100 dark:bg-neutral-800 flex justify-between items-center space-x-4 rounded-lg"> |
|
||||
<span>Friday - Sunday</span> |
|
||||
<span>$219</span> |
|
||||
</div> |
|
||||
<div className="p-4 flex justify-between items-center space-x-4 rounded-lg"> |
|
||||
<span>Rent by month</span> |
|
||||
<span>-8.34 %</span> |
|
||||
</div> |
|
||||
<div className="p-4 bg-neutral-100 dark:bg-neutral-800 flex justify-between items-center space-x-4 rounded-lg"> |
|
||||
<span>Minimum number of nights</span> |
|
||||
<span>1 night</span> |
|
||||
</div> |
|
||||
<div className="p-4 flex justify-between items-center space-x-4 rounded-lg"> |
|
||||
<span>Max number of nights</span> |
|
||||
<span>90 nights</span> |
|
||||
</div> |
|
||||
</div> |
|
||||
</div> |
|
||||
</div> |
|
||||
); |
|
||||
}; |
|
||||
|
|
||||
const renderSection5 = () => { |
|
||||
return ( |
|
||||
<div className="listingSection__wrap"> |
|
||||
{/* HEADING */} |
|
||||
<h2 className="text-2xl font-semibold">Host Information</h2> |
|
||||
<div className="w-14 border-b border-neutral-200 dark:border-neutral-700"></div> |
|
||||
|
|
||||
{/* host */} |
|
||||
<div className="flex items-center space-x-4"> |
|
||||
<Avatar |
|
||||
hasChecked |
|
||||
hasCheckedClass="w-4 h-4 -top-0.5 right-0.5" |
|
||||
sizeClass="h-14 w-14" |
|
||||
radius="rounded-full" |
|
||||
/> |
|
||||
<div> |
|
||||
<a className="block text-xl font-medium" href="##"> |
|
||||
Kevin Francis |
|
||||
</a> |
|
||||
<div className="mt-1.5 flex items-center text-sm text-neutral-500 dark:text-neutral-400"> |
|
||||
<StartRating /> |
|
||||
<span className="mx-2">·</span> |
|
||||
<span> 12 places</span> |
|
||||
</div> |
|
||||
</div> |
|
||||
</div> |
|
||||
|
|
||||
{/* desc */} |
|
||||
<span className="block text-neutral-6000 dark:text-neutral-300"> |
|
||||
Providing lake views, The Symphony 9 Tam Coc in Ninh Binh provides |
|
||||
accommodation, an outdoor swimming pool, a bar, a shared lounge, a |
|
||||
garden and barbecue facilities... |
|
||||
</span> |
|
||||
|
|
||||
{/* info */} |
|
||||
<div className="block text-neutral-500 dark:text-neutral-400 space-y-2.5"> |
|
||||
<div className="flex items-center space-x-3"> |
|
||||
<svg |
|
||||
xmlns="http://www.w3.org/2000/svg" |
|
||||
className="h-6 w-6" |
|
||||
fill="none" |
|
||||
viewBox="0 0 24 24" |
|
||||
stroke="currentColor" |
|
||||
> |
|
||||
<path |
|
||||
strokeLinecap="round" |
|
||||
strokeLinejoin="round" |
|
||||
strokeWidth={1.5} |
|
||||
d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z" |
|
||||
/> |
|
||||
</svg> |
|
||||
<span>Joined in March 2016</span> |
|
||||
</div> |
|
||||
<div className="flex items-center space-x-3"> |
|
||||
<svg |
|
||||
xmlns="http://www.w3.org/2000/svg" |
|
||||
className="h-6 w-6" |
|
||||
fill="none" |
|
||||
viewBox="0 0 24 24" |
|
||||
stroke="currentColor" |
|
||||
> |
|
||||
<path |
|
||||
strokeLinecap="round" |
|
||||
strokeLinejoin="round" |
|
||||
strokeWidth={1.5} |
|
||||
d="M7 8h10M7 12h4m1 8l-4-4H5a2 2 0 01-2-2V6a2 2 0 012-2h14a2 2 0 012 2v8a2 2 0 01-2 2h-3l-4 4z" |
|
||||
/> |
|
||||
</svg> |
|
||||
<span>Response rate - 100%</span> |
|
||||
</div> |
|
||||
<div className="flex items-center space-x-3"> |
|
||||
<svg |
|
||||
xmlns="http://www.w3.org/2000/svg" |
|
||||
className="h-6 w-6" |
|
||||
fill="none" |
|
||||
viewBox="0 0 24 24" |
|
||||
stroke="currentColor" |
|
||||
> |
|
||||
<path |
|
||||
strokeLinecap="round" |
|
||||
strokeLinejoin="round" |
|
||||
strokeWidth={1.5} |
|
||||
d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z" |
|
||||
/> |
|
||||
</svg> |
|
||||
|
|
||||
<span>Fast response - within a few hours</span> |
|
||||
</div> |
|
||||
</div> |
|
||||
|
|
||||
{/* == */} |
|
||||
<div className="w-14 border-b border-neutral-200 dark:border-neutral-700"></div> |
|
||||
<div> |
|
||||
<ButtonSecondary href="/author">See host profile</ButtonSecondary> |
|
||||
</div> |
|
||||
</div> |
|
||||
); |
|
||||
}; |
|
||||
|
|
||||
const renderSection6 = () => { |
|
||||
return ( |
|
||||
<div className="listingSection__wrap"> |
|
||||
{/* HEADING */} |
|
||||
<h2 className="text-2xl font-semibold">Reviews (23 reviews)</h2> |
|
||||
<div className="w-14 border-b border-neutral-200 dark:border-neutral-700"></div> |
|
||||
|
|
||||
{/* Content */} |
|
||||
<div className="space-y-5"> |
|
||||
<FiveStartIconForRate iconClass="w-6 h-6" className="space-x-0.5" /> |
|
||||
<div className="relative"> |
|
||||
<Input |
|
||||
fontClass="" |
|
||||
sizeClass="h-16 px-4 py-3" |
|
||||
rounded="rounded-3xl" |
|
||||
placeholder="Share your thoughts ..." |
|
||||
/> |
|
||||
<ButtonCircle |
|
||||
className="absolute right-2 top-1/2 transform -translate-y-1/2" |
|
||||
size=" w-12 h-12 " |
|
||||
> |
|
||||
<ArrowRightIcon className="w-5 h-5" /> |
|
||||
</ButtonCircle> |
|
||||
</div> |
|
||||
</div> |
|
||||
|
|
||||
{/* comment */} |
|
||||
<div className="divide-y divide-neutral-100 dark:divide-neutral-800"> |
|
||||
<CommentListing className="py-8" /> |
|
||||
<CommentListing className="py-8" /> |
|
||||
<CommentListing className="py-8" /> |
|
||||
<CommentListing className="py-8" /> |
|
||||
<div className="pt-8"> |
|
||||
<ButtonSecondary>View more 20 reviews</ButtonSecondary> |
|
||||
</div> |
|
||||
</div> |
|
||||
</div> |
|
||||
); |
|
||||
}; |
|
||||
|
|
||||
const renderSection7 = () => { |
|
||||
return ( |
|
||||
<div className="listingSection__wrap"> |
|
||||
{/* HEADING */} |
|
||||
<div> |
|
||||
<h2 className="text-2xl font-semibold">Location</h2> |
|
||||
<span className="block mt-2 text-neutral-500 dark:text-neutral-400"> |
|
||||
San Diego, CA, United States of America (SAN-San Diego Intl.) |
|
||||
</span> |
|
||||
</div> |
|
||||
<div className="w-14 border-b border-neutral-200 dark:border-neutral-700" /> |
|
||||
|
|
||||
{/* MAP */} |
|
||||
<div className="aspect-w-5 aspect-h-5 sm:aspect-h-3 ring-1 ring-black/10 rounded-xl z-0"> |
|
||||
<div className="rounded-xl overflow-hidden z-0"> |
|
||||
<iframe |
|
||||
width="100%" |
|
||||
height="100%" |
|
||||
loading="lazy" |
|
||||
allowFullScreen |
|
||||
referrerPolicy="no-referrer-when-downgrade" |
|
||||
src="https://www.google.com/maps/embed/v1/place?key=AIzaSyAGVJfZMAKYfZ71nzL_v5i3LjTTWnCYwTY&q=Eiffel+Tower,Paris+France" |
|
||||
></iframe> |
|
||||
</div> |
|
||||
</div> |
|
||||
</div> |
|
||||
); |
|
||||
}; |
|
||||
|
|
||||
const renderSection8 = () => { |
|
||||
return ( |
|
||||
<div className="listingSection__wrap"> |
|
||||
{/* HEADING */} |
|
||||
<h2 className="text-2xl font-semibold">Things to know</h2> |
|
||||
<div className="w-14 border-b border-neutral-200 dark:border-neutral-700" /> |
|
||||
|
|
||||
{/* CONTENT */} |
|
||||
<div> |
|
||||
<h4 className="text-lg font-semibold">Cancellation policy</h4> |
|
||||
<span className="block mt-3 text-neutral-500 dark:text-neutral-400"> |
|
||||
Refund 50% of the booking value when customers cancel the room |
|
||||
within 48 hours after successful booking and 14 days before the |
|
||||
check-in time. <br /> |
|
||||
Then, cancel the room 14 days before the check-in time, get a 50% |
|
||||
refund of the total amount paid (minus the service fee). |
|
||||
</span> |
|
||||
</div> |
|
||||
<div className="w-14 border-b border-neutral-200 dark:border-neutral-700" /> |
|
||||
|
|
||||
{/* CONTENT */} |
|
||||
<div> |
|
||||
<h4 className="text-lg font-semibold">Check-in time</h4> |
|
||||
<div className="mt-3 text-neutral-500 dark:text-neutral-400 max-w-md text-sm sm:text-base"> |
|
||||
<div className="flex space-x-10 justify-between p-3 bg-neutral-100 dark:bg-neutral-800 rounded-lg"> |
|
||||
<span>Check-in</span> |
|
||||
<span>08:00 am - 12:00 am</span> |
|
||||
</div> |
|
||||
<div className="flex space-x-10 justify-between p-3"> |
|
||||
<span>Check-out</span> |
|
||||
<span>02:00 pm - 04:00 pm</span> |
|
||||
</div> |
|
||||
</div> |
|
||||
</div> |
|
||||
<div className="w-14 border-b border-neutral-200 dark:border-neutral-700" /> |
|
||||
|
|
||||
{/* CONTENT */} |
|
||||
<div> |
|
||||
<h4 className="text-lg font-semibold">Special Note</h4> |
|
||||
<div className="prose sm:prose"> |
|
||||
<ul className="mt-3 text-neutral-500 dark:text-neutral-400 space-y-2"> |
|
||||
<li> |
|
||||
Ban and I will work together to keep the landscape and |
|
||||
environment green and clean by not littering, not using |
|
||||
stimulants and respecting people around. |
|
||||
</li> |
|
||||
<li>Do not sing karaoke past 11:30</li> |
|
||||
</ul> |
|
||||
</div> |
|
||||
</div> |
|
||||
</div> |
|
||||
); |
|
||||
}; |
|
||||
|
|
||||
const renderSidebar = () => { |
|
||||
return ( |
|
||||
<div className="listingSectionSidebar__wrap shadow-xl"> |
|
||||
{/* PRICE */} |
|
||||
<div className="flex justify-between"> |
|
||||
<span className="text-3xl font-semibold"> |
|
||||
$119 |
|
||||
<span className="ml-1 text-base font-normal text-neutral-500 dark:text-neutral-400"> |
|
||||
/night |
|
||||
</span> |
|
||||
</span> |
|
||||
<StartRating /> |
|
||||
</div> |
|
||||
|
|
||||
{/* FORM */} |
|
||||
<form className="flex flex-col border border-neutral-200 dark:border-neutral-700 rounded-3xl "> |
|
||||
<StayDatesRangeInput className="flex-1 z-[11]" /> |
|
||||
<div className="w-full border-b border-neutral-200 dark:border-neutral-700"></div> |
|
||||
<GuestsInput className="flex-1" /> |
|
||||
</form> |
|
||||
|
|
||||
{/* SUM */} |
|
||||
<div className="flex flex-col space-y-4"> |
|
||||
<div className="flex justify-between text-neutral-6000 dark:text-neutral-300"> |
|
||||
<span>$119 x 3 night</span> |
|
||||
<span>$357</span> |
|
||||
</div> |
|
||||
<div className="flex justify-between text-neutral-6000 dark:text-neutral-300"> |
|
||||
<span>Service charge</span> |
|
||||
<span>$0</span> |
|
||||
</div> |
|
||||
<div className="border-b border-neutral-200 dark:border-neutral-700"></div> |
|
||||
<div className="flex justify-between font-semibold"> |
|
||||
<span>Total</span> |
|
||||
<span>$199</span> |
|
||||
</div> |
|
||||
</div> |
|
||||
|
|
||||
{/* SUBMIT */} |
|
||||
<ButtonPrimary href={"/checkout"}>Reserve</ButtonPrimary> |
|
||||
</div> |
|
||||
); |
|
||||
}; |
|
||||
|
|
||||
return ( |
|
||||
<div className="nc-ListingStayDetailPage"> |
|
||||
{/* HEADER */} |
|
||||
<header className="rounded-md sm:rounded-xl"> |
|
||||
<div className="relative grid grid-cols-3 sm:grid-cols-4 gap-1 sm:gap-2"> |
|
||||
<div |
|
||||
className="col-span-2 row-span-3 sm:row-span-2 relative rounded-md sm:rounded-xl overflow-hidden cursor-pointer" |
|
||||
onClick={handleOpenModalImageGallery} |
|
||||
> |
|
||||
<Image |
|
||||
fill |
|
||||
className="object-cover rounded-md sm:rounded-xl" |
|
||||
src={PHOTOS[0]} |
|
||||
alt="" |
|
||||
sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 50vw" |
|
||||
/> |
|
||||
<div className="absolute inset-0 bg-neutral-900 bg-opacity-20 opacity-0 hover:opacity-100 transition-opacity"></div> |
|
||||
</div> |
|
||||
{PHOTOS.filter((_, i) => i >= 1 && i < 5).map((item, index) => ( |
|
||||
<div |
|
||||
key={index} |
|
||||
className={`relative rounded-md sm:rounded-xl overflow-hidden ${ |
|
||||
index >= 3 ? "hidden sm:block" : "" |
|
||||
}`}
|
|
||||
> |
|
||||
<div className="aspect-w-4 aspect-h-3 sm:aspect-w-6 sm:aspect-h-5"> |
|
||||
<Image |
|
||||
fill |
|
||||
className="object-cover rounded-md sm:rounded-xl " |
|
||||
src={item || ""} |
|
||||
alt="" |
|
||||
sizes="400px" |
|
||||
/> |
|
||||
</div> |
|
||||
|
|
||||
{/* OVERLAY */} |
|
||||
<div |
|
||||
className="absolute inset-0 bg-neutral-900 bg-opacity-20 opacity-0 hover:opacity-100 transition-opacity cursor-pointer" |
|
||||
onClick={handleOpenModalImageGallery} |
|
||||
/> |
|
||||
</div> |
|
||||
))} |
|
||||
|
|
||||
<button |
|
||||
className="absolute hidden md:flex md:items-center md:justify-center left-3 bottom-3 px-4 py-2 rounded-xl bg-neutral-100 text-neutral-500 hover:bg-neutral-200 z-10" |
|
||||
onClick={handleOpenModalImageGallery} |
|
||||
> |
|
||||
<Squares2X2Icon className="w-5 h-5" /> |
|
||||
<span className="ml-2 text-neutral-800 text-sm font-medium"> |
|
||||
Show all photos |
|
||||
</span> |
|
||||
</button> |
|
||||
</div> |
|
||||
</header> |
|
||||
|
|
||||
{/* MAIN */} |
|
||||
<main className=" relative z-10 mt-11 flex flex-col lg:flex-row "> |
|
||||
{/* CONTENT */} |
|
||||
<div className="w-full lg:w-3/5 xl:w-2/3 space-y-8 lg:space-y-10 lg:pr-10"> |
|
||||
{renderSection1()} |
|
||||
{renderSection2()} |
|
||||
{renderSection3()} |
|
||||
{renderSection4()} |
|
||||
<SectionDateRange /> |
|
||||
{renderSection5()} |
|
||||
{renderSection6()} |
|
||||
{renderSection7()} |
|
||||
{renderSection8()} |
|
||||
</div> |
|
||||
|
|
||||
{/* SIDEBAR */} |
|
||||
<div className="hidden lg:block flex-grow mt-14 lg:mt-0"> |
|
||||
<div className="sticky top-28">{renderSidebar()}</div> |
|
||||
</div> |
|
||||
</main> |
|
||||
</div> |
|
||||
); |
|
||||
}; |
|
||||
|
|
||||
export default ListingStayDetailPage; |
|
@ -1,45 +0,0 @@ |
|||||
import React, { FC } from "react"; |
|
||||
import imagePng from "@/images/hero-right-3.png"; |
|
||||
import Image from "next/image"; |
|
||||
import HeroRealEstateSearchForm from "../(client-components)/(HeroSearchForm)/(real-estate-search-form)/HeroRealEstateSearchForm"; |
|
||||
|
|
||||
export interface SectionHero2ArchivePageProps { |
|
||||
className?: string; |
|
||||
} |
|
||||
|
|
||||
const SectionHero2ArchivePage: FC<SectionHero2ArchivePageProps> = ({ |
|
||||
className = "", |
|
||||
}) => { |
|
||||
return ( |
|
||||
<div |
|
||||
className={`nc-SectionHero2ArchivePage relative ${className}`} |
|
||||
data-nc-id="SectionHero2ArchivePage" |
|
||||
> |
|
||||
<div className="absolute inset-y-0 w-5/6 xl:w-3/4 right-0 flex-grow"> |
|
||||
<Image fill className="object-cover" src={imagePng} alt="hero" /> |
|
||||
</div> |
|
||||
<div className="relative py-14 "> |
|
||||
<div className="relative inline-flex"> |
|
||||
<div className="w-screen right-10 md:right-32 inset-y-0 absolute bg-primary-500"></div> |
|
||||
<div className="relative max-w-3xl inline-flex flex-shrink-0 flex-col items-start py-16 sm:py-20 space-y-8 sm:space-y-10 text-white"> |
|
||||
<h2 className="font-medium text-4xl md:text-5xl xl:text-7xl leading-[110%]"> |
|
||||
Tokyo, Jappan |
|
||||
</h2> |
|
||||
<div className="flex items-center text-base md:text-lg "> |
|
||||
<i className="text-2xl las la-map-marked"></i> |
|
||||
<span className="ml-2.5">Jappan </span> |
|
||||
<span className="mx-5"></span> |
|
||||
<i className="text-2xl las la-home"></i> |
|
||||
<span className="ml-2.5">112 properties</span> |
|
||||
</div> |
|
||||
</div> |
|
||||
</div> |
|
||||
<div className="hidden lg:block mt-10 w-full"> |
|
||||
<HeroRealEstateSearchForm /> |
|
||||
</div> |
|
||||
</div> |
|
||||
</div> |
|
||||
); |
|
||||
}; |
|
||||
|
|
||||
export default SectionHero2ArchivePage; |
|
Write
Preview
Loading…
Cancel
Save
Reference in new issue