Browse Source

feat : age label added to passengers

-API is not connected
feat : bills page UI added
main
sina_sajjadi 3 weeks ago
parent
commit
f401932d4c
  1. 2
      next.config.js
  2. 1
      src/app/(account-pages)/(components)/Nav.tsx
  3. 68
      src/app/(account-pages)/bills/BillCard.tsx
  4. 113
      src/app/(account-pages)/bills/[slug]/page.tsx
  5. 106
      src/app/(account-pages)/bills/page.tsx
  6. 48
      src/app/(car-listings)/SectionGridFilterCard.tsx
  7. 112
      src/app/(car-listings)/SectionGridHasMap.tsx
  8. 683
      src/app/(car-listings)/TabFilters.tsx
  9. 59
      src/app/(car-listings)/layout.tsx
  10. 14
      src/app/(car-listings)/listing-car-map/page.tsx
  11. 14
      src/app/(car-listings)/listing-car/page.tsx
  12. 1
      src/app/(client-components)/(Header)/SiteHeader.tsx
  13. 134
      src/app/(client-components)/(HeroSearchForm)/(car-search-form)/RentalCarDatesRangeInput.tsx
  14. 70
      src/app/(client-components)/(HeroSearchForm)/(car-search-form)/RentalCarSearchForm.tsx
  15. 119
      src/app/(client-components)/(HeroSearchForm)/(experiences-search-form)/ExperiencesDateSingleInput.tsx
  16. 27
      src/app/(client-components)/(HeroSearchForm)/(experiences-search-form)/ExperiencesSearchForm.tsx
  17. 152
      src/app/(client-components)/(HeroSearchForm)/(flight-search-form)/FlightDateRangeInput.tsx
  18. 246
      src/app/(client-components)/(HeroSearchForm)/(flight-search-form)/FlightSearchForm.tsx
  19. 66
      src/app/(client-components)/(HeroSearchForm)/(real-estate-search-form)/HeroRealEstateSearchForm.tsx
  20. 137
      src/app/(client-components)/(HeroSearchForm)/(real-estate-search-form)/PriceRangeInput.tsx
  21. 126
      src/app/(client-components)/(HeroSearchForm)/(real-estate-search-form)/PropertyTypeSelect.tsx
  22. 25
      src/app/(client-components)/(HeroSearchForm)/(real-estate-search-form)/RealEstateSearchForm.tsx
  23. 71
      src/app/(client-components)/(HeroSearchForm)/GuestsInput.tsx
  24. 129
      src/app/(client-components)/(HeroSearchFormSmall)/(car-search-form)/RentalCarDatesRangeInput.tsx
  25. 72
      src/app/(client-components)/(HeroSearchFormSmall)/(car-search-form)/RentalCarSearchForm.tsx
  26. 114
      src/app/(client-components)/(HeroSearchFormSmall)/(experiences-search-form)/ExperiencesDateSingleInput.tsx
  27. 29
      src/app/(client-components)/(HeroSearchFormSmall)/(experiences-search-form)/ExperiencesSearchForm.tsx
  28. 148
      src/app/(client-components)/(HeroSearchFormSmall)/(flight-search-form)/FlightDateRangeInput.tsx
  29. 247
      src/app/(client-components)/(HeroSearchFormSmall)/(flight-search-form)/FlightSearchForm.tsx
  30. 51
      src/app/(experience-listings)/SectionGridFilterCard.tsx
  31. 113
      src/app/(experience-listings)/SectionGridHasMap.tsx
  32. 538
      src/app/(experience-listings)/TabFilters.tsx
  33. 56
      src/app/(experience-listings)/layout.tsx
  34. 14
      src/app/(experience-listings)/listing-experiences-map/page.tsx
  35. 14
      src/app/(experience-listings)/listing-experiences/page.tsx
  36. 195
      src/app/(home)/home-2/page.tsx
  37. 104
      src/app/(home)/home-3/page.tsx
  38. 55
      src/app/(listing-detail)/SectionDateRange.tsx
  39. 77
      src/app/(listing-detail)/layout.tsx
  40. 118
      src/app/(listing-detail)/listing-car-detail/RentalCarDatesRangeInput.tsx
  41. 68
      src/app/(listing-detail)/listing-car-detail/constant.ts
  42. 542
      src/app/(listing-detail)/listing-car-detail/page.tsx
  43. 122
      src/app/(listing-detail)/listing-experiences-detail/GuestsInput.tsx
  44. 109
      src/app/(listing-detail)/listing-experiences-detail/StayDatesRangeInput.tsx
  45. 39
      src/app/(listing-detail)/listing-experiences-detail/constant.ts
  46. 505
      src/app/(listing-detail)/listing-experiences-detail/page.tsx
  47. 122
      src/app/(listing-detail)/listing-stay-detail/GuestsInput.tsx
  48. 109
      src/app/(listing-detail)/listing-stay-detail/StayDatesRangeInput.tsx
  49. 71
      src/app/(listing-detail)/listing-stay-detail/constant.ts
  50. 637
      src/app/(listing-detail)/listing-stay-detail/page.tsx
  51. 4
      src/app/(real-estate-listings)/layout.tsx
  52. 45
      src/app/(server-components)/SectionHero2ArchivePage.tsx
  53. 281
      src/app/add-listing/[[...stepIndex]]/PageAddListing1.tsx
  54. 66
      src/app/add-listing/[[...stepIndex]]/page.tsx
  55. 2
      src/app/blog/SectionMagazine5.tsx
  56. 107
      src/app/custom-trip/page.tsx
  57. 89
      src/app/tours/[slug]/GuestsInput.tsx
  58. 8
      src/app/tours/[slug]/page.tsx
  59. 25
      src/app/tours/layout.tsx
  60. 75
      src/components/MobileFooterSticky.tsx
  61. 0
      src/components/ModalReserveMobile.tsx
  62. 28
      src/components/contexts/tourDetails.tsx

2
next.config.js

@ -13,7 +13,7 @@ module.exports = {
ignoreBuildErrors: true,
},
eslint: {
ignoreDuringBuilds: true,
ignoreDuringBuilds: false,
},
i18n,

1
src/app/(account-pages)/(components)/Nav.tsx

@ -12,6 +12,7 @@ export const Nav = () => {
"/account",
"/my-trips",
"/passengers-list",
"/bills",
];
return (

68
src/app/(account-pages)/bills/BillCard.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;

113
src/app/(account-pages)/bills/[slug]/page.tsx

@ -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;

106
src/app/(account-pages)/bills/page.tsx

@ -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;

48
src/app/(car-listings)/SectionGridFilterCard.tsx

@ -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;

112
src/app/(car-listings)/SectionGridHasMap.tsx

@ -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;

683
src/app/(car-listings)/TabFilters.tsx

@ -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"
>
&#8203;
</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"
>
&#8203;
</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;

59
src/app/(car-listings)/layout.tsx

@ -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;

14
src/app/(car-listings)/listing-car-map/page.tsx

@ -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;

14
src/app/(car-listings)/listing-car/page.tsx

@ -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
src/app/(client-components)/(Header)/SiteHeader.tsx

@ -28,7 +28,6 @@ let OPTIONS = {
let OBSERVER: IntersectionObserver | null = null;
const PAGES_HIDE_HEADER_BORDER: PathName[] = [
"/home-3",
"/listing-car-detail",
"/listing-experiences-detail",
"/listing-stay-detail",
];

134
src/app/(client-components)/(HeroSearchForm)/(car-search-form)/RentalCarDatesRangeInput.tsx

@ -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;

70
src/app/(client-components)/(HeroSearchForm)/(car-search-form)/RentalCarSearchForm.tsx

@ -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;

119
src/app/(client-components)/(HeroSearchForm)/(experiences-search-form)/ExperiencesDateSingleInput.tsx

@ -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;

27
src/app/(client-components)/(HeroSearchForm)/(experiences-search-form)/ExperiencesSearchForm.tsx

@ -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;

152
src/app/(client-components)/(HeroSearchForm)/(flight-search-form)/FlightDateRangeInput.tsx

@ -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;

246
src/app/(client-components)/(HeroSearchForm)/(flight-search-form)/FlightSearchForm.tsx

@ -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;

66
src/app/(client-components)/(HeroSearchForm)/(real-estate-search-form)/HeroRealEstateSearchForm.tsx

@ -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;

137
src/app/(client-components)/(HeroSearchForm)/(real-estate-search-form)/PriceRangeInput.tsx

@ -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;

126
src/app/(client-components)/(HeroSearchForm)/(real-estate-search-form)/PropertyTypeSelect.tsx

@ -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;

25
src/app/(client-components)/(HeroSearchForm)/(real-estate-search-form)/RealEstateSearchForm.tsx

@ -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;

71
src/app/(client-components)/(HeroSearchForm)/GuestsInput.tsx

@ -1,5 +1,4 @@
"use client"
"use client";
import React, { Fragment, useContext, useEffect, useState } from "react";
import { Popover, Transition } from "@headlessui/react";
@ -10,7 +9,7 @@ import ButtonSubmit from "./ButtonSubmit";
import { PathName } from "@/routers/types";
import { UserPlusIcon } from "@heroicons/react/24/outline";
import { GuestsObject } from "../type";
import { Context } from "@/components/contexts/tourDetails";
import { Context, useToursContext } from "@/components/contexts/tourDetails";
export interface GuestsInputProps {
fieldClassName?: string;
@ -26,22 +25,38 @@ const GuestsInput: FC<GuestsInputProps> = ({
hasButtonSubmit = true,
}) => {
const [guestAdultsInputValue, setGuestAdultsInputValue] = useState(0);
const { passengers, details , setPassengers } = useContext(Context);
const [guestChildrenInputValue, setGuestChildrenInputValue] = useState(0);
const [guestInfantsInputValue, setGuestInfantsInputValue] = useState(0);
const { details, setPassengers } = useToursContext()
const handleChangeData = (value: number, type: keyof GuestsObject) => {
let newValue = {
const newValue = {
guestAdults: guestAdultsInputValue,
guestChildren: guestChildrenInputValue,
guestInfants: guestInfantsInputValue,
};
setGuestAdultsInputValue(value);
newValue.guestAdults = value;
setPassengers(value)
if (type === "guestAdults") setGuestAdultsInputValue(value);
else if (type === "guestChildren") setGuestChildrenInputValue(value);
else if (type === "guestInfants") setGuestInfantsInputValue(value);
setPassengers({
guestAdults: newValue.guestAdults,
guestChildren: newValue.guestChildren,
guestInfants: newValue.guestInfants,
});
};
useEffect(()=>{
setPassengers(guestAdultsInputValue)
useEffect(() => {
setPassengers({
guestAdults: guestAdultsInputValue,
guestChildren: guestChildrenInputValue,
guestInfants: guestInfantsInputValue,
});
}, [guestAdultsInputValue, guestChildrenInputValue, guestInfantsInputValue]);
} , [])
const totalGuests = guestAdultsInputValue + guestChildrenInputValue + guestInfantsInputValue;
return (
<Popover className={`flex relative ${className}`}>
@ -60,27 +75,31 @@ const GuestsInput: FC<GuestsInputProps> = ({
</div>
<div className="flex-grow">
<span className="block xl:text-lg font-semibold">
{guestAdultsInputValue || ""} Passengers
{totalGuests || ""} Guests
</span>
<span className="block mt-1 text-sm text-neutral-400 leading-none font-light">
{guestAdultsInputValue ? "Passengers" : "Add passengers"}
{totalGuests ? "Guests" : "Add guests"}
</span>
</div>
{!!guestAdultsInputValue && open && (
{!!totalGuests && open && (
<ClearDataButton
onClick={() => {
setGuestAdultsInputValue(0);
setPassengers(0)
setGuestChildrenInputValue(0);
setGuestInfantsInputValue(0);
setPassengers({ guestAdults: 0, guestChildren: 0, guestInfants: 0 });
}}
/>
)}
</Popover.Button>
{/* BUTTON SUBMIT OF FORM */}
{hasButtonSubmit && (
<div className="pr-2 xl:pr-4">
<ButtonSubmit className={`${!details && "opacity-40 pointer-events-none" }`} href={`/tours/${details?.slug}-${details?.id}`} />
<ButtonSubmit
className={`${!details && "opacity-40 pointer-events-none"}`}
href={`/tours/${details?.slug}-${details?.id}`}
/>
</div>
)}
</div>
@ -107,6 +126,24 @@ const GuestsInput: FC<GuestsInputProps> = ({
label="Adults"
desc="Ages 13 or above"
/>
<NcInputNumber
className="w-full mt-6"
defaultValue={guestChildrenInputValue}
onChange={(value) => handleChangeData(value, "guestChildren")}
max={4}
min={0}
label="Children"
desc="Ages 2–12"
/>
<NcInputNumber
className="w-full mt-6"
defaultValue={guestInfantsInputValue}
onChange={(value) => handleChangeData(value, "guestInfants")}
max={4}
min={0}
label="Infants"
desc="Ages 0–2"
/>
</Popover.Panel>
</Transition>
</>

129
src/app/(client-components)/(HeroSearchFormSmall)/(car-search-form)/RentalCarDatesRangeInput.tsx

@ -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;

72
src/app/(client-components)/(HeroSearchFormSmall)/(car-search-form)/RentalCarSearchForm.tsx

@ -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;

114
src/app/(client-components)/(HeroSearchFormSmall)/(experiences-search-form)/ExperiencesDateSingleInput.tsx

@ -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;

29
src/app/(client-components)/(HeroSearchFormSmall)/(experiences-search-form)/ExperiencesSearchForm.tsx

@ -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;

148
src/app/(client-components)/(HeroSearchFormSmall)/(flight-search-form)/FlightDateRangeInput.tsx

@ -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;

247
src/app/(client-components)/(HeroSearchFormSmall)/(flight-search-form)/FlightSearchForm.tsx

@ -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;

51
src/app/(experience-listings)/SectionGridFilterCard.tsx

@ -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;

113
src/app/(experience-listings)/SectionGridHasMap.tsx

@ -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;

538
src/app/(experience-listings)/TabFilters.tsx

@ -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"
>
&#8203;
</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;

56
src/app/(experience-listings)/layout.tsx

@ -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;

14
src/app/(experience-listings)/listing-experiences-map/page.tsx

@ -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;

14
src/app/(experience-listings)/listing-experiences/page.tsx

@ -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;

195
src/app/(home)/home-2/page.tsx

@ -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;

104
src/app/(home)/home-3/page.tsx

@ -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;

55
src/app/(listing-detail)/SectionDateRange.tsx

@ -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;

77
src/app/(listing-detail)/layout.tsx

@ -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;

118
src/app/(listing-detail)/listing-car-detail/RentalCarDatesRangeInput.tsx

@ -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;

68
src/app/(listing-detail)/listing-car-detail/constant.ts

@ -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,
};
}
);

542
src/app/(listing-detail)/listing-car-detail/page.tsx

@ -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 youll 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;

122
src/app/(listing-detail)/listing-experiences-detail/GuestsInput.tsx

@ -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;

109
src/app/(listing-detail)/listing-experiences-detail/StayDatesRangeInput.tsx

@ -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;

39
src/app/(listing-detail)/listing-experiences-detail/constant.ts

@ -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,
};
}
);

505
src/app/(listing-detail)/listing-experiences-detail/page.tsx

@ -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;

122
src/app/(listing-detail)/listing-stay-detail/GuestsInput.tsx

@ -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;

109
src/app/(listing-detail)/listing-stay-detail/StayDatesRangeInput.tsx

@ -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;

71
src/app/(listing-detail)/listing-stay-detail/constant.ts

@ -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,
};
}
);

637
src/app/(listing-detail)/listing-stay-detail/page.tsx

@ -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"
>
&#8203;
</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;

4
src/app/(real-estate-listings)/layout.tsx

@ -3,14 +3,10 @@ import SectionGridAuthorBox from "@/components/SectionGridAuthorBox";
import SectionSliderNewCategories from "@/components/SectionSliderNewCategories";
import SectionSubscribe2 from "@/components/SectionSubscribe2";
import React, { ReactNode } from "react";
import SectionHero2ArchivePage from "../(server-components)/SectionHero2ArchivePage";
const Layout = ({ children }: { children: ReactNode }) => {
return (
<div className="nc-ListingRealEstateMapPage">
<div className="container pb-24 lg:pb-28">
<SectionHero2ArchivePage />
</div>
{children}

45
src/app/(server-components)/SectionHero2ArchivePage.tsx

@ -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;

281
src/app/add-listing/[[...stepIndex]]/PageAddListing1.tsx

@ -1,17 +1,16 @@
import React, { FC, useEffect, useState } from "react";
import Input from "@/shared/Input";
import Select from "@/shared/Select";
import FormItem from "../FormItem";
import { IoPersonAddOutline } from "react-icons/io5";
import getImageURL from "@/components/api/getImageURL";
import ConfirmModal from "../../../shared/popUp";
import axiosInstance from "@/components/api/axios";
import PassengerTable from "@/app/(account-pages)/passengers-list/PassengerTable";
import { useUserContext } from "@/components/contexts/userContext";
import { useRouter } from "next/navigation";
export interface PageAddListing1Props {
ageLabel : string ;
Passenger: any;
setNewPassenger: (passenger: any) => void;
errors: any;
@ -22,26 +21,25 @@ export interface PageAddListing1Props {
}
const PageAddListing1: FC<PageAddListing1Props> = ({
ageLabel ,
Passenger,
setNewPassenger,
errors,
passengerID,
setPassengerID,
selectedPassenger,
setSelectedPassenger, // Receive the function to reset selectedPassenger
setSelectedPassenger,
}) => {
const router = useRouter()
const { fullName, date, number, passport } = Passenger;
const { user } = useUserContext()
const router = useRouter();
const { user } = useUserContext();
const [savedPassengers, setSavedPassengers] = useState([]);
const [loading, setLoading] = useState(false);
const [dateError , setDateError] = useState(false)
const [passengerAgeLable , setPassengerAgeLAble] = useState(ageLabel)
if(!Object.keys(user).length){
router.replace("/signup")
if (!Object.keys(user).length) {
router.replace("/signup");
}
useEffect(() => {
@ -59,135 +57,208 @@ const PageAddListing1: FC<PageAddListing1Props> = ({
console.error(error);
});
}, []);
console.log(savedPassengers);
const handleFileChange = async (e: React.ChangeEvent<HTMLInputElement>) => {
setLoading(true)
setLoading(true);
const file = e.target.files?.[0];
if (file) {
const uploadedFile = await getImageURL(file);
setNewPassenger((prev: any) => ({ ...prev, passport_image: uploadedFile.url }));
setLoading(false)
setNewPassenger((prev: any) => ({
...prev,
passport_image: uploadedFile.url,
}));
setLoading(false);
}
};
const AddPassengerId = (passenger: {id : string | number}) => {
const AddPassengerId = (passenger: { id: string | number }) => {
setSelectedPassenger(passenger);
console.log(passenger.id);
setPassengerID((prev : any) => {
setPassengerID((prev: any) => {
if (!Array.isArray(prev)) {
prev = [];
}
const exists = prev.some((p: { passenger_id: any; }) => p.passenger_id === passenger.id);
const exists = prev.some(
(p: { passenger_id: any }) => p.passenger_id === passenger.id
);
if (exists) {
return prev;
}
return [...prev, { passenger_id: passenger.id }];
});
};
const INFANT_AGE = 2;
const CHILD_AGE = 12;
const calculateAgeCategory = (birthdate: string): string => {
const birthDate = new Date(birthdate);
const today = new Date();
let age = today.getFullYear() - birthDate.getFullYear();
const monthDifference = today.getMonth() - birthDate.getMonth();
if (
monthDifference < 0 ||
(monthDifference === 0 && today.getDate() < birthDate.getDate())
) {
age--;
}
if (age <= INFANT_AGE) {
setPassengerAgeLAble("Infant")
return "Infant";
} else if (age <= CHILD_AGE) {
setPassengerAgeLAble("Child")
return "Child";
} else {
setPassengerAgeLAble("Adult")
return "Adult";
}
};
// Handle change for birthdate and validate against ageLabel
const handleBirthdateChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const newBirthdate = e.target.value;
const derivedAgeCategory = calculateAgeCategory(newBirthdate);
const isDateMatchingAgeLabel = derivedAgeCategory === ageLabel;
return (
!selectedPassenger ? (
<>
<h2 className="text-2xl font-semibold">Choosing listing categories</h2>
<div className="w-14 border-b border-neutral-200 dark:border-neutral-700"></div>
{/* FORM */}
<ConfirmModal
lable={
<div className="flex items-center space-x-2 text-orange-500 cursor-pointer hover:text-orange-600">
<IoPersonAddOutline className="text-xl" /> {/* Adjust icon size */}
<p className="text-sm font-medium">Add from passenger list</p>
</div>
}
>
{(closeModal: () => void) => (
<ul className="space-y-2 p-3">
{savedPassengers?.map((item : {fullname : string , id : string | number}, index) => (
<li onClick={() => { AddPassengerId(item); closeModal(); }}
setDateError(!isDateMatchingAgeLabel);
setNewPassenger((prev: any) => ({
...prev,
birthdate: newBirthdate,
}));
};
return !selectedPassenger ? (
<>
<h2 className="text-2xl font-semibold">Choosing listing categories</h2>
<div className="w-14 border-b border-neutral-200 dark:border-neutral-700"></div>
{/* FORM */}
<ConfirmModal
lable={
<div className="flex items-center space-x-2 text-orange-500 cursor-pointer hover:text-orange-600">
<IoPersonAddOutline className="text-xl" />
<p className="text-sm font-medium">Add from passenger list</p>
</div>
}
>
{(closeModal: () => void) => (
<ul className="space-y-2 p-3">
{savedPassengers?.map(
(item: { fullname: string; id: string | number }, index) => (
<li
onClick={() => {
AddPassengerId(item);
closeModal();
}}
key={index}
className={`cursor-pointer rounded-md p-3 hover:bg-bronze bg-white border-2 dark:bg-gray-700 text-neutral-900 dark:text-white`}
>
<h3 className="text-lg font-semibold">{item.fullname}</h3>
</li>
))}
</ul>
)
)}
</ul>
)}
</ConfirmModal>
<div className="space-y-8">
{/* Full Name Field */}
<FormItem label="Full Name" desc="">
<Input
onChange={(e) =>
setNewPassenger((prev: any) => ({
...prev,
fullname: e.target.value,
}))
}
value={Passenger.fullname || ""}
placeholder="Full Name"
/>
{errors.fullname && (
<p className="text-xs text-red-500"> {errors.fullname} </p>
)}
</ConfirmModal>
</FormItem>
<div className="space-y-8">
{/* ITEM */}
{/* Passport Number Field */}
<FormItem label="Passport Number" desc="">
<Input
type="number"
className="[appearance:textfield] [&::-webkit-outer-spin-button]:appearance-none [&::-webkit-inner-spin-button]:appearance-none"
onChange={(e) =>
setNewPassenger((prev: any) => ({
...prev,
passport_number: e.target.value,
}))
}
value={Passenger.passport_number || ""}
placeholder="Passport Number"
/>
{errors.passport_number && (
<p className="text-xs text-red-500"> {errors.passport_number} </p>
)}
</FormItem>
<FormItem label="Full Name" desc="">
<Input
onChange={(e) =>
setNewPassenger((prev: any) => ({ ...prev, fullname: e.target.value }))
}
value={fullName}
placeholder="Full Name"
/>
{errors.fullName && (
<p className="text-xs text-red-500"> {errors.fullName} </p>
)}
</FormItem>
<FormItem label="Passport Number" desc="">
<Input
type="number"
className="[appearance:textfield] [&::-webkit-outer-spin-button]:appearance-none [&::-webkit-inner-spin-button]:appearance-none"
onChange={(e) =>
setNewPassenger((prev: any) => ({ ...prev, passport_number: e.target.value }))
}
value={passport}
placeholder="Passport Number"
/>
{errors.passport && (
<p className="text-xs text-red-500"> {errors.passport} </p>
)}
</FormItem>
<FormItem label="Date of Birth" desc="">
{/* Date of Birth Field */}
<FormItem label="Date of Birth" desc="">
<div className="relative">
<Input
className={`rounded-lg border ${dateError ? "border-red-500" : ""} pr-20`}
type="date"
value={date}
onChange={(e) =>
setNewPassenger((prev: any) => ({ ...prev, birthdate: e.target.value }))
}
value={Passenger.birthdate || ""}
onChange={handleBirthdateChange}
placeholder="Date of Birth"
/>
{errors.date && (
<p className="text-xs text-red-500"> {errors.date} </p>
)}
</FormItem>
<FormItem label="Phone Number" desc="">
<Input
type="number"
className="[appearance:textfield] [&::-webkit-outer-spin-button]:appearance-none [&::-webkit-inner-spin-button]:appearance-none"
value={number}
onChange={(e) =>
setNewPassenger((prev: any) => ({ ...prev, phone_number: e.target.value }))
}
placeholder="Phone Number"
/>
{errors.number && (
<p className="text-xs text-red-500"> {errors.number} </p>
{ageLabel && (
<span className={`absolute right-2 top-2 px-2 py-1 rounded-md text-sm ${dateError ?"bg-red-100 text-red-600" : "bg-green-100 text-green-600"} `}>
{passengerAgeLable}
</span>
)}
</FormItem>
<FormItem label="Upload Passport image Here" desc="">
<Input
</div>
{errors.birthdate && (
<p className="text-xs text-red-500"> {errors.birthdate} </p>
)}
{/* {dateError && (
<p className="text-xs text-red-500">Date does not match the age category</p>
)} */}
</FormItem>
type="file"
onChange={handleFileChange}
placeholder="Passport"
/>
{loading && <p>loading ...</p>}
{errors.image && (
<p className="text-xs text-red-500"> {errors.image} </p>
)}
</FormItem>
</div>
</>
) : (
<p> {selectedPassenger.fullname}</p>
)
{/* Phone Number Field */}
<FormItem label="Phone Number" desc="">
<Input
type="number"
className="[appearance:textfield] [&::-webkit-outer-spin-button]:appearance-none [&::-webkit-inner-spin-button]:appearance-none"
value={Passenger.phone_number || ""}
onChange={(e) =>
setNewPassenger((prev: any) => ({
...prev,
phone_number: e.target.value,
}))
}
placeholder="Phone Number"
/>
{errors.phone_number && (
<p className="text-xs text-red-500"> {errors.phone_number} </p>
)}
</FormItem>
{/* Passport Image Field */}
<FormItem label="Upload Passport image Here" desc="">
<Input type="file" onChange={handleFileChange} placeholder="Passport" />
{loading && <p>loading ...</p>}
{errors.passport_image && (
<p className="text-xs text-red-500"> {errors.passport_image} </p>
)}
</FormItem>
</div>
</>
) : (
<p>{selectedPassenger.fullname}</p>
);
};
export default PageAddListing1;

66
src/app/add-listing/[[...stepIndex]]/page.tsx

@ -1,4 +1,4 @@
"use client"
"use client";
import React, { useContext, useState, useEffect } from "react";
import { FC } from "react";
import ButtonPrimary from "@/shared/ButtonPrimary";
@ -9,6 +9,7 @@ import { useRouter } from "next/navigation";
import axiosInstance from "@/components/api/axios";
import { useUserContext } from "@/components/contexts/userContext";
import { toast } from "react-toastify";
import ButtonSecondary from "@/shared/ButtonSecondary";
export interface CommonLayoutProps {
children: React.ReactNode;
@ -36,12 +37,23 @@ const CommonLayout: FC<CommonLayoutProps> = ({ params }) => {
const [redirecting, setRedirecting] = useState(false);
const tourID = params.stepIndex[0];
const totalPassengers = useContext(Context).passengers;
const context = useContext(Context);
if (!context) {
throw new Error("Context is not defined");
}
const { guestAdults, guestChildren, guestInfants } = context.passengers;
const totalPassengers = guestAdults + guestChildren + guestInfants
console.log(totalPassengers);
const nextHref = () => setIndex((prev) => prev + 1);
const backtHref = () => (index > 1 ? setIndex((prev) => prev - 1) : index);
const backHref = () => setIndex((prev) => prev - 1);
const nextBtnText = index === totalPassengers ? "Save Passengers" : "Continue";
const ageLabel = index > guestAdults ? index > guestAdults + guestChildren ?"Infant" : "Child" : "Adult"
useEffect(() => {
Object.values(errors).forEach((error) => {
toast.error(error, {
@ -51,6 +63,7 @@ const CommonLayout: FC<CommonLayoutProps> = ({ params }) => {
});
}, [errors]);
console.log(index , guestAdults);
const sendPassengers = async () => {
const dataToSend = {
tour_id: tourID,
@ -60,7 +73,6 @@ const CommonLayout: FC<CommonLayoutProps> = ({ params }) => {
},
};
console.log("Data being sent:", dataToSend);
try {
const response = await axiosInstance.post(
@ -76,7 +88,7 @@ const CommonLayout: FC<CommonLayoutProps> = ({ params }) => {
console.log(response);
setRedirecting(true);
} catch (error) {
backtHref();
backHref();
console.error("Error submitting passengers:", error);
}
};
@ -110,8 +122,19 @@ const CommonLayout: FC<CommonLayoutProps> = ({ params }) => {
return;
}
// Add new passenger
setPassengers((prevPassengers) => [...prevPassengers, newPassenger]);
if (index <= passengers.length) {
// Update existing passenger
setPassengers((prevPassengers) => {
const updatedPassengers = [...prevPassengers];
updatedPassengers[index - 1] = newPassenger;
return updatedPassengers;
});
} else {
// Add new passenger
setPassengers((prevPassengers) => [...prevPassengers, newPassenger]);
}
// Reset newPassenger
setNewPassenger({
fullname: "",
passport_number: "",
@ -124,23 +147,47 @@ const CommonLayout: FC<CommonLayoutProps> = ({ params }) => {
nextHref();
};
const backHandler = () => {
if (index > 1) {
backHref();
if (selectedPassenger) {
// Remove the last selected passenger ID
setPassengerID((prev) => prev.slice(0, -1));
setSelectedPassenger(null);
} else {
// Set newPassenger to the data of the last passenger
const lastPassenger = passengers[index - 2]; // Adjust index for zero-based array
setNewPassenger(lastPassenger);
// Remove the last passenger from the passengers array
setPassengers((prevPassengers) => prevPassengers.slice(0, -1));
}
}
};
if (redirecting) {
return null;
}
return (
<div className={`nc-PageAddListing1 px-4 max-w-3xl mx-auto pb-24 pt-14 sm:py-24 lg:pb-32`}>
<div
className={`nc-PageAddListing1 px-4 max-w-3xl mx-auto pb-24 pt-14 sm:py-24 lg:pb-32`}
>
<div className="space-y-11">
<div>
<span className="text-4xl font-semibold">{index}</span>{" "}
<span className="text-lg text-neutral-500 dark:text-neutral-400">
/ {totalPassengers}
</span>
<span className="text-base text-neutral-500 dark:text-neutral-400">
&nbsp; {ageLabel}
</span>
</div>
{/* Passenger Form */}
<div className="listingSection__wrap">
<PageAddListing1
ageLabel = {ageLabel}
Passenger={newPassenger}
setNewPassenger={setNewPassenger}
errors={errors}
@ -151,7 +198,8 @@ const CommonLayout: FC<CommonLayoutProps> = ({ params }) => {
/>
</div>
<div className="flex justify-end space-x-5">
<div className="flex justify-between space-x-5">
<ButtonSecondary onClick={backHandler}>Back</ButtonSecondary>
<ButtonPrimary onClick={nextHandler}>{nextBtnText}</ButtonPrimary>
</div>
</div>

2
src/app/blog/SectionMagazine5.tsx

@ -1,7 +1,6 @@
"use client"
import React, { FC, useEffect, useState } from "react";
import { PostDataType } from "@/data/types";
import Card12 from "./Card12";
import Card13 from "./Card13";
import axiosInstance from "@/components/api/axios";
@ -26,7 +25,6 @@ const SectionMagazine5 = () => {
<div className="nc-SectionMagazine5">
<div className="grid lg:grid-cols-2 gap-6 md:gap-8">
<p>{t("welcome")}</p>
{posts[0] && <Card12 post={posts[0]} />}
<div className="grid gap-6 md:gap-8">
{posts

107
src/app/custom-trip/page.tsx

@ -9,6 +9,7 @@ import Input from "@/shared/Input";
import { useRouter } from "next/navigation";
import { useUserContext } from "@/components/contexts/userContext";
import { toast } from "react-toastify";
import NcInputNumber from "@/components/NcInputNumber";
interface City {
name: string;
@ -26,15 +27,12 @@ const CommonLayout: FC<CommonLayoutProps> = () => {
const { user } = useUserContext();
const router = useRouter();
useEffect(() => {
if (!Object.keys(user).length) {
router.replace("/signup");
}
}, [user, router]);
const [countries, setCountries] = useState<Country[]>([]);
const [startCity, setStartCity] = useState<string>("");
const [startDate, setStartDate] = useState<string>("");
const [guestAdultsInputValue, setGuestAdultsInputValue] = useState(1);
const [guestChildrenInputValue, setGuestChildrenInputValue] = useState(0);
const [guestInfantsInputValue, setGuestInfantsInputValue] = useState(0);
const [passengers, setPassengers] = useState<number>(1);
const [transport, setTransport] = useState<any[]>([]);
const [hotel, setHotel] = useState<any[]>([]);
@ -224,6 +222,22 @@ const CommonLayout: FC<CommonLayoutProps> = () => {
validateForm();
};
const handleChangeData = (value: number, type: string) => {
if (type === "guestAdults") {
setGuestAdultsInputValue(value);
} else if (type === "guestChildren") {
setGuestChildrenInputValue(value);
} else if (type === "guestInfants") {
setGuestInfantsInputValue(value);
}
};
useEffect(() => {
setPassengers(
guestAdultsInputValue + guestChildrenInputValue + guestInfantsInputValue
);
}, [guestAdultsInputValue, guestChildrenInputValue, guestInfantsInputValue]);
const submitTour = async () => {
const formData = {
destinations: destinations.map((destination, index) => ({
@ -241,25 +255,28 @@ const CommonLayout: FC<CommonLayoutProps> = () => {
number_passenger: `${passengers}`,
start_date: startDate.replace(/-/g, "/"),
},
...destinations.reduce<{ [key: number]: any }>((acc, destination, index) => {
acc[index + 2] = {
title: `${stringifyNumber(index + 1)} Destination`,
city: `${destination.endCity
.split("/")[0]
.trim()}-${destination.endCity.split("/")[1].trim()} `,
transportation: destination.transport,
hotel: destination.hotel,
duration: `${destination.duration} day`,
finish_date: destination.finishDate.replace(/-/g, "/"),
start_date: startDate.replace(/-/g, "/"),
};
return acc;
}, {}),
...destinations.reduce<{ [key: number]: any }>(
(acc, destination, index) => {
acc[index + 2] = {
title: `${stringifyNumber(index + 1)} Destination`,
city: `${destination.endCity
.split("/")[0]
.trim()}-${destination.endCity.split("/")[1].trim()} `,
transportation: destination.transport,
hotel: destination.hotel,
duration: `${destination.duration} day`,
finish_date: destination.finishDate.replace(/-/g, "/"),
start_date: startDate.replace(/-/g, "/"),
};
return acc;
},
{}
),
summary: {
cost_estimate: estimatedCost.toString(),
journery_schedule: `${startDate.replace(/-/g, "/")} To ${destinations[
destinations.length - 1
]?.finishDate.replace(/-/g, "/")} Day ${destinations.length}`,
journery_schedule: `${startDate.replace(/-/g, "/")} To ${
destinations[destinations.length - 1]?.finishDate.replace(/-/g, "/")
} Day ${destinations.length}`,
},
}),
};
@ -313,25 +330,45 @@ const CommonLayout: FC<CommonLayoutProps> = () => {
)}
</Select>
</FormItem>
<div className="flex w-[100%]">
<FormItem className="w-[50%]" label="Start Date">
<FormItem label="Start Date">
<Input
type="date"
value={startDate}
onChange={(e) => setStartDate(e.target.value)}
/>
</FormItem>
<FormItem className="w-[50%] ml-4" label="Number Of Passengers">
<Input
type="number"
value={passengers}
onChange={(e) =>
setPassengers(Math.max(1, Number(e.target.value)))
}
min="1"
/>
<FormItem className=" ml-4" label="Number Of Passengers">
<div className={`flex flex-col relative p-5`}>
<NcInputNumber
className="w-full"
defaultValue={guestAdultsInputValue}
onChange={(value) => handleChangeData(value, "guestAdults")}
max={20}
label="Adults"
desc="Ages 13 or above"
/>
<NcInputNumber
className="w-full mt-6"
defaultValue={guestChildrenInputValue}
onChange={(value) =>
handleChangeData(value, "guestChildren")
}
max={20}
label="Children"
desc="Ages 2–12"
/>
<NcInputNumber
className="w-full mt-6"
defaultValue={guestInfantsInputValue}
onChange={(value) => handleChangeData(value, "guestInfants")}
max={20}
label="Infants"
desc="Ages 0–2"
/>
</div>
</FormItem>
</div>
</div>
<div className="w-14 border-b border-neutral-200 dark:border-neutral-700"></div>

89
src/app/tours/[slug]/GuestsInput.tsx

@ -1,33 +1,62 @@
"use client";
import React, { Fragment, FC, useState, useContext } from "react";
import React, { Fragment, FC, useState, useContext, useEffect } 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";
import { Context, useToursContext } from "@/components/contexts/tourDetails";
import { useToursContext } from "@/components/contexts/tourDetails";
export interface GuestsInputProps {
className?: string;
footer?: boolean;
}
const GuestsInput: FC<GuestsInputProps> = ({ className = "flex-1" }) => {
const { setPassengers, passengers } = useToursContext();
const GuestsInput: FC<GuestsInputProps> = ({
className = "flex-1",
footer = false,
}) => {
const { passengers, setPassengers } = useToursContext();
const [guestAdultsInputValue, setGuestAdultsInputValue] =
useState(passengers);
const [guestAdultsInputValue, setGuestAdultsInputValue] = useState(0);
const [guestChildrenInputValue, setGuestChildrenInputValue] = useState(0);
const [guestInfantsInputValue, setGuestInfantsInputValue] = useState(0);
const handleChangeData = (value: number) => {
let newValue = {
const handleChangeData = (value: number, type: keyof GuestsObject) => {
const newValue = {
guestAdults: guestAdultsInputValue,
guestChildren: guestChildrenInputValue,
guestInfants: guestInfantsInputValue,
};
setGuestAdultsInputValue(value);
newValue.guestAdults = value;
setPassengers(value);
if (type === "guestAdults") setGuestAdultsInputValue(value);
else if (type === "guestChildren") setGuestChildrenInputValue(value);
else if (type === "guestInfants") setGuestInfantsInputValue(value);
setPassengers({
guestAdults: newValue.guestAdults,
guestChildren: newValue.guestChildren,
guestInfants: newValue.guestInfants,
});
};
const totalGuests = guestAdultsInputValue;
useEffect(() => {
setGuestAdultsInputValue(passengers.guestAdults);
setGuestChildrenInputValue(passengers.guestChildren);
setGuestInfantsInputValue(passengers.guestInfants);
}, []);
useEffect(() => {
setPassengers({
guestAdults: guestAdultsInputValue,
guestChildren: guestChildrenInputValue,
guestInfants: guestInfantsInputValue,
});
}, [guestAdultsInputValue, guestChildrenInputValue, guestInfantsInputValue]);
const totalGuests =
guestAdultsInputValue + guestChildrenInputValue + guestInfantsInputValue;
return (
<Popover className={`flex relative ${className}`}>
@ -57,7 +86,13 @@ const GuestsInput: FC<GuestsInputProps> = ({ className = "flex-1" }) => {
<ClearDataButton
onClick={() => {
setGuestAdultsInputValue(0);
setPassengers(0);
setGuestChildrenInputValue(0);
setGuestInfantsInputValue(0);
setPassengers({
guestAdults: 0,
guestChildren: 0,
guestInfants: 0,
});
}}
/>
)}
@ -73,16 +108,38 @@ const GuestsInput: FC<GuestsInputProps> = ({ className = "flex-1" }) => {
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 ">
<Popover.Panel
className={`absolute z-10 w-full min-w-[340px] max-w-sm bg-white dark:bg-neutral-800 ${
footer ? "bottom-full mb-3" : "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)}
onChange={(value) => handleChangeData(value, "guestAdults")}
max={10}
min={1}
label="Passsengers"
label="Adults"
desc="Ages 13 or above"
/>
<NcInputNumber
className="w-full mt-6"
defaultValue={guestChildrenInputValue}
onChange={(value) => handleChangeData(value, "guestChildren")}
max={4}
min={0}
label="Children"
desc="Ages 2–12"
/>
<NcInputNumber
className="w-full mt-6"
defaultValue={guestInfantsInputValue}
onChange={(value) => handleChangeData(value, "guestInfants")}
max={4}
min={0}
label="Infants"
desc="Ages 0–2"
/>
</Popover.Panel>
</Transition>
</>

8
src/app/tours/[slug]/page.tsx

@ -9,7 +9,7 @@ import { useToursContext } from "@/components/contexts/tourDetails";
import ConfirmModal from "@/shared/popUp";
import StayDatesRangeInput from "./StayDatesRangeInput";
import GuestsInput from "./GuestsInput";
import MobileFooterSticky from "@/app/(listing-detail)/(components)/MobileFooterSticky";
import MobileFooterSticky from "@/components/MobileFooterSticky";
import ButtonPrimary from "@/shared/ButtonPrimary";
import StartRating from "@/components/StartRating";
@ -47,6 +47,8 @@ const ListingStayDetailPage: FC = () => {
const { slug } = useParams(); // Assuming `slug` contains the tour ID
const id = slug?.match(/-?(\d+)$/)?.[1] || null;
const totalGuests = passengers.guestAdults + passengers.guestChildren + passengers.guestInfants;
// Fetch details and itinerary on component mount
useEffect(() => {
@ -120,7 +122,7 @@ const ListingStayDetailPage: FC = () => {
const renderSidebar = () => {
const total = details?.final_price && passengers
? (Number(details?.final_price) * passengers).toLocaleString("en-US", { style: "currency", currency: "USD" })
? (Number(details?.final_price) * totalGuests).toLocaleString("en-US", { style: "currency", currency: "USD" })
: 0;
return (
@ -217,7 +219,7 @@ const ListingStayDetailPage: FC = () => {
</main>
{/* Mobile Footer */}
<MobileFooterSticky passengers={passengers} data={details} />
<MobileFooterSticky/>
</div>
);
};

25
src/app/tours/layout.tsx

@ -6,9 +6,6 @@ import SectionSliderNewCategories from "@/components/SectionSliderNewCategories"
import SectionSubscribe2 from "@/components/SectionSubscribe2";
import { usePathname, useRouter, useSearchParams } from "next/navigation";
import React, { ReactNode } from "react";
import MobileFooterSticky from "../(listing-detail)/(components)/MobileFooterSticky";
import { imageGallery as listingStayImageGallery } from "./[slug]/constant";
import { Route } from "next";
const DetailtLayout = ({ children }: { children: ReactNode }) => {
const router = useRouter();
@ -16,33 +13,11 @@ const DetailtLayout = ({ children }: { children: ReactNode }) => {
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>

75
src/app/(listing-detail)/(components)/MobileFooterSticky.tsx → src/components/MobileFooterSticky.tsx

@ -4,71 +4,68 @@ import ButtonPrimary from "@/shared/ButtonPrimary";
import converSelectedDateToString from "@/utils/converSelectedDateToString";
import ModalReserveMobile from "./ModalReserveMobile";
import { useParams } from "next/navigation";
import GuestsInput from "../listing-experiences-detail/GuestsInput";
import ClearDataButton from "@/app/(client-components)/(HeroSearchForm)/ClearDataButton";
import { UserPlusIcon } from "@heroicons/react/24/solid";
import { useToursContext } from "@/components/contexts/tourDetails";
import NcInputNumber from "@/components/NcInputNumber";
import GuestsInput from "@/app/tours/[slug]/GuestsInput";
const MobileFooterSticky = ({ data }) => {
const MobileFooterSticky = () => {
const [startDate, setStartDate] = useState<Date | null>(
new Date("2023/02/06")
);
const { setPassengers, passengers } = useToursContext();
const { setPassengers, passengers , details } = useToursContext();
const [guestAdultsInputValue, setGuestAdultsInputValue] =
useState(passengers);
const [guestAdultsInputValue, setGuestAdultsInputValue] = useState(
passengers.guestAdults || 0
);
const handleChangeData = (value: number) => {
let newValue = {
guestAdults: guestAdultsInputValue,
};
setGuestAdultsInputValue(value);
newValue.guestAdults = value;
setPassengers(value);
setPassengers((prev) => ({
...prev,
guestAdults: value,
}));
};
console.log(data);
const r = /-?(\d+)$/;
const id: string = useParams().slug.match(r)[1];
const { slug } = useParams();
const id = slug?.match(/-?(\d+)$/)?.[1] || "";
// Calculate total based on guestAdults and data.price
const totalGuests = guestAdultsInputValue;
const totalPrice = details?.price
? (Number(details.price) * totalGuests).toLocaleString("en-US", {
style: "currency",
currency: "USD",
})
: "N/A";
return (
<div className="block lg:hidden fixed bottom-0 inset-x-0 py-2 sm:py-3 bg-white dark:bg-neutral-800 border-t border-neutral-200 dark:border-neutral-6000 z-40">
<div className="block lg:hidden fixed bottom-0 inset-x-0 py-2 sm:py-3 bg-white dark:bg-neutral-800 border-t border-neutral-200 dark:border-neutral-600 z-40">
<div className="container flex items-center justify-between">
<div className="flex gap-4">
<div className="">
<NcInputNumber
className="w-full"
defaultValue={guestAdultsInputValue}
onChange={(value) => handleChangeData(value)}
max={10}
min={1}
label="Passsengers"
desc="Ages 13 or above"
/>
<GuestsInput footer ={true} className="flex-1" />
</div>
</div>
<div className="flex items-center gap-2">
<span className="block text-xl font-semibold">
{data?.price * passengers}
</span>
<span className="block text-xl font-semibold">{totalPrice}</span>
<ModalReserveMobile
renderChildren={({ openModal }) => (
<>
<ButtonPrimary
disabled={true}
className={`${
data?.status === "AVAILABLE" && passengers
? ""
: "opacity-60 pointer-events-none"
}`}
href={`/add-listing/${id}`}
>
Reserve
</ButtonPrimary>
</>
<ButtonPrimary
onClick={openModal}
disabled={details?.status !== "AVAILABLE" || totalGuests === 0}
className={`${
details?.status === "AVAILABLE" && totalGuests > 0
? ""
: "opacity-60 pointer-events-none"
}`}
href={`/add-listing/${id}`}
>
Reserve
</ButtonPrimary>
)}
/>
</div>

0
src/app/(listing-detail)/(components)/ModalReserveMobile.tsx → src/components/ModalReserveMobile.tsx

28
src/components/contexts/tourDetails.tsx

@ -3,6 +3,7 @@
import { usePathname } from "next/navigation";
import axiosInstance from "../api/axios";
import React, { createContext, useContext, useEffect, useState, ReactNode } from "react";
interface ImageURL {
sm: string;
md: string;
@ -57,16 +58,21 @@ interface Tour {
duration: string;
}
export const Context = createContext<ToursContextType | undefined>(undefined);
interface Passengers {
guestAdults: number;
guestChildren: number;
guestInfants: number;
}
interface ToursContextType {
details: TourDetails | undefined;
passengers: number;
passengers: Passengers;
getTourData: (item: number) => Promise<void>;
setPassengers: React.Dispatch<React.SetStateAction<number>>;
setPassengers: React.Dispatch<React.SetStateAction<Passengers>>;
setDetails: React.Dispatch<React.SetStateAction<TourDetails | undefined>>;
tours: {results : Tour[]};
tours: { results: Tour[] };
countries: Country[];
}
@ -76,8 +82,12 @@ interface ContextProviderProps {
export const ContextProvider = ({ children }: ContextProviderProps) => {
const [details, setDetails] = useState<TourDetails | undefined>(undefined);
const [passengers, setPassengers] = useState<number>(0);
const [tours, setTours] = useState<{ results: Tour[] }>({ results: [] }); // <-- Wrap in results
const [passengers, setPassengers] = useState<Passengers>({
guestAdults: 0,
guestChildren: 0,
guestInfants: 0,
});
const [tours, setTours] = useState<{ results: Tour[] }>({ results: [] });
const [countries, setCountries] = useState<Country[]>([]);
const path = usePathname();
@ -92,19 +102,17 @@ export const ContextProvider = ({ children }: ContextProviderProps) => {
});
}, []);
useEffect(() => {
axiosInstance
.get("/api/tours/")
.then((response) => {
setTours({ results: response.data.results }); // <-- Expect results
setTours({ results: response.data.results });
})
.catch((error) => {
console.error("Error fetching data:", error);
});
}, []);
useEffect(() => {
setDetails(undefined);
}, [path]);
@ -118,8 +126,6 @@ export const ContextProvider = ({ children }: ContextProviderProps) => {
}
};
console.log(tours);
return (
<Context.Provider
value={{

Loading…
Cancel
Save