|
|
@ -1,285 +1,96 @@ |
|
|
|
"use client"; |
|
|
|
|
|
|
|
import React, { FC, Fragment, useContext, useEffect, 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 React, { FC, useEffect, useState } from "react"; |
|
|
|
import { useParams, usePathname, useRouter } from "next/navigation"; |
|
|
|
import { Amenities_demos, PHOTOS } from "./constant"; |
|
|
|
import StayDatesRangeInput from "./StayDatesRangeInput"; |
|
|
|
import GuestsInput from "./GuestsInput"; |
|
|
|
import { Route } from "next"; |
|
|
|
import { Context } from "@/components/contexts/tourDetails"; |
|
|
|
import axiosInstance from "@/components/api/axios"; |
|
|
|
import Image from "next/image"; |
|
|
|
import { FaCheckCircle } from "react-icons/fa"; |
|
|
|
import axiosInstance from "@/components/api/axios"; |
|
|
|
import { useToursContext } from "@/components/contexts/tourDetails"; |
|
|
|
import ConfirmModal from "@/shared/popUp"; |
|
|
|
import { IoPersonAddOutline } from "react-icons/io5"; |
|
|
|
import StayDatesRangeInput from "./StayDatesRangeInput"; |
|
|
|
import GuestsInput from "./GuestsInput"; |
|
|
|
import MobileFooterSticky from "@/app/(listing-detail)/(components)/MobileFooterSticky"; |
|
|
|
import ButtonPrimary from "@/shared/ButtonPrimary"; |
|
|
|
import StartRating from "@/components/StartRating"; |
|
|
|
|
|
|
|
export interface ListingStayDetailPageProps {} |
|
|
|
|
|
|
|
const ListingStayDetailPage: FC<ListingStayDetailPageProps> = ({}) => { |
|
|
|
//
|
|
|
|
|
|
|
|
const { getTourData, details, passengers } = useContext(Context); |
|
|
|
const [iteneries, setIteneries] = useState(); |
|
|
|
const r = /-?(\d+)$/; |
|
|
|
const id: number = useParams().slug.match(r)[1]; |
|
|
|
const totalIteneries = iteneries?.length; |
|
|
|
|
|
|
|
console.log(details); |
|
|
|
|
|
|
|
useEffect(() => { |
|
|
|
getTourData(id); |
|
|
|
axiosInstance |
|
|
|
.get(`https://aqila.nwhco.ir/api/tours/${id}/itinerary/`) |
|
|
|
.then((response) => { |
|
|
|
console.log(response.data.results); |
|
|
|
setIteneries(response.data.results); |
|
|
|
}) |
|
|
|
.catch((error) => { |
|
|
|
console.error("Error fetching data:", error); |
|
|
|
}); |
|
|
|
}, []); |
|
|
|
|
|
|
|
let [isOpenModalAmenities, setIsOpenModalAmenities] = useState(false); |
|
|
|
|
|
|
|
const thisPathname = usePathname(); |
|
|
|
// Define the type of `details`
|
|
|
|
interface TourDetails { |
|
|
|
title: string; |
|
|
|
description: string; |
|
|
|
price: string | number; |
|
|
|
final_price: string | number | undefined; |
|
|
|
status: string; |
|
|
|
images: { |
|
|
|
image_url: { |
|
|
|
lg: string; |
|
|
|
}; |
|
|
|
}[]; |
|
|
|
tour_features?: { |
|
|
|
id: number; |
|
|
|
title: string; |
|
|
|
}[]; |
|
|
|
} |
|
|
|
|
|
|
|
// Define the type of itinerary
|
|
|
|
interface Itinerary { |
|
|
|
id: number; |
|
|
|
title: string; |
|
|
|
started_at: string; |
|
|
|
summary: string; |
|
|
|
} |
|
|
|
|
|
|
|
const ListingStayDetailPage: FC = () => { |
|
|
|
const { getTourData, details, passengers } = useToursContext(); |
|
|
|
const [itineraries, setItineraries] = useState<Itinerary[]>([]); |
|
|
|
const router = useRouter(); |
|
|
|
const path = usePathname(); |
|
|
|
const { slug } = useParams(); // Assuming `slug` contains the tour ID
|
|
|
|
|
|
|
|
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"> |
|
|
|
{details?.title} |
|
|
|
</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" /> |
|
|
|
const id = slug?.match(/-?(\d+)$/)?.[1] || null; |
|
|
|
|
|
|
|
{/* 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 = () => { |
|
|
|
// Fetch details and itinerary on component mount
|
|
|
|
useEffect(() => { |
|
|
|
if (id) { |
|
|
|
getTourData(parseInt(id, 10)); // Fetch tour details
|
|
|
|
axiosInstance |
|
|
|
.get(`/api/tours/${id}/itinerary/`) |
|
|
|
.then((response) => { |
|
|
|
setItineraries(response.data.results); |
|
|
|
}) |
|
|
|
.catch((error) => console.error("Error fetching itinerary:", error)); |
|
|
|
} |
|
|
|
}, [id]); |
|
|
|
|
|
|
|
const renderSectionDetails = () => { |
|
|
|
return ( |
|
|
|
<div className="listingSection__wrap"> |
|
|
|
<h2 className="text-3xl font-semibold">{details?.title}</h2> |
|
|
|
<div className="w-14 border-b border-neutral-200 dark:border-neutral-700"></div> |
|
|
|
<div className="text-neutral-6000 dark:text-neutral-300"> |
|
|
|
<div className="text-neutral-600 dark:text-neutral-300"> |
|
|
|
<span>{details?.description}</span> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
); |
|
|
|
}; |
|
|
|
|
|
|
|
const renderSection3 = () => { |
|
|
|
return ( |
|
|
|
<div className="listingSection__wrap"> |
|
|
|
<div> |
|
|
|
<h2 className="text-2xl font-semibold">Tour Features </h2> |
|
|
|
</div> |
|
|
|
<div className="w-14 border-b border-neutral-200 dark:border-neutral-700"></div> |
|
|
|
{/* 6 */} |
|
|
|
<div className="grid gap-6 text-sm text-neutral-700 dark:text-neutral-300 "> |
|
|
|
{details && |
|
|
|
details.tour_features?.map((item) => ( |
|
|
|
<div key={item.id} className="flex items-center space-x-3"> |
|
|
|
<FaCheckCircle className="w-5 text-green-500" /> |
|
|
|
<span>{item.title}</span> |
|
|
|
</div> |
|
|
|
))} |
|
|
|
</div> |
|
|
|
|
|
|
|
{/* <div className="w-14 border-b border-neutral-200"></div> |
|
|
|
<div> |
|
|
|
<ButtonSecondary onClick={openModalAmenities}> |
|
|
|
View more 20 amenities |
|
|
|
</ButtonSecondary> |
|
|
|
</div> |
|
|
|
{renderMotalAmenities()} */} |
|
|
|
</div> |
|
|
|
); |
|
|
|
}; |
|
|
|
|
|
|
|
// const renderMotalAmenities = () => {
|
|
|
|
// return (
|
|
|
|
// <Transition appear show={isOpenModalAmenities} as={Fragment}>
|
|
|
|
// <Dialog
|
|
|
|
// as="div"
|
|
|
|
// className="fixed inset-0 z-50 overflow-y-auto"
|
|
|
|
// onClose={closeModalAmenities}
|
|
|
|
// >
|
|
|
|
// <div className="min-h-screen px-4 text-center">
|
|
|
|
// <Transition.Child
|
|
|
|
// as={Fragment}
|
|
|
|
// enter="ease-out duration-300"
|
|
|
|
// enterFrom="opacity-0"
|
|
|
|
// enterTo="opacity-100"
|
|
|
|
// leave="ease-in duration-200"
|
|
|
|
// leaveFrom="opacity-100"
|
|
|
|
// leaveTo="opacity-0"
|
|
|
|
// >
|
|
|
|
// <Dialog.Overlay className="fixed inset-0 bg-black bg-opacity-40" />
|
|
|
|
// </Transition.Child>
|
|
|
|
|
|
|
|
// {/* This element is to trick the browser into centering the modal contents. */}
|
|
|
|
// <span
|
|
|
|
// className="inline-block h-screen align-middle"
|
|
|
|
// aria-hidden="true"
|
|
|
|
// >
|
|
|
|
// ​
|
|
|
|
// </span>
|
|
|
|
// <Transition.Child
|
|
|
|
// as={Fragment}
|
|
|
|
// enter="ease-out duration-300"
|
|
|
|
// enterFrom="opacity-0 scale-95"
|
|
|
|
// enterTo="opacity-100 scale-100"
|
|
|
|
// leave="ease-in duration-200"
|
|
|
|
// leaveFrom="opacity-100 scale-100"
|
|
|
|
// leaveTo="opacity-0 scale-95"
|
|
|
|
// >
|
|
|
|
// <div className="inline-block py-8 h-screen w-full max-w-4xl">
|
|
|
|
// <div className="inline-flex pb-2 flex-col w-full text-left align-middle transition-all transform overflow-hidden rounded-2xl bg-white dark:bg-neutral-900 dark:border dark:border-neutral-700 dark:text-neutral-100 shadow-xl h-full">
|
|
|
|
// <div className="relative flex-shrink-0 px-6 py-4 border-b border-neutral-200 dark:border-neutral-800 text-center">
|
|
|
|
// <h3
|
|
|
|
// className="text-lg font-medium leading-6 text-gray-900"
|
|
|
|
// id="headlessui-dialog-title-70"
|
|
|
|
// >
|
|
|
|
// Amenities
|
|
|
|
// </h3>
|
|
|
|
// <span className="absolute left-3 top-3">
|
|
|
|
// <ButtonClose onClick={closeModalAmenities} />
|
|
|
|
// </span>
|
|
|
|
// </div>
|
|
|
|
// <div className="px-8 overflow-auto text-neutral-700 dark:text-neutral-300 divide-y divide-neutral-200">
|
|
|
|
// {Amenities_demos.filter((_, i) => i < 1212).map((item) => (
|
|
|
|
// <div
|
|
|
|
// key={item.name}
|
|
|
|
// className="flex items-center py-2.5 sm:py-4 lg:py-5 space-x-5 lg:space-x-8"
|
|
|
|
// >
|
|
|
|
// <i
|
|
|
|
// className={`text-4xl text-neutral-6000 las ${item.icon}`}
|
|
|
|
// ></i>
|
|
|
|
// <span>{item.name}</span>
|
|
|
|
// </div>
|
|
|
|
// ))}
|
|
|
|
// </div>
|
|
|
|
// </div>
|
|
|
|
// </div>
|
|
|
|
// </Transition.Child>
|
|
|
|
// </div>
|
|
|
|
// </Dialog>
|
|
|
|
// </Transition>
|
|
|
|
// );
|
|
|
|
// };
|
|
|
|
|
|
|
|
const renderSection4 = () => { |
|
|
|
const renderItinerarySection = () => { |
|
|
|
return ( |
|
|
|
<div className="listingSection__wrap"> |
|
|
|
{/* HEADING */} |
|
|
|
<div> |
|
|
|
<h2 className="text-2xl font-semibold">Itinerary </h2> |
|
|
|
</div> |
|
|
|
<h2 className="text-2xl font-semibold">Itinerary</h2> |
|
|
|
<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"> |
|
|
|
{iteneries?.map((item, index) => ( |
|
|
|
<div key={index} className="flex"> |
|
|
|
<div className="text-sm sm:text-base text-neutral-600 dark:text-neutral-300 mb-4"> |
|
|
|
{itineraries.map((item, index) => ( |
|
|
|
<div key={item.id} className="flex"> |
|
|
|
<div className="mt-2"> |
|
|
|
<div className="dark:bg-black mx-4 transform -translate-x-1/2 w-8 h-8 bg-white border-secondery-bronze border-[3px] rounded-full"></div> |
|
|
|
{totalIteneries !== index + 1 && ( |
|
|
|
<div className=" mx-4 mt-[-2px] transform -translate-x-1/2 w-[1px] h-full border-secondery-bronze border-[2px]"></div> |
|
|
|
<div className="dark:bg-black mx-4 transform -translate-x-1/2 w-8 h-8 bg-white border-secondery-bronze border-[3px] rounded-full"></div> |
|
|
|
{itineraries.length !== index + 1 && ( |
|
|
|
<div className="mx-4 mt-[-2px] transform -translate-x-1/2 w-[1px] h-full border-secondery-bronze border-[2px]"></div> |
|
|
|
)} |
|
|
|
</div> |
|
|
|
<div className="p-5 rounded-3xl mb-4 border-2"> |
|
|
|
<h1 className="text-black font-bold mb-1 dark:text-white"> |
|
|
|
{item?.title} |
|
|
|
</h1> |
|
|
|
<span> |
|
|
|
{item?.started_at |
|
|
|
.replaceAll("-", " ") |
|
|
|
.replaceAll("T", " | ")} |
|
|
|
</span> |
|
|
|
<h1 className="text-black font-bold mb-1 dark:text-white">{item.title}</h1> |
|
|
|
<span>{item.started_at.replace(/T/, " | ")}</span> |
|
|
|
<div className="mt-3">{item.summary}</div> |
|
|
|
</div> |
|
|
|
</div> |
|
|
@ -290,259 +101,59 @@ const ListingStayDetailPage: FC<ListingStayDetailPageProps> = ({}) => { |
|
|
|
); |
|
|
|
}; |
|
|
|
|
|
|
|
const renderSection5 = () => { |
|
|
|
const renderTourFeatures = () => { |
|
|
|
return ( |
|
|
|
<div className="listingSection__wrap"> |
|
|
|
{/* HEADING */} |
|
|
|
<h2 className="text-2xl font-semibold">Host Information</h2> |
|
|
|
<h2 className="text-2xl font-semibold">Tour Features</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 className="grid gap-6 text-sm text-neutral-700 dark:text-neutral-300"> |
|
|
|
{details?.tour_features?.map((feature) => ( |
|
|
|
<div key={feature.id} className="flex items-center space-x-3"> |
|
|
|
<FaCheckCircle className="w-5 text-green-500" /> |
|
|
|
<span>{feature.title}</span> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
|
|
|
|
{/* desc */} |
|
|
|
<span className="block text-neutral-6000 dark:text-neutral-300"> |
|
|
|
{details?.description} |
|
|
|
</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=Iran+Mashhad+Imam Reza Holy Shrine" |
|
|
|
></iframe> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
); |
|
|
|
}; |
|
|
|
|
|
|
|
const renderSection8 = () => { |
|
|
|
return ( |
|
|
|
<div className="listingSection__wrap"> |
|
|
|
{/* HEADING */} |
|
|
|
<h2 className="text-2xl font-semibold">Travel guide and tips</h2> |
|
|
|
<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"> |
|
|
|
{details && |
|
|
|
details.travel_tips.map((item) => ( |
|
|
|
<li key={item.id}> |
|
|
|
<h4 className="dark:text-neutral-400 space-y-2"> |
|
|
|
{item.title} |
|
|
|
</h4> |
|
|
|
<p>{item.description}</p> |
|
|
|
</li> |
|
|
|
))} |
|
|
|
</ul> |
|
|
|
</div> |
|
|
|
))} |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
); |
|
|
|
}; |
|
|
|
|
|
|
|
const renderSidebar = () => { |
|
|
|
const total = details?.final_price && passengers |
|
|
|
? (Number(details?.final_price) * passengers).toLocaleString("en-US", { style: "currency", currency: "USD" }) |
|
|
|
: 0; |
|
|
|
|
|
|
|
return ( |
|
|
|
<div className="listingSectionSidebar__wrap shadow-xl"> |
|
|
|
{/* PRICE */} |
|
|
|
{/* Price display */} |
|
|
|
<div className="flex justify-between"> |
|
|
|
{+details?.price === +details?.final_price ? ( |
|
|
|
<span className="text-3xl font-semibold">{details?.price}</span> |
|
|
|
{details?.price === details?.final_price ? ( |
|
|
|
<span className="text-3xl font-semibold">${details?.price}</span> |
|
|
|
) : ( |
|
|
|
<div > |
|
|
|
<span className="mr-2 text-3xl font-semibold">{details?.final_price}</span> |
|
|
|
<span className="line-through">{details?.price}</span>{" "} |
|
|
|
<div> |
|
|
|
<span className="mr-2 text-3xl font-semibold">${details?.final_price}</span> |
|
|
|
<span className="line-through">${details?.price}</span> |
|
|
|
</div> |
|
|
|
)} |
|
|
|
|
|
|
|
<StartRating /> |
|
|
|
</div> |
|
|
|
|
|
|
|
{/* FORM */} |
|
|
|
<form className="flex flex-col border border-neutral-200 dark:border-neutral-700 rounded-3xl "> |
|
|
|
{/* Booking Form */} |
|
|
|
<form className="flex flex-col border border-neutral-200 dark:border-neutral-700 rounded-3xl"> |
|
|
|
<StayDatesRangeInput details={details} 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> |
|
|
|
{details?.final_price} x {passengers} passengers |
|
|
|
</span> |
|
|
|
<span> |
|
|
|
{" "} |
|
|
|
{isNaN(details?.final_price * passengers) |
|
|
|
? "N/A" // Or any fallback value, like "0" or a string message
|
|
|
|
: (details?.final_price * passengers).toString()} |
|
|
|
</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> |
|
|
|
{isNaN(details?.price * passengers) |
|
|
|
? "N/A" // Or any fallback value, like "0" or a string message
|
|
|
|
: (details?.price * passengers).toString()} |
|
|
|
</span> |
|
|
|
</div> |
|
|
|
{/* Total price */} |
|
|
|
<div className="flex justify-between font-semibold"> |
|
|
|
<span>Total</span> |
|
|
|
<span>{total}</span> |
|
|
|
</div> |
|
|
|
|
|
|
|
{/* SUBMIT */} |
|
|
|
{/* Reserve Button */} |
|
|
|
<ButtonPrimary |
|
|
|
className={`${ |
|
|
|
details?.status === "AVAILABLE" && passengers |
|
|
|
? "" |
|
|
|
: "opacity-60 pointer-events-none" |
|
|
|
}`}
|
|
|
|
className={details?.status === "AVAILABLE" ? "" : "opacity-60 pointer-events-none"} |
|
|
|
href={`/add-listing/${id}`} |
|
|
|
> |
|
|
|
Reserve |
|
|
@ -553,100 +164,59 @@ const ListingStayDetailPage: FC<ListingStayDetailPageProps> = ({}) => { |
|
|
|
|
|
|
|
return ( |
|
|
|
<div className="nc-ListingStayDetailPage"> |
|
|
|
{/* HEADER */} |
|
|
|
{/* Header Section with Images */} |
|
|
|
<header className="rounded-md sm:rounded-xl"> |
|
|
|
<div className="relative grid grid-cols-3 sm:grid-cols-4 gap-1 sm:gap-2"> |
|
|
|
{/* Main Image */} |
|
|
|
<div className="col-span-2 row-span-3 sm:row-span-2 relative rounded-md sm:rounded-xl overflow-hidden cursor-pointer"> |
|
|
|
{details && ( |
|
|
|
<Image |
|
|
|
fill |
|
|
|
className="object-cover rounded-md sm:rounded-xl" |
|
|
|
src={details?.images[0]?.image_url.lg} |
|
|
|
alt="" |
|
|
|
alt={details.title} |
|
|
|
sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 50vw" |
|
|
|
/> |
|
|
|
)} |
|
|
|
{/* <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.image_url.lg} |
|
|
|
alt="" |
|
|
|
sizes="400px" |
|
|
|
/> |
|
|
|
</div> */} |
|
|
|
<div className="absolute inset-0 bg-neutral-900 bg-opacity-20 opacity-0 hover:opacity-100 transition-opacity"></div> |
|
|
|
</div> |
|
|
|
{details?.images |
|
|
|
.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" : "" |
|
|
|
}`}
|
|
|
|
> |
|
|
|
<ConfirmModal |
|
|
|
lable={ |
|
|
|
<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.image_url.lg} |
|
|
|
alt="" |
|
|
|
sizes="400px" |
|
|
|
/> |
|
|
|
</div> |
|
|
|
} |
|
|
|
> |
|
|
|
{(closeModal) => ( |
|
|
|
<div className="h-[600px] w-[600px]"> |
|
|
|
<Image |
|
|
|
fill |
|
|
|
className="object-cover rounded-md sm:rounded-xl " |
|
|
|
src={item.image_url.lg} |
|
|
|
alt="" |
|
|
|
/> |
|
|
|
</div> |
|
|
|
)} |
|
|
|
</ConfirmModal> |
|
|
|
|
|
|
|
{/* OVERLAY */} |
|
|
|
</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> */} |
|
|
|
{/* Additional Images */} |
|
|
|
{details?.images.slice(1, 5).map((item, index) => ( |
|
|
|
<div key={index} className={`relative rounded-md sm:rounded-xl overflow-hidden ${index >= 3 ? "hidden sm:block" : ""}`}> |
|
|
|
<ConfirmModal |
|
|
|
lable={ |
|
|
|
<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.image_url.lg} alt="" sizes="400px" /> |
|
|
|
</div> |
|
|
|
} |
|
|
|
> |
|
|
|
{() => ( |
|
|
|
<div className="h-[600px] w-[600px]"> |
|
|
|
<Image fill className="object-cover rounded-md sm:rounded-xl" src={item.image_url.lg} alt="" /> |
|
|
|
</div> |
|
|
|
)} |
|
|
|
</ConfirmModal> |
|
|
|
</div> |
|
|
|
))} |
|
|
|
</div> |
|
|
|
</header> |
|
|
|
|
|
|
|
{/* MAIN */} |
|
|
|
<main className=" relative z-10 mt-11 flex flex-col lg:flex-row "> |
|
|
|
{/* CONTENT */} |
|
|
|
{/* Main content section */} |
|
|
|
<main className="relative z-10 mt-11 flex flex-col lg:flex-row"> |
|
|
|
{/* Left 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()} |
|
|
|
{renderSection8()} |
|
|
|
{renderSection4()} |
|
|
|
|
|
|
|
{/* {renderSection5()} */} |
|
|
|
{/* {renderSection6()} */} |
|
|
|
{/* {renderSection7()} */} |
|
|
|
{renderSectionDetails()} |
|
|
|
{renderTourFeatures()} |
|
|
|
{renderItinerarySection()} |
|
|
|
</div> |
|
|
|
|
|
|
|
{/* SIDEBAR */} |
|
|
|
{/* Right Sidebar */} |
|
|
|
<div className="hidden lg:block flex-grow mt-14 lg:mt-0"> |
|
|
|
<div className="sticky top-28">{renderSidebar()}</div> |
|
|
|
</div> |
|
|
|
</main> |
|
|
|
|
|
|
|
{/* Mobile Footer */} |
|
|
|
<MobileFooterSticky passengers={passengers} data={details} /> |
|
|
|
</div> |
|
|
|
); |
|
|
|