Browse Source

search bar bugs fixed

main
sina_sajjadi 2 months ago
parent
commit
50c972218b
  1. 8
      src/app/(account-pages)/my-trips/page.tsx
  2. 53
      src/app/(account-pages)/passengers-list/PassengerTable.tsx
  3. 144
      src/app/(account-pages)/passengers-list/[id]/page.tsx
  4. 13
      src/app/(account-pages)/passengers-list/page.tsx
  5. 79
      src/app/(client-components)/(Header)/SearchDropdown.tsx
  6. 22
      src/app/add-listing/[[...stepIndex]]/PageAddListing1.tsx
  7. 78
      src/app/custom-history/page.tsx
  8. 54
      src/app/custom-trip/page.tsx
  9. 2
      src/app/forgot-password/page.tsx
  10. 60
      src/app/signup/methodes/page.tsx
  11. 10
      src/app/signup/otp-code/page.tsx
  12. 4
      src/app/tours/[slug]/GuestsInput.tsx
  13. 41
      src/app/tours/[slug]/StayDatesRangeInput.tsx
  14. 672
      src/app/tours/[slug]/page.tsx
  15. 62
      src/components/SearchCard.tsx
  16. 1
      src/components/StayCard2.tsx
  17. 68
      src/components/contexts/tourDetails.tsx

8
src/app/(account-pages)/my-trips/page.tsx

@ -15,6 +15,12 @@ import axiosInstance from "@/components/api/axios";
import StayCard2 from "@/components/StayCard2";
import { useRouter } from "next/navigation";
import { useUserContext } from "@/components/contexts/userContext";
import { StayDataType } from "@/data/types";
// interface stay {
// id : string
// }
const MyTrips = () => {
const router = useRouter()
@ -57,7 +63,7 @@ const MyTrips = () => {
<Tab.Panels>
<Tab.Panel className="mt-8">
<div className="grid grid-cols-1 gap-6 md:gap-8 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4">
{tours.length ? (tours?.filter((_, i) => i < 8).map((stay) => (
{tours.length ? (tours?.filter((_, i) => i < 8).map((stay : StayDataType) => (
<StayCard2 key={stay.id} data={stay} />
))) : (<h1 className="text-2xl"> You have no trips </h1>)}
</div>

53
src/app/(account-pages)/passengers-list/PassengerTable.tsx

@ -2,31 +2,41 @@
import axiosInstance from "@/components/api/axios";
import { useUserContext } from "@/components/contexts/userContext";
import { useRouter } from "next/navigation";
import React, { useEffect, useState } from "react";
import React, { FC, useState } from "react";
import { IoMdTrash } from "react-icons/io";
import { MdEdit } from "react-icons/md";
const PassengerTable = ({ data }) => {
interface Passenger {
birthdate: string;
fullname: string;
id: number;
passport_image: string;
passport_number: string;
phone_number: string;
}
interface TableProps {
data: Passenger;
}
const PassengerTable: FC<TableProps> = ({ data }) => {
const { user } = useUserContext();
const router = useRouter()
const router = useRouter();
const [show, setShow] = useState(true);
const deletHandler = async () => {
const deleteHandler = async () => {
try {
const response = await axiosInstance.delete(
`/api/account/passengers/${data.id}/`,
{
headers: {
Authorization: `token ${user.token}`,
},
}
);
await axiosInstance.delete(`/api/account/passengers/${data.id}/`, {
headers: {
Authorization: `token ${user.token}`,
},
});
setShow(false);
} catch (error) {
console.log(error);
}
};
return (
<div
className={`${
@ -41,16 +51,25 @@ const PassengerTable = ({ data }) => {
<h3 className="text-lg font-semibold text-neutral-900 dark:text-white">
{data.fullname}
</h3>
{data.passport_number && (
<p className="text-sm text-neutral-500 dark:text-neutral-400">
Passport No: {data.passport_number}
</p>
)}
</div>
{/* Action Icons */}
<div className="flex space-x-6 text-neutral-500">
<button className="hover:text-red-500 transition-colors" type="submit">
<IoMdTrash onClick={deletHandler} className="text-2xl" />{" "}
{/* Increased size using text-2xl */}
<IoMdTrash onClick={deleteHandler} className="text-2xl" />
</button>
<button onClick={()=>{router.push(`/passengers-list/${data.id}`)}} className="hover:text-blue-500 transition-colors">
<MdEdit className="text-2xl" /> {/* Increased size using text-2xl */}
<button
onClick={() => {
router.push(`/passengers-list/${data.id}`);
}}
className="hover:text-blue-500 transition-colors"
>
<MdEdit className="text-2xl" />
</button>
</div>
</div>

144
src/app/(account-pages)/passengers-list/[id]/page.tsx

@ -122,7 +122,13 @@ const EditPassenger: FC<CommonLayoutProps> = ({ params }) => {
const handleSavePassenger = async () => {
if (!validateForm()) return;
const updatedFields: Partial<typeof passenger> = {};
const updatedFields: Partial<{
fullname: string;
passport_number: string;
birthdate: string;
phone_number: string;
passport_image: string;
}> = {};
// Only add fields that were changed
if (passenger.name !== originalPassenger.name) {
@ -169,86 +175,76 @@ const EditPassenger: FC<CommonLayoutProps> = ({ params }) => {
};
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">
<form>
<div className="listingSection__wrap">
<>
<h2 className="text-2xl font-semibold">Passenger Information</h2>
<div className="w-14 border-b border-neutral-200 dark:border-neutral-700"></div>
<div className="space-y-8">
<FormItem label="Full Name" desc="">
<Input
required
value={passenger.name}
onChange={(e) =>
setPassenger((prev) => ({
...prev,
name: e.target.value,
}))
}
placeholder="Full Name"
/>
</FormItem>
<h2 className="text-2xl font-semibold">Passenger Information</h2>
<div className="w-14 border-b border-neutral-200 dark:border-neutral-700"></div>
<div className="space-y-8">
<FormItem label="Full Name" desc="">
<Input
required
value={passenger.name}
onChange={(e) =>
setPassenger((prev) => ({ ...prev, name: e.target.value }))
}
placeholder="Full Name"
/>
</FormItem>
<FormItem label="Passport Number" desc="">
<Input
required
value={passenger.passport}
onChange={(e) =>
setPassenger((prev) => ({
...prev,
passport: e.target.value,
}))
}
type="text" // Changed from 'number' to 'text'
placeholder="Passport Number"
/>
</FormItem>
<FormItem label="Passport Number" desc="">
<Input
required
value={passenger.passport}
onChange={(e) =>
setPassenger((prev) => ({
...prev,
passport: e.target.value,
}))
}
type="text" // Changed from 'number' to 'text'
placeholder="Passport Number"
/>
</FormItem>
<FormItem label="Date of Birth" desc="">
<Input
required
value={passenger.date}
onChange={(e) =>
setPassenger((prev) => ({
...prev,
date: e.target.value,
}))
}
type="date"
placeholder="Date of Birth"
/>
</FormItem>
<FormItem label="Date of Birth" desc="">
<Input
required
value={passenger.date}
onChange={(e) =>
setPassenger((prev) => ({ ...prev, date: e.target.value }))
}
type="date"
placeholder="Date of Birth"
/>
</FormItem>
<FormItem label="Phone Number" desc="">
<Input
required
value={passenger.number} // Remove non-numeric characters
onChange={(e) =>
setPassenger((prev) => ({
...prev,
number: e.target.value.replace(/\D/g, ""), // Ensure only numeric input
}))
}
type="text" // Keep as 'text' to prevent unwanted behavior
placeholder="Phone Number"
/>
</FormItem>
<FormItem label="Phone Number" desc="">
<Input
required
value={passenger.number} // Remove non-numeric characters
onChange={(e) =>
setPassenger((prev) => ({
...prev,
number: e.target.value.replace(/\D/g, ""), // Ensure only numeric input
}))
}
type="text" // Keep as 'text' to prevent unwanted behavior
placeholder="Phone Number"
/>
</FormItem>
<FormItem label="Upload Passport Image Here" desc="">
<Input
required
onChange={handleFileChange}
type="file"
placeholder="Passport"
/>
{loading && <p>Loading ...</p>}
</FormItem>
</div>
</>
<FormItem label="Upload Passport Image Here" desc="">
<Input
required
onChange={handleFileChange}
type="file"
placeholder="Passport"
/>
{loading && <p>Loading ...</p>}
</FormItem>
</div>
</div>
<div className="flex justify-end space-x-5">

13
src/app/(account-pages)/passengers-list/page.tsx

@ -8,6 +8,17 @@ import Link from "next/link";
import { useUserContext } from "@/components/contexts/userContext";
import { useRouter } from "next/navigation";
interface data {
birthdate: string;
fullname: string;
id: number;
passport_image: string;
phone_number: string;
passport_number: string;
}
const PassengersList = () => {
const [passengers , setPassenger ] = useState([])
const {user} = useUserContext()
@ -44,7 +55,7 @@ useEffect(()=>{
</Link>
{/* Passenger Table */}
{passengers.map((item)=>(
{passengers.map((item : data)=>(
<PassengerTable key={item.id} data={item} />
))}

79
src/app/(client-components)/(Header)/SearchDropdown.tsx

@ -9,20 +9,39 @@ interface Props {
className?: string;
}
interface Tour {
id: number;
title: string;
description: string;
started_at: string;
ended_at: string;
status: string;
trip_status: string;
price: string;
image: {
id: number;
image_url: {
sm: string;
md: string;
lg: string;
};
};
destination_country: string;
}
const SearchDropdown: FC<Props> = ({ className = "" }) => {
const inputRef = React.createRef<HTMLInputElement>();
const [value, setValue] = useState("");
const [toursDetails, setToursDetail] = useState([]);
const { getTourData, tours } = useToursContext();
const [toursDetails, setToursDetail] = useState<Tour[]>([]);
const { tours } = useToursContext();
useEffect(() => {
// Fetch detailed tour data (including description) for all tours
tours?.results?.forEach((item) => {
axiosInstance
.get(`/api/tours/${item.id}/`)
.then((response) => {
setToursDetail((prev) => [...prev, { ...item, ...response.data }]); // Combine tour data with fetched detail
setToursDetail((prev) => [...prev, { ...item, ...response.data }]);
})
.catch((error) => {
console.log(error);
@ -30,18 +49,25 @@ const SearchDropdown: FC<Props> = ({ className = "" }) => {
});
}, [tours]);
// Filter tours based on title or description using the fetched tour details
const filterdTours = toursDetails?.filter((item) => {
return (
item.title.toLowerCase().includes(value.toLowerCase()) ||
item.description?.toLowerCase().includes(value.toLowerCase())
);
});
const filteredTours = toursDetails
?.filter((item) => {
return (
item.title.toLowerCase().includes(value.toLowerCase()) ||
item.description?.toLowerCase().includes(value.toLowerCase())
);
})
.reduce((acc: Tour[], current: Tour) => {
const duplicate = acc.find((tour) => tour.id === current.id);
if (!duplicate) {
acc.push(current);
}
return acc;
}, []);
return (
<React.Fragment>
<Popover className={`relative ${className}`}>
{({ open }) => {
{({ open, close }) => {
if (open) {
setTimeout(() => {
inputRef.current?.focus();
@ -77,16 +103,25 @@ const SearchDropdown: FC<Props> = ({ className = "" }) => {
rounded="rounded-full"
type="text"
placeholder="Type and search"
/>{value.replaceAll(" " , "") &&(
<Popover.Panel className="absolute right-0 z-10 top-full w-screen max-w-sm bg-white shadow-md rounded-3xl mt-1 ">
{filterdTours?.length ? (
filterdTours?.map((item) => (
<SearchCard key={item.id} data={item} />
))
) : (
<h3 className="p-4 text-center">No Matches Found</h3>
)}
</Popover.Panel>
/>
{value.replaceAll(" ", "") && (
<Popover.Panel className="absolute right-0 z-10 top-full w-screen max-w-sm bg-white shadow-md rounded-3xl mt-1">
{filteredTours?.length ? (
filteredTours?.map((item) => (
<div
key={item.id}
onClick={() => {
setValue("");
close();
}}
>
<SearchCard data={item} />
</div>
))
) : (
<h3 className="p-4 text-center">No Matches Found</h3>
)}
</Popover.Panel>
)}
</Popover.Panel>
</Transition>

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

@ -16,7 +16,7 @@ export interface PageAddListing1Props {
setNewPassenger: (passenger: any) => void;
errors: any;
passengerID: any[];
setPassengerID: (ids: any[]) => void;
setPassengerID: any
selectedPassenger: any; // Add prop for selectedPassenger
setSelectedPassenger: (passenger: any) => void; // Add prop for setting selectedPassenger
}
@ -66,19 +66,19 @@ const PageAddListing1: FC<PageAddListing1Props> = ({
const file = e.target.files?.[0];
if (file) {
const uploadedFile = await getImageURL(file);
setNewPassenger((prev) => ({ ...prev, passport_image: uploadedFile.url }));
setNewPassenger((prev: any) => ({ ...prev, passport_image: uploadedFile.url }));
setLoading(false)
}
};
const AddPassengerId = (passenger) => {
const AddPassengerId = (passenger: {id : string | number}) => {
setSelectedPassenger(passenger);
console.log(passenger.id);
setPassengerID((prev) => {
setPassengerID((prev : any) => {
if (!Array.isArray(prev)) {
prev = [];
}
const exists = prev.some((p) => p.passenger_id === passenger.id);
const exists = prev.some((p: { passenger_id: any; }) => p.passenger_id === passenger.id);
if (exists) {
return prev;
}
@ -100,9 +100,9 @@ const PageAddListing1: FC<PageAddListing1Props> = ({
</div>
}
>
{(closeModal) => (
{(closeModal: () => void) => (
<ul className="space-y-2 p-3">
{savedPassengers?.map((item, index) => (
{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`}
@ -120,7 +120,7 @@ const PageAddListing1: FC<PageAddListing1Props> = ({
<FormItem label="Full Name" desc="">
<Input
onChange={(e) =>
setNewPassenger((prev) => ({ ...prev, fullname: e.target.value }))
setNewPassenger((prev: any) => ({ ...prev, fullname: e.target.value }))
}
value={fullName}
placeholder="Full Name"
@ -134,7 +134,7 @@ const PageAddListing1: FC<PageAddListing1Props> = ({
type="number"
className="[appearance:textfield] [&::-webkit-outer-spin-button]:appearance-none [&::-webkit-inner-spin-button]:appearance-none"
onChange={(e) =>
setNewPassenger((prev) => ({ ...prev, passport_number: e.target.value }))
setNewPassenger((prev: any) => ({ ...prev, passport_number: e.target.value }))
}
value={passport}
placeholder="Passport Number"
@ -148,7 +148,7 @@ const PageAddListing1: FC<PageAddListing1Props> = ({
type="date"
value={date}
onChange={(e) =>
setNewPassenger((prev) => ({ ...prev, birthdate: e.target.value }))
setNewPassenger((prev: any) => ({ ...prev, birthdate: e.target.value }))
}
placeholder="Date of Birth"
/>
@ -162,7 +162,7 @@ const PageAddListing1: FC<PageAddListing1Props> = ({
className="[appearance:textfield] [&::-webkit-outer-spin-button]:appearance-none [&::-webkit-inner-spin-button]:appearance-none"
value={number}
onChange={(e) =>
setNewPassenger((prev) => ({ ...prev, phone_number: e.target.value }))
setNewPassenger((prev: any) => ({ ...prev, phone_number: e.target.value }))
}
placeholder="Phone Number"
/>

78
src/app/custom-history/page.tsx

@ -1,24 +1,22 @@
'use client';
import React, { FC, use, useEffect, useState } from 'react';
import React, { FC, useEffect, useState } from 'react';
import axiosInstance from '@/components/api/axios';
import { TrashIcon } from '@heroicons/react/24/outline'; // Import the Trash icon for the delete button
import { FaWhatsapp } from "react-icons/fa";
import { useUserContext } from '@/components/contexts/userContext';
import { useRouter } from 'next/navigation';
export interface PageAddListing10Props {}
const PageAddListing10: FC<PageAddListing10Props> = () => {
const router = useRouter()
const [tours, setTours] = useState([]);
const [toursDetail, setToursDetail] = useState([]);
const {user} = useUserContext();
const router = useRouter();
const [tours, setTours] = useState<any[]>([]); // Typed as any[] since the detail is parsed dynamically
const [toursDetail, setToursDetail] = useState<any[]>([]);
const { user } = useUserContext();
useEffect(() => {
if (user) {
axiosInstance('/api/trip/custom/orders/', {
headers: {
Authorization: `token ${user.token}`,
@ -26,31 +24,49 @@ const PageAddListing10: FC<PageAddListing10Props> = () => {
})
.then((response) => {
setTours(response.data.results);
console.log(response);
// Parse tour details
const details = response.data.results.map((item) =>
const details = response.data.results.map((item: any) =>
JSON.parse(item.detail)
);
setToursDetail(details);
})
.catch((error) => {
console.log(error);
console.error('Error fetching custom trip orders:', error);
});
}else{
router.replace("/signup")
} else {
router.replace("/signup");
}
}, []);
}, [user, router]);
const deleteTour = (tour)=>{
const selected = tours.find((item)=>{
return JSON.parse(item.detail) === tour
const deleteTour = (tourDetail: any) => {
// Find the selected tour that matches the tourDetail object
const selectedTour = tours.find((item) => {
return JSON.parse(item.detail).title === tourDetail.title;
});
if (selectedTour) {
axiosInstance
.delete(`/api/trip/custom/orders/${selectedTour.id}/`, {
headers: {
Authorization: `token ${user.token}`,
},
})
console.log(tour[1].title , tours)
}
console.log(tours);
.then((response) => {
// Remove the deleted tour from the state
setTours((prevTours) =>
prevTours.filter((tour) => tour.id !== selectedTour.id)
);
setToursDetail((prevDetails) =>
prevDetails.filter((detail) => detail !== tourDetail)
);
console.log(response);
})
.catch((error) => {
console.error('Error deleting the tour:', error);
});
}
};
return (
<div className="px-4 max-w-md mx-auto pb-24 pt-14 sm:py-24 lg:pb-32">
@ -64,7 +80,7 @@ console.log(tours);
className="bg-white border border-gray-200 rounded-3xl p-4 mb-4 shadow-md"
>
<div className="flex flex-col mb-2">
<div className='flex justify-between'>
<div className="flex justify-between">
<p className="font-semibold text-gray-700">Origin:</p>
<p className="text-gray-900">{item['1']?.city || 'N/A'}</p>
</div>
@ -72,7 +88,7 @@ console.log(tours);
{Object.keys(item)
.filter((key) => !isNaN(Number(key)) && Number(key) > 1)
.map((key) => (
<div className='flex justify-between' key={key}>
<div className="flex justify-between" key={key}>
<p className="font-semibold text-gray-700">{item[key].title}:</p>
<p className="text-gray-900">{item[key]?.city || 'N/A'}</p>
</div>
@ -81,11 +97,15 @@ console.log(tours);
<div className="flex items-center justify-between">
<div>
<p className=" text-gray-600">Estimated Cost: <span className="">${item.summary?.cost_estimate || 'N/A'}</span></p>
<p className="text-gray-600">
Estimated Cost: <span className="">${item.summary?.cost_estimate || 'N/A'}</span>
</p>
</div>
<button onClick={()=>{deleteTour(item)}} className="flex items-center text-red-500 hover:text-red-700">
<TrashIcon className="w-5 h-5 mr-1" />
<button
onClick={() => deleteTour(item)}
className="flex items-center text-red-500 hover:text-red-700"
>
<TrashIcon className="w-5 h-5 mr-1" />
Delete
</button>
</div>
@ -95,10 +115,10 @@ console.log(tours);
<p className="text-center text-gray-500">No tours available.</p>
)}
<div className="flex justify-between mt-8 items-center">
<div className="flex justify-between mt-8 items-center">
<p className="font-semibold text-gray-700 mb-2">Contact Support</p>
<button className="flex items-center justify-center bg-[#032935] rounded-full text-white p-3">
<FaWhatsapp className='w-9' />
<FaWhatsapp className="w-9" />
Whatsapp
</button>
</div>

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

@ -24,10 +24,8 @@ interface CommonLayoutProps {}
const CommonLayout: FC<CommonLayoutProps> = () => {
const { user } = useUserContext();
const router = useRouter();
useEffect(() => {
if (!Object.keys(user).length) {
router.replace("/signup");
@ -52,9 +50,10 @@ const CommonLayout: FC<CommonLayoutProps> = () => {
hotelCost: number;
}[]
>([]);
const [isFormValid, setIsFormValid] = useState<boolean>(false);
const [isFormValid, setIsFormValid] = useState(false);
const [isContinueValid, setIsContinueValid] = useState<boolean>(false);
var special = [
const special = [
"Zeroth",
"First",
"Second",
@ -76,7 +75,8 @@ const CommonLayout: FC<CommonLayoutProps> = () => {
"Eighteenth",
"Nineteenth",
];
var deca = [
const deca = [
"Twent",
"Thirt",
"Fort",
@ -87,7 +87,7 @@ const CommonLayout: FC<CommonLayoutProps> = () => {
"Ninet",
];
function stringifyNumber(n) {
function stringifyNumber(n: number) {
if (n < 20) return special[n];
if (n % 10 === 0) return deca[Math.floor(n / 10) - 2] + "ieth";
return deca[Math.floor(n / 10) - 2] + "y-" + special[n % 10];
@ -143,37 +143,28 @@ const CommonLayout: FC<CommonLayoutProps> = () => {
const validateForm = () => {
const isValid =
startCity &&
startDate &&
startCity !== "" &&
startDate !== "" &&
passengers > 0 &&
destinations.every(
(destination) =>
destination.endCity &&
destination.transport &&
destination.hotel &&
destination.endCity !== "" &&
destination.transport !== "" &&
destination.hotel !== "" &&
destination.duration > 0 &&
destination.finishDate
destination.finishDate !== ""
);
setIsFormValid(isValid);
};
// const validateContinue = () => {
// // Allow continue if startCity, startDate, and passengers are valid
// const isValid = destinations.length;
// console.log(isValid > 0);
// setIsContinueValid(isValid);
// };
useEffect(() => {
const isValid =
destinations.length > 0 &&
isFormValid &&
startCity &&
startDate &&
startCity !== "" &&
startDate !== "" &&
passengers > 0;
console.log(destinations.length, isFormValid, isValid);
setIsContinueValid(isValid);
}, [destinations, startCity, startDate, passengers, isFormValid]);
@ -231,9 +222,7 @@ const CommonLayout: FC<CommonLayoutProps> = () => {
setDestinations(updatedDestinations);
validateForm();
// validateContinue();
};
console.log(destinations);
const submitTour = async () => {
const formData = {
@ -252,7 +241,7 @@ const CommonLayout: FC<CommonLayoutProps> = () => {
number_passenger: `${passengers}`,
start_date: startDate.replace(/-/g, "/"),
},
...destinations.reduce((acc, destination, index) => {
...destinations.reduce<{ [key: number]: any }>((acc, destination, index) => {
acc[index + 2] = {
title: `${stringifyNumber(index + 1)} Destination`,
city: `${destination.endCity
@ -260,9 +249,9 @@ const CommonLayout: FC<CommonLayoutProps> = () => {
.trim()}-${destination.endCity.split("/")[1].trim()} `,
transportation: destination.transport,
hotel: destination.hotel,
duration: `${destination.duration}day`,
duration: `${destination.duration} day`,
finish_date: destination.finishDate.replace(/-/g, "/"),
start_date: startDate.replace(/-/g, "/"), // adjust as needed
start_date: startDate.replace(/-/g, "/"),
};
return acc;
}, {}),
@ -275,16 +264,13 @@ const CommonLayout: FC<CommonLayoutProps> = () => {
}),
};
try {
const response = await axiosInstance.post("/api/trip/custom/", formData, {
await axiosInstance.post("/api/trip/custom/", formData, {
headers: {
Authorization: `token ${user.token}`,
"Content-Type": "application/json",
"X-CSRFToken":
"HaqCxkS63ejsvwmlmk360sbFowGtwfNS06vGDYMIfWmHTWzJdod7x0zMEeC9gBSX",
Accept: "application/json",
},
});
toast.success("Successfully Registered")
toast.success("Successfully Registered");
router.push("/custom-history");
} catch (error) {
console.error("Error sending trip details:", error);

2
src/app/forgot-password/page.tsx

@ -25,7 +25,7 @@ const PageSignUp: FC<PageSignUpProps> = () => {
const [confirmPassword, setConfirmPassword] = useState('');
const [loading , setLoading] = useState(false)
const [errors, setErrors] = useState<{ phoneNumber?: string; password?: string; confirmPassword?: string }>({});
const [errors, setErrors] = useState<{ phoneNumber?: string; password?: string; confirmPassword?: string; countryCode? : string }>({});
const countryCodeHandler = (e: React.ChangeEvent<HTMLInputElement>) => {
if (e.target.value.length <= 3) {

60
src/app/signup/methodes/page.tsx

@ -4,28 +4,44 @@ import axiosInstance from "@/components/api/axios";
import { useUserContext } from "@/components/contexts/userContext";
import ButtonPrimary from "@/shared/ButtonPrimary";
import { useRouter } from "next/navigation";
import { useEffect, useState } from "react";
import { useEffect, useState, ChangeEvent } from "react";
import { FaWhatsapp } from "react-icons/fa";
import { MdOutlineTextsms } from "react-icons/md";
interface Form {
phoneNumber: string;
countryCode: string;
name?: string;
password: string;
confirmPassword: string;
method: "register" | "reset"; // Define possible values for method
verification_methodes?: string[];
}
interface EnabledMethods {
watsapp: boolean;
sms: boolean;
}
function SelectMethods() {
const router = useRouter();
const { user, form ,setForm } = useUserContext();
const {form, setForm } = useUserContext()
const [selectedMethod, setSelectedMethod] = useState("");
const [error, setError] = useState("");
const [loading, setLoading] = useState(false);
const [enabled, setEnabled] = useState({ watsapp: false, sms: false });
const [selectedMethod, setSelectedMethod] = useState<string>("");
const [error, setError] = useState<string>("");
const [loading, setLoading] = useState<boolean>(false);
const [enabled, setEnabled] = useState<EnabledMethods>({ watsapp: false, sms: false });
// Initialize enabled methods based on the form
useEffect(() => {
setEnabled({
watsapp: form.verification_methodes?.includes("watsapp") ?? false,
sms: form.verification_methodes?.includes("sms") ?? false,
watsapp: form?.verification_methodes?.includes("watsapp") ?? false,
sms: form?.verification_methodes?.includes("sms") ?? false,
});
}, []);
const handleMethodChange = (e) => {
}, [form]);
// Handle method change
const handleMethodChange = (e: ChangeEvent<HTMLInputElement>) => {
if (!enabled.watsapp && !enabled.sms) {
setSelectedMethod("");
} else if (!enabled.watsapp) {
@ -37,15 +53,17 @@ function SelectMethods() {
}
};
// Handle form submission
const handleSubmit = async () => {
setLoading(true);
try {
const payload = {
const payload: any = {
phone_number: form.phoneNumber,
verification_method: selectedMethod,
range_phone: form.countryCode,
};
// Handle Register
if (form.method === "register") {
payload.fullname = form.name;
payload.password = form.password;
@ -56,12 +74,13 @@ function SelectMethods() {
});
if (response.status === 202) {
setForm((prev) => ({ ...prev, verification_method: selectedMethod }));
setForm((prev : Record<string, any>) => ({ ...prev, verification_method: selectedMethod }));
setLoading(false);
router.replace("signup/otp-code");
}
} else if (form.method === "reset") {
}
// Handle Password Reset
else if (form.method === "reset") {
payload.password = form.password;
payload.password_confirmation = form.confirmPassword;
@ -70,14 +89,13 @@ function SelectMethods() {
});
if (response.status === 202) {
setForm((prev) => ({ ...prev, verification_method: selectedMethod }));
setForm((prev : Record<string, any>) => ({ ...prev, verification_method: selectedMethod }));
setLoading(false);
router.replace("signup/otp-code");
}
}
} catch (error) {
setError(error);
console.log(error);
} catch (error: any) {
setError(error.response?.data?.detail || "An error occurred.");
setLoading(false);
}
};
@ -148,7 +166,7 @@ function SelectMethods() {
</div>
{/* Error Message */}
{error && <p className="text-xs text-red-500">{error.message}</p>}
{error && <p className="text-xs text-red-500">{error}</p>}
{/* Continue Button */}
<ButtonPrimary

10
src/app/signup/otp-code/page.tsx

@ -61,7 +61,7 @@ const PageSignUp: FC<PageSignUpProps> = () => {
setTime(30);
setLoading(true);
try {
const payload = {
const payload : Record<string, any> = {
phone_number: form.phoneNumber,
verification_method: form.verification_method,
range_phone: form.countryCode,
@ -73,7 +73,7 @@ const PageSignUp: FC<PageSignUpProps> = () => {
payload.password_confirmation = form.confirmPassword;
const response = await axiosInstance.post(
`/api/account/register/`,
`/api/account/reister/`,
payload,
{
headers: { Accept: "application/json" },
@ -89,7 +89,7 @@ const PageSignUp: FC<PageSignUpProps> = () => {
payload.password_confirmation = form.confirmPassword;
const response = await axiosInstance.post(
`/api/account/recover/`,
`/api/account/recoer/`,
payload,
{
headers: { Accept: "application/json" },
@ -100,8 +100,8 @@ const PageSignUp: FC<PageSignUpProps> = () => {
setLoading(false);
}
}
} catch (error) {
setError(error);
} catch (error : any) {
setError(error.message);
console.log(error);
setLoading(false);
}

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

@ -6,14 +6,14 @@ 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 } from "@/components/contexts/tourDetails";
import { Context, useToursContext } from "@/components/contexts/tourDetails";
export interface GuestsInputProps {
className?: string;
}
const GuestsInput: FC<GuestsInputProps> = ({ className = "flex-1" }) => {
const { setPassengers, passengers } = useContext(Context);
const { setPassengers, passengers } = useToursContext();
const [guestAdultsInputValue, setGuestAdultsInputValue] =
useState(passengers);

41
src/app/tours/[slug]/StayDatesRangeInput.tsx

@ -8,8 +8,49 @@ import DatePickerCustomDay from "@/components/DatePickerCustomDay";
import DatePicker from "react-datepicker";
import ClearDataButton from "@/app/(client-components)/(HeroSearchForm)/ClearDataButton";
interface ImageURL {
sm: string;
md: string;
lg: string;
}
interface Image {
id: number;
image_url: ImageURL;
}
interface TourFeature {
id: number;
title: string;
}
interface TravelTip {
id: number;
title: string;
description: string;
}
interface Tour {
id: number;
title: string;
slug: string;
description: string;
started_at: string;
ended_at: string;
capacity: number;
number_sold: number;
tour_features: TourFeature[];
travel_tips: TravelTip[];
status: string;
price: string;
percent_off: number;
final_price: string | number;
images: Image[];
trip_status: string;
}
export interface StayDatesRangeInputProps {
className?: string;
details ? : Tour
}
const StayDatesRangeInput: FC<StayDatesRangeInputProps> = ({

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

@ -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"
// >
// &#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 = () => {
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>
);

62
src/components/SearchCard.tsx

@ -3,13 +3,11 @@ import { useToursContext } from "./contexts/tourDetails";
import Image from "next/image";
import axiosInstance from "./api/axios";
import calender from "./../images/Group.svg";
import Link from "next/link";
function SearchCard({ data }) {
function SearchCard({ data }: { data: any }) {
const { details } = useToursContext();
const [tourDetail, setTourDetail] = useState();
console.log(data.image.image_url.sm);
const [tourDetail, setTourDetail] = useState<any>();
useEffect(() => {
axiosInstance
@ -21,6 +19,7 @@ function SearchCard({ data }) {
console.log(error);
});
}, [data]);
const formatDate = (dateStr: string) => {
const date = new Date(dateStr);
return date.toLocaleDateString("en-GB", {
@ -30,46 +29,45 @@ function SearchCard({ data }) {
});
};
// Function to calculate the number of days between two dates
const calculateDuration = (start: string, end: string) => {
const startDate = new Date(start);
const endDate = new Date(end);
const timeDiff = endDate.getTime() - startDate.getTime();
const daysDiff = Math.ceil(timeDiff / (1000 * 3600 * 24)); // Convert ms to days
const daysDiff = Math.ceil(timeDiff / (1000 * 3600 * 24));
return daysDiff;
};
if (!tourDetail) return null; // Return null if tourDetail is not available
const formattedStartDate = formatDate(data.started_at);
const formattedEndDate = formatDate(data.ended_at);
const tripDuration = calculateDuration(data.started_at, data.ended_at);
if (tourDetail) {
return (
<div className="flex hover:bg-neutral-100 p-4 cursor-pointer rounded-3xl">
<div className="h-30 inset-0 w-30 h-30 overflow-hidden rounded-xl relative">
<Image
className="max-h-16 object-cover"
width={100}
height={100}
alt={data.title}
src={data.image.image_url.sm}
/>
</div>
<div className="ml-4">
<h3 className="text-amber-800">{data?.title}</h3>
<div className="flex items-center text-neutral-500 dark:text-neutral-400 text-sm space-x-1.5">
<Image alt="calendar" src={calender} />
<span className="">
{formattedStartDate} - {formattedEndDate}
</span>
</div>
<p className="flex items-center text-neutral-500 dark:text-neutral-400 text-sm space-x-1.5">
({tripDuration} Days)
</p>
return (
<Link href={`/tours/${data?.slug}-${data?.id}`} className="flex hover:bg-neutral-100 p-4 cursor-pointer rounded-3xl">
<div className="h-30 inset-0 w-30 h-30 overflow-hidden rounded-xl relative">
<Image
className="max-h-16 object-cover"
width={100}
height={100}
alt={data.title}
src={data.image.image_url.sm}
/>
</div>
<div className="ml-4">
<h3 className="text-amber-800">{data?.title}</h3>
<div className="flex items-center text-neutral-500 dark:text-neutral-400 text-sm space-x-1.5">
<Image alt="calendar" src={calender} />
<span>
{formattedStartDate} - {formattedEndDate}
</span>
</div>
<p className="flex items-center text-neutral-500 dark:text-neutral-400 text-sm space-x-1.5">
({tripDuration} Days)
</p>
</div>
);
}
</Link>
);
}
export default SearchCard;

1
src/components/StayCard2.tsx

@ -23,6 +23,7 @@ const StayCard2: FC<StayCard2Props> = ({
className = "",
data = DEMO_DATA,
}) => {
const {
galleryImgs,
listingCategory,

68
src/components/contexts/tourDetails.tsx

@ -1,7 +1,48 @@
"use client";
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;
lg: string;
}
interface Image {
id: number;
image_url: ImageURL;
}
interface TourFeature {
id: number;
title: string;
}
interface TravelTip {
id: number;
title: string;
description: string;
}
interface TourDetails {
id: number;
title: string;
slug: string;
description: string;
started_at: string;
ended_at: string;
capacity: number;
number_sold: number;
tour_features: TourFeature[];
travel_tips: TravelTip[];
status: string;
price: string;
percent_off: number;
final_price: string | number | undefined;
images: Image[];
trip_status: string;
}
interface Country {
id: number;
@ -16,14 +57,6 @@ interface Tour {
duration: string;
}
interface TourDetails {
id: number;
name: string;
description: string;
startDate: string;
endDate: string;
passengers: number;
}
export const Context = createContext<ToursContextType | undefined>(undefined);
@ -33,7 +66,7 @@ interface ToursContextType {
getTourData: (item: number) => Promise<void>;
setPassengers: React.Dispatch<React.SetStateAction<number>>;
setDetails: React.Dispatch<React.SetStateAction<TourDetails | undefined>>;
tours: Tour[];
tours: {results : Tour[]};
countries: Country[];
}
@ -44,9 +77,9 @@ interface ContextProviderProps {
export const ContextProvider = ({ children }: ContextProviderProps) => {
const [details, setDetails] = useState<TourDetails | undefined>(undefined);
const [passengers, setPassengers] = useState<number>(0);
const [tours, setTours] = useState<Tour[]>([]);
const [tours, setTours] = useState<{ results: Tour[] }>({ results: [] }); // <-- Wrap in results
const [countries, setCountries] = useState<Country[]>([]);
const path = usePathname()
const path = usePathname();
useEffect(() => {
axiosInstance
@ -59,20 +92,23 @@ export const ContextProvider = ({ children }: ContextProviderProps) => {
});
}, []);
useEffect(() => {
axiosInstance
.get("/api/tours/")
.then((response) => {
setTours(response.data);
setTours({ results: response.data.results }); // <-- Expect results
})
.catch((error) => {
console.error("Error fetching data:", error);
});
}, []);
useEffect(() => {
setDetails(undefined);
}, [path]);
useEffect(()=>{
setDetails(undefined)
} , [path])
const getTourData = async (item: number) => {
try {
const response = await axiosInstance.get(`/api/tours/${item}/`);
@ -81,6 +117,8 @@ export const ContextProvider = ({ children }: ContextProviderProps) => {
console.error("Error fetching tour data:", error);
}
};
console.log(tours);
return (
<Context.Provider

Loading…
Cancel
Save