diff --git a/src/app/(account-pages)/my-trips/page.tsx b/src/app/(account-pages)/my-trips/page.tsx index e0abd3b..b57e41c 100644 --- a/src/app/(account-pages)/my-trips/page.tsx +++ b/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 = () => {
- {tours.length ? (tours?.filter((_, i) => i < 8).map((stay) => ( + {tours.length ? (tours?.filter((_, i) => i < 8).map((stay : StayDataType) => ( ))) : (

You have no trips

)}
diff --git a/src/app/(account-pages)/passengers-list/PassengerTable.tsx b/src/app/(account-pages)/passengers-list/PassengerTable.tsx index 0a3052f..615d42f 100644 --- a/src/app/(account-pages)/passengers-list/PassengerTable.tsx +++ b/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 = ({ 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 (
{

{data.fullname}

+ {data.passport_number && ( +

+ Passport No: {data.passport_number} +

+ )}
{/* Action Icons */}
-
diff --git a/src/app/(account-pages)/passengers-list/[id]/page.tsx b/src/app/(account-pages)/passengers-list/[id]/page.tsx index 7862b5e..d4231ef 100644 --- a/src/app/(account-pages)/passengers-list/[id]/page.tsx +++ b/src/app/(account-pages)/passengers-list/[id]/page.tsx @@ -122,7 +122,13 @@ const EditPassenger: FC = ({ params }) => { const handleSavePassenger = async () => { if (!validateForm()) return; - const updatedFields: Partial = {}; + 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 = ({ params }) => { }; return ( -
+
- <> -

Passenger Information

-
-
- - - setPassenger((prev) => ({ - ...prev, - name: e.target.value, - })) - } - placeholder="Full Name" - /> - +

Passenger Information

+
+
+ + + setPassenger((prev) => ({ ...prev, name: e.target.value })) + } + placeholder="Full Name" + /> + - - - setPassenger((prev) => ({ - ...prev, - passport: e.target.value, - })) - } - type="text" // Changed from 'number' to 'text' - placeholder="Passport Number" - /> - + + + setPassenger((prev) => ({ + ...prev, + passport: e.target.value, + })) + } + type="text" // Changed from 'number' to 'text' + placeholder="Passport Number" + /> + - - - setPassenger((prev) => ({ - ...prev, - date: e.target.value, - })) - } - type="date" - placeholder="Date of Birth" - /> - + + + setPassenger((prev) => ({ ...prev, date: e.target.value })) + } + type="date" + placeholder="Date of Birth" + /> + - - - 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" - /> - + + + 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" + /> + - - - {loading &&

Loading ...

} -
-
- + + + {loading &&

Loading ...

} +
+
diff --git a/src/app/(account-pages)/passengers-list/page.tsx b/src/app/(account-pages)/passengers-list/page.tsx index b93b0b3..a57965f 100644 --- a/src/app/(account-pages)/passengers-list/page.tsx +++ b/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(()=>{ {/* Passenger Table */} - {passengers.map((item)=>( + {passengers.map((item : data)=>( ))} diff --git a/src/app/(client-components)/(Header)/SearchDropdown.tsx b/src/app/(client-components)/(Header)/SearchDropdown.tsx index 7d50866..0c85201 100644 --- a/src/app/(client-components)/(Header)/SearchDropdown.tsx +++ b/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 = ({ className = "" }) => { const inputRef = React.createRef(); const [value, setValue] = useState(""); - const [toursDetails, setToursDetail] = useState([]); - const { getTourData, tours } = useToursContext(); + const [toursDetails, setToursDetail] = useState([]); + 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 = ({ 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 ( - {({ open }) => { + {({ open, close }) => { if (open) { setTimeout(() => { inputRef.current?.focus(); @@ -77,16 +103,25 @@ const SearchDropdown: FC = ({ className = "" }) => { rounded="rounded-full" type="text" placeholder="Type and search" - />{value.replaceAll(" " , "") &&( - - {filterdTours?.length ? ( - filterdTours?.map((item) => ( - - )) - ) : ( -

No Matches Found

- )} -
+ /> + {value.replaceAll(" ", "") && ( + + {filteredTours?.length ? ( + filteredTours?.map((item) => ( +
{ + setValue(""); + close(); + }} + > + +
+ )) + ) : ( +

No Matches Found

+ )} +
)} diff --git a/src/app/add-listing/[[...stepIndex]]/PageAddListing1.tsx b/src/app/add-listing/[[...stepIndex]]/PageAddListing1.tsx index 5b275a8..f832690 100644 --- a/src/app/add-listing/[[...stepIndex]]/PageAddListing1.tsx +++ b/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 = ({ 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 = ({
} > - {(closeModal) => ( + {(closeModal: () => void) => (
    - {savedPassengers?.map((item, index) => ( + {savedPassengers?.map((item : {fullname : string , id : string | number}, index) => (
  • { 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 = ({ - 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 = ({ 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 = ({ 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 = ({ 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" /> diff --git a/src/app/custom-history/page.tsx b/src/app/custom-history/page.tsx index 44d0595..97fa299 100644 --- a/src/app/custom-history/page.tsx +++ b/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 = () => { - const router = useRouter() - const [tours, setTours] = useState([]); - const [toursDetail, setToursDetail] = useState([]); - const {user} = useUserContext(); + const router = useRouter(); + const [tours, setTours] = useState([]); // Typed as any[] since the detail is parsed dynamically + const [toursDetail, setToursDetail] = useState([]); + const { user } = useUserContext(); useEffect(() => { if (user) { - axiosInstance('/api/trip/custom/orders/', { headers: { Authorization: `token ${user.token}`, @@ -26,31 +24,49 @@ const PageAddListing10: FC = () => { }) .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 (
    @@ -64,7 +80,7 @@ console.log(tours); className="bg-white border border-gray-200 rounded-3xl p-4 mb-4 shadow-md" >
    -
    +

    Origin:

    {item['1']?.city || 'N/A'}

    @@ -72,7 +88,7 @@ console.log(tours); {Object.keys(item) .filter((key) => !isNaN(Number(key)) && Number(key) > 1) .map((key) => ( -
    +

    {item[key].title}:

    {item[key]?.city || 'N/A'}

    @@ -81,11 +97,15 @@ console.log(tours);
    -

    Estimated Cost: ${item.summary?.cost_estimate || 'N/A'}

    - +

    + Estimated Cost: ${item.summary?.cost_estimate || 'N/A'} +

    -
    @@ -95,10 +115,10 @@ console.log(tours);

    No tours available.

    )} -
    +

    Contact Support

    diff --git a/src/app/custom-trip/page.tsx b/src/app/custom-trip/page.tsx index 7ccb809..54eebab 100644 --- a/src/app/custom-trip/page.tsx +++ b/src/app/custom-trip/page.tsx @@ -24,10 +24,8 @@ interface CommonLayoutProps {} const CommonLayout: FC = () => { const { user } = useUserContext(); - const router = useRouter(); - useEffect(() => { if (!Object.keys(user).length) { router.replace("/signup"); @@ -52,9 +50,10 @@ const CommonLayout: FC = () => { hotelCost: number; }[] >([]); - const [isFormValid, setIsFormValid] = useState(false); + const [isFormValid, setIsFormValid] = useState(false); const [isContinueValid, setIsContinueValid] = useState(false); - var special = [ + + const special = [ "Zeroth", "First", "Second", @@ -76,7 +75,8 @@ const CommonLayout: FC = () => { "Eighteenth", "Nineteenth", ]; - var deca = [ + + const deca = [ "Twent", "Thirt", "Fort", @@ -87,7 +87,7 @@ const CommonLayout: FC = () => { "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 = () => { 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 = () => { setDestinations(updatedDestinations); validateForm(); - // validateContinue(); }; - console.log(destinations); const submitTour = async () => { const formData = { @@ -252,7 +241,7 @@ const CommonLayout: FC = () => { 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 = () => { .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 = () => { }), }; 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); diff --git a/src/app/forgot-password/page.tsx b/src/app/forgot-password/page.tsx index c789bb3..efa6d26 100644 --- a/src/app/forgot-password/page.tsx +++ b/src/app/forgot-password/page.tsx @@ -25,7 +25,7 @@ const PageSignUp: FC = () => { 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) => { if (e.target.value.length <= 3) { diff --git a/src/app/signup/methodes/page.tsx b/src/app/signup/methodes/page.tsx index ef4c080..9a038f1 100644 --- a/src/app/signup/methodes/page.tsx +++ b/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(""); + const [error, setError] = useState(""); + const [loading, setLoading] = useState(false); + const [enabled, setEnabled] = useState({ 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) => { 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) => ({ ...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) => ({ ...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() {
    {/* Error Message */} - {error &&

    {error.message}

    } + {error &&

    {error}

    } {/* Continue Button */} = () => { setTime(30); setLoading(true); try { - const payload = { + const payload : Record = { phone_number: form.phoneNumber, verification_method: form.verification_method, range_phone: form.countryCode, @@ -73,7 +73,7 @@ const PageSignUp: FC = () => { 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 = () => { 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 = () => { setLoading(false); } } - } catch (error) { - setError(error); + } catch (error : any) { + setError(error.message); console.log(error); setLoading(false); } diff --git a/src/app/tours/[slug]/GuestsInput.tsx b/src/app/tours/[slug]/GuestsInput.tsx index b463138..a8265dd 100644 --- a/src/app/tours/[slug]/GuestsInput.tsx +++ b/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 = ({ className = "flex-1" }) => { - const { setPassengers, passengers } = useContext(Context); + const { setPassengers, passengers } = useToursContext(); const [guestAdultsInputValue, setGuestAdultsInputValue] = useState(passengers); diff --git a/src/app/tours/[slug]/StayDatesRangeInput.tsx b/src/app/tours/[slug]/StayDatesRangeInput.tsx index 259071e..58dabb4 100644 --- a/src/app/tours/[slug]/StayDatesRangeInput.tsx +++ b/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 = ({ diff --git a/src/app/tours/[slug]/page.tsx b/src/app/tours/[slug]/page.tsx index 6298b3e..3575835 100644 --- a/src/app/tours/[slug]/page.tsx +++ b/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 = ({}) => { - // - - 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([]); 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 ( -
    - {/* 1 */} -
    - - -
    - - {/* 2 */} -

    - {details?.title} -

    - - {/* 3 */} -
    - - · - - - Tokyo, Jappan - -
    - - {/* 4 */} -
    - - - Hosted by{" "} - - Kevin Francis - - -
    - - {/* 5 */} -
    + const id = slug?.match(/-?(\d+)$/)?.[1] || null; - {/* 6 */} -
    -
    - - - 6 guests - -
    -
    - - - 6 beds - -
    -
    - - - 3 baths - -
    -
    - - - 2 bedrooms - -
    -
    -
    - ); - }; - - 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 (

    {details?.title}

    -
    +
    {details?.description}
    ); }; - const renderSection3 = () => { - return ( -
    -
    -

    Tour Features

    -
    -
    - {/* 6 */} -
    - {details && - details.tour_features?.map((item) => ( -
    - - {item.title} -
    - ))} -
    - - {/*
    -
    - - View more 20 amenities - -
    - {renderMotalAmenities()} */} -
    - ); - }; - - // const renderMotalAmenities = () => { - // return ( - // - // - //
    - // - // - // - - // {/* This element is to trick the browser into centering the modal contents. */} - // - // - //
    - //
    - //
    - //

    - // Amenities - //

    - // - // - // - //
    - //
    - // {Amenities_demos.filter((_, i) => i < 1212).map((item) => ( - //
    - // - // {item.name} - //
    - // ))} - //
    - //
    - //
    - //
    - //
    - //
    - //
    - // ); - // }; - - const renderSection4 = () => { + const renderItinerarySection = () => { return (
    - {/* HEADING */} -
    -

    Itinerary

    -
    +

    Itinerary

    - {/* CONTENT */}
    -
    - {iteneries?.map((item, index) => ( -
    +
    + {itineraries.map((item, index) => ( +
    -
    - {totalIteneries !== index + 1 && ( -
    +
    + {itineraries.length !== index + 1 && ( +
    )}
    -

    - {item?.title} -

    - - {item?.started_at - .replaceAll("-", " ") - .replaceAll("T", " | ")} - +

    {item.title}

    + {item.started_at.replace(/T/, " | ")}
    {item.summary}
    @@ -290,259 +101,59 @@ const ListingStayDetailPage: FC = ({}) => { ); }; - const renderSection5 = () => { + const renderTourFeatures = () => { return (
    - {/* HEADING */} -

    Host Information

    +

    Tour Features

    - - {/* host */} -
    - -
    - - Kevin Francis - -
    - - · - 12 places +
    + {details?.tour_features?.map((feature) => ( +
    + + {feature.title}
    -
    -
    - - {/* desc */} - - {details?.description} - - - {/* info */} -
    -
    - - - - Joined in March 2016 -
    -
    - - - - Response rate - 100% -
    -
    - - - - - Fast response - within a few hours -
    -
    - - {/* == */} -
    -
    - See host profile -
    -
    - ); - }; - - const renderSection6 = () => { - return ( -
    - {/* HEADING */} -

    Reviews (23 reviews)

    -
    - - {/* Content */} -
    - -
    - - - - -
    -
    - - {/* comment */} -
    - - - - -
    - View more 20 reviews -
    -
    -
    - ); - }; - - const renderSection7 = () => { - return ( -
    - {/* HEADING */} -
    -

    Location

    - - San Diego, CA, United States of America (SAN-San Diego Intl.) - -
    -
    - - {/* MAP */} -
    -
    - -
    -
    -
    - ); - }; - - const renderSection8 = () => { - return ( -
    - {/* HEADING */} -

    Travel guide and tips

    -
    - - {/* CONTENT */} -
    -

    Special Note

    -
    -
      - {details && - details.travel_tips.map((item) => ( -
    • -

      - {item.title} -

      -

      {item.description}

      -
    • - ))} -
    -
    + ))}
    ); }; const renderSidebar = () => { + const total = details?.final_price && passengers + ? (Number(details?.final_price) * passengers).toLocaleString("en-US", { style: "currency", currency: "USD" }) + : 0; + return (
    - {/* PRICE */} + {/* Price display */}
    - {+details?.price === +details?.final_price ? ( - {details?.price} + {details?.price === details?.final_price ? ( + ${details?.price} ) : ( -
    - {details?.final_price} - {details?.price}{" "} +
    + ${details?.final_price} + ${details?.price}
    )} -
    - {/* FORM */} - + {/* Booking Form */} +
    - {/* SUM */} -
    -
    - - {details?.final_price} x {passengers} passengers - - - {" "} - {isNaN(details?.final_price * passengers) - ? "N/A" // Or any fallback value, like "0" or a string message - : (details?.final_price * passengers).toString()} - -
    -
    - Service charge - $0 -
    -
    -
    - Total - - {isNaN(details?.price * passengers) - ? "N/A" // Or any fallback value, like "0" or a string message - : (details?.price * passengers).toString()} - -
    + {/* Total price */} +
    + Total + {total}
    - {/* SUBMIT */} + {/* Reserve Button */} Reserve @@ -553,100 +164,59 @@ const ListingStayDetailPage: FC = ({}) => { return (
    - {/* HEADER */} + {/* Header Section with Images */}
    + {/* Main Image */}
    {details && ( )} - {/*
    - -
    */} -
    - {details?.images - .filter((_, i) => i >= 1 && i < 5) - .map((item, index) => ( -
    = 3 ? "hidden sm:block" : "" - }`} - > - - -
    - } - > - {(closeModal) => ( -
    - -
    - )} - - {/* OVERLAY */} -
    - ))} - - {/* */} + {/* Additional Images */} + {details?.images.slice(1, 5).map((item, index) => ( +
    = 3 ? "hidden sm:block" : ""}`}> + + +
    + } + > + {() => ( +
    + +
    + )} + +
    + ))}
    - {/* MAIN */} -
    - {/* CONTENT */} + {/* Main content section */} +
    + {/* Left Content */}
    - {/* {renderSection1()} */} - {renderSection2()} - {renderSection3()} - {renderSection8()} - {renderSection4()} - - {/* {renderSection5()} */} - {/* {renderSection6()} */} - {/* {renderSection7()} */} + {renderSectionDetails()} + {renderTourFeatures()} + {renderItinerarySection()}
    - {/* SIDEBAR */} + {/* Right Sidebar */}
    {renderSidebar()}
    + + {/* Mobile Footer */}
    ); diff --git a/src/components/SearchCard.tsx b/src/components/SearchCard.tsx index 87cc4fe..fa69850 100644 --- a/src/components/SearchCard.tsx +++ b/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(); 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 ( -
    -
    - {data.title} -
    -
    -

    {data?.title}

    -
    - calendar - - {formattedStartDate} - {formattedEndDate} - -
    -

    - ({tripDuration} Days) -

    + return ( + +
    + {data.title} +
    +
    +

    {data?.title}

    +
    + calendar + + {formattedStartDate} - {formattedEndDate} +
    +

    + ({tripDuration} Days) +

    - ); - } + + ); } export default SearchCard; diff --git a/src/components/StayCard2.tsx b/src/components/StayCard2.tsx index 24d67bf..3262b78 100644 --- a/src/components/StayCard2.tsx +++ b/src/components/StayCard2.tsx @@ -23,6 +23,7 @@ const StayCard2: FC = ({ className = "", data = DEMO_DATA, }) => { + const { galleryImgs, listingCategory, diff --git a/src/components/contexts/tourDetails.tsx b/src/components/contexts/tourDetails.tsx index 5f95ea5..8101217 100644 --- a/src/components/contexts/tourDetails.tsx +++ b/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(undefined); @@ -33,7 +66,7 @@ interface ToursContextType { getTourData: (item: number) => Promise; setPassengers: React.Dispatch>; setDetails: React.Dispatch>; - tours: Tour[]; + tours: {results : Tour[]}; countries: Country[]; } @@ -44,9 +77,9 @@ interface ContextProviderProps { export const ContextProvider = ({ children }: ContextProviderProps) => { const [details, setDetails] = useState(undefined); const [passengers, setPassengers] = useState(0); - const [tours, setTours] = useState([]); + const [tours, setTours] = useState<{ results: Tour[] }>({ results: [] }); // <-- Wrap in results const [countries, setCountries] = useState([]); - 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 (