diff --git a/src/app/(account-pages)/(components)/Nav.tsx b/src/app/(account-pages)/(components)/Nav.tsx
index d548f4a..1633962 100644
--- a/src/app/(account-pages)/(components)/Nav.tsx
+++ b/src/app/(account-pages)/(components)/Nav.tsx
@@ -10,7 +10,7 @@ export const Nav = () => {
const listNav: Route[] = [
"/account",
- "/account-savelists",
+ "/my-trips",
"/account-password",
"/passengers-list",
];
diff --git a/src/app/(account-pages)/account-savelists/page.tsx b/src/app/(account-pages)/account-savelists/page.tsx
deleted file mode 100644
index 05905d4..0000000
--- a/src/app/(account-pages)/account-savelists/page.tsx
+++ /dev/null
@@ -1,88 +0,0 @@
-"use client";
-
-import { Tab } from "@headlessui/react";
-import CarCard from "@/components/CarCard";
-import ExperiencesCard from "@/components/ExperiencesCard";
-import StayCard from "@/components/StayCard";
-import {
- DEMO_CAR_LISTINGS,
- DEMO_EXPERIENCES_LISTINGS,
- DEMO_STAY_LISTINGS,
-} from "@/data/listings";
-import React, { Fragment, useState } from "react";
-import ButtonSecondary from "@/shared/ButtonSecondary";
-
-const AccountSavelists = () => {
- let [categories] = useState(["Stays", "Experiences", "Cars"]);
-
- const renderSection1 = () => {
- return (
-
-
-
Save lists
-
-
-
-
-
-
- {categories.map((item) => (
-
- {({ selected }) => (
-
- {item}
-
- )}
-
- ))}
-
-
-
-
- {DEMO_STAY_LISTINGS.filter((_, i) => i < 8).map((stay) => (
-
- ))}
-
-
- Show me more
-
-
-
-
- {DEMO_EXPERIENCES_LISTINGS.filter((_, i) => i < 8).map(
- (stay) => (
-
- )
- )}
-
-
- Show me more
-
-
-
-
- {DEMO_CAR_LISTINGS.filter((_, i) => i < 8).map((stay) => (
-
- ))}
-
-
- Show me more
-
-
-
-
-
-
- );
- };
-
- return renderSection1();
-};
-
-export default AccountSavelists;
diff --git a/src/app/(account-pages)/account/page.tsx b/src/app/(account-pages)/account/page.tsx
index 297e3d6..4d9a106 100644
--- a/src/app/(account-pages)/account/page.tsx
+++ b/src/app/(account-pages)/account/page.tsx
@@ -15,7 +15,7 @@ export interface AccountPageProps {}
const AccountPage = () => {
const router = useRouter();
- const User = JSON.parse(localStorage.getItem("user"))
+ const User = JSON.parse(localStorage.getItem("user"));
let user = JSON.parse(localStorage.getItem("user"));
if (!user) {
return router.replace("/");
@@ -29,12 +29,11 @@ const AccountPage = () => {
const [image, setImage] = useState(null);
const [imageURL, setImageURL] = useState(null);
const [error, setError] = useState("");
+ const [loading, setLoading] = useState({});
const deleteHandler = async () => {
- setStatus(false);
-
setError("");
-
+ setLoading({delete : true})
try {
const response = await axiosInstance.delete(
`/api/account/profile/delete/`,
@@ -46,13 +45,20 @@ const AccountPage = () => {
);
if (response.status === 204) {
localStorage.removeItem("user");
+ setStatus(false);
+
router.replace("/");
} else {
setError("Something went wrong");
}
} catch (error) {
setError(error.message);
- }
+
+ } finally{
+ setLoading({delete : false})
+
+
+ }
};
const signOutHandler = () => {
localStorage.removeItem("user");
@@ -63,6 +69,8 @@ const AccountPage = () => {
const changeHandler = async () => {
setError("");
+ setLoading({change : true})
+
const formData = new FormData();
formData.append("fullname", name);
formData.append("email", email);
@@ -83,17 +91,25 @@ const AccountPage = () => {
}
);
if (response.status === 200) {
+ console.log(response);
+
user.avatar = response.data.avatar;
user.email = response.data.email;
user.fullname = response.data.fullname;
user.phone_number = response.data.phone_number;
localStorage.setItem("user", JSON.stringify(user));
+
} else {
setError("Something went wrong");
+
}
} catch (error) {
setError(error.message);
+
+ } finally{
+ setLoading({change : false})
+
}
};
@@ -103,14 +119,11 @@ const AccountPage = () => {
const uploadedFile = await getImageURL(file);
setImageURL(uploadedFile.url);
- };
-
- setImage(file);
-
-
-
}
+ setImage(file);
+ };
+
return (
Account information
@@ -186,9 +199,9 @@ const AccountPage = () => {
className="mt-1.5"
/>
- {error && {error}
}
+ {error && {error}
}
-
+
Update info
@@ -196,6 +209,7 @@ const AccountPage = () => {
Sign out
diff --git a/src/app/(account-pages)/my-trips/page.tsx b/src/app/(account-pages)/my-trips/page.tsx
new file mode 100644
index 0000000..47f47bb
--- /dev/null
+++ b/src/app/(account-pages)/my-trips/page.tsx
@@ -0,0 +1,69 @@
+"use client";
+
+import { Tab } from "@headlessui/react";
+import CarCard from "@/components/CarCard";
+import ExperiencesCard from "@/components/ExperiencesCard";
+import StayCard from "@/components/StayCard";
+import {
+ DEMO_CAR_LISTINGS,
+ DEMO_EXPERIENCES_LISTINGS,
+ DEMO_STAY_LISTINGS,
+} from "@/data/listings";
+import React, { Fragment, useEffect, useState } from "react";
+import ButtonSecondary from "@/shared/ButtonSecondary";
+import axiosInstance from "@/components/api/axios";
+import StayCard2 from "@/components/StayCard2";
+
+const MyTrips = () => {
+ const user = JSON.parse(localStorage.getItem("user"));
+ let [categories] = useState(["Stays", "Experiences", "Cars"]);
+ let [tours , setTours] = useState([]);
+ useEffect(() => {
+ axiosInstance
+ .get("/api/tours/orders/", {
+ headers: {
+ Authorization: `token ${user.token}`,
+ },
+ })
+ .then((response) => {
+ setTours(response.data.results);
+ console.log(response);
+
+ })
+ .catch((error) => {
+ console.error(error);
+ });
+ }, []);
+
+
+ const renderSection1 = () => {
+ return (
+
+
+
Save lists
+
+
+
+
+
+
+
+
+ {tours.length ? (tours?.filter((_, i) => i < 8).map((stay) => (
+
+ ))) : (
You have no trips )}
+
+
+
+
+
+
+
+
+ );
+ };
+
+ return renderSection1();
+};
+
+export default MyTrips;
diff --git a/src/app/add-listing/[[...stepIndex]]/PageAddListing1.tsx b/src/app/add-listing/[[...stepIndex]]/PageAddListing1.tsx
index 4647b19..4dabc4d 100644
--- a/src/app/add-listing/[[...stepIndex]]/PageAddListing1.tsx
+++ b/src/app/add-listing/[[...stepIndex]]/PageAddListing1.tsx
@@ -1,92 +1,177 @@
-import React, { FC } from "react";
+import React, { FC, useEffect, useState } from "react";
import Input from "@/shared/Input";
import Select from "@/shared/Select";
import FormItem from "../FormItem";
import { IoPersonAddOutline } from "react-icons/io5";
import getImageURL from "@/components/api/getImageURL";
+import ConfirmModal from "../../../shared/popUp";
+import axiosInstance from "@/components/api/axios";
+import PassengerTable from "@/app/(account-pages)/passengers-list/PassengerTable";
-export interface PageAddListing1Props {}
+export interface PageAddListing1Props {
+ Passenger: any;
+ setNewPassenger: (passenger: any) => void;
+ errors: any;
+ passengerID: any[];
+ setPassengerID: (ids: any[]) => void;
+ selectedPassenger: any; // Add prop for selectedPassenger
+ setSelectedPassenger: (passenger: any) => void; // Add prop for setting selectedPassenger
+}
const PageAddListing1: FC = ({
Passenger,
setNewPassenger,
+ errors,
+ passengerID,
+ setPassengerID,
+ selectedPassenger,
+ setSelectedPassenger, // Receive the function to reset selectedPassenger
}) => {
-
const { fullName, date, number, passport } = Passenger;
+ const user = JSON.parse(localStorage.getItem("user"));
+
+ const [savedPassengers, setSavedPassengers] = useState([]);
+
+ useEffect(() => {
+ axiosInstance
+ .get("/api/account/passengers/", {
+ headers: {
+ Authorization: `token ${user.token}`,
+ },
+ })
+ .then((response) => {
+ setSavedPassengers(response.data.results);
+ console.log(response);
+ })
+ .catch((error) => {
+ console.error(error);
+ });
+ }, []);
+ console.log(savedPassengers);
+
const handleFileChange = async (e: React.ChangeEvent) => {
const file = e.target.files?.[0];
if (file) {
const uploadedFile = await getImageURL(file);
-
- setNewPassenger((prev) => ({ ...prev, image: uploadedFile.url }));
+ setNewPassenger((prev) => ({ ...prev, passport_image: uploadedFile.url }));
}
-
+ };
+
+ const AddPassengerId = (passenger) => {
+ setSelectedPassenger(passenger);
+ console.log(passenger.id);
+ setPassengerID((prev) => {
+ if (!Array.isArray(prev)) {
+ prev = [];
+ }
+ const exists = prev.some((p) => p.passenger_id === passenger.id);
+ if (exists) {
+ return prev;
+ }
+ return [...prev, { passenger_id: passenger.id }];
+ });
};
return (
- <>
- Choosing listing categories
-
- {/* FORM */}
-
- {/* ITEM */}
+ !selectedPassenger ? (
+ <>
+
Choosing listing categories
+
+ {/* FORM */}
+
+ {/* Adjust icon size */}
+ Add from passenger list
+
+ }
+ >
+ {(closeModal) => (
+
+ {savedPassengers?.map((item, 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`}
+ >
+ {item.fullname}
+
+ ))}
+
+ )}
+
-
-
{/* Adjust icon size */}
-
Add From passengers List
-
+
+ {/* ITEM */}
-
-
- setNewPassenger((prev) => ({ ...prev, fullName: e.target.value }))
- }
- value={fullName}
- placeholder="Full Name"
- />
-
-
-
- setNewPassenger((prev) => ({ ...prev, passport: e.target.value }))
- }
- value={passport}
- placeholder="Passport Number"
- />
-
-
-
- setNewPassenger((prev) => ({ ...prev, date: e.target.value }))
- }
- placeholder="Date of Birth"
- />
-
-
-
- setNewPassenger((prev) => ({ ...prev, number: e.target.value }))
- }
- placeholder="Phone Number"
- />
-
-
-
-
-
- >
+
+
+ setNewPassenger((prev) => ({ ...prev, fullname: e.target.value }))
+ }
+ value={fullName}
+ placeholder="Full Name"
+ />
+ {errors.fullName && (
+ {errors.fullName}
+ )}
+
+
+
+ setNewPassenger((prev) => ({ ...prev, passport_number: e.target.value }))
+ }
+ value={passport}
+ placeholder="Passport Number"
+ />
+ {errors.passport && (
+ {errors.passport}
+ )}
+
+
+
+ setNewPassenger((prev) => ({ ...prev, birthdate: e.target.value }))
+ }
+ placeholder="Date of Birth"
+ />
+ {errors.date && (
+ {errors.date}
+ )}
+
+
+
+ setNewPassenger((prev) => ({ ...prev, phone_number: e.target.value }))
+ }
+ placeholder="Phone Number"
+ />
+ {errors.number && (
+ {errors.number}
+ )}
+
+
+
+ {errors.image && (
+ {errors.image}
+ )}
+
+
+ >
+ ) : (
+ {selectedPassenger.fullname}
+ )
);
};
diff --git a/src/app/add-listing/[[...stepIndex]]/page.tsx b/src/app/add-listing/[[...stepIndex]]/page.tsx
index f768db2..0edab37 100644
--- a/src/app/add-listing/[[...stepIndex]]/page.tsx
+++ b/src/app/add-listing/[[...stepIndex]]/page.tsx
@@ -1,12 +1,14 @@
-"use client"
+"use client";
-import React, { useState } from "react";
+import React, { useContext, useState, useEffect } from "react";
import { FC } from "react";
import ButtonPrimary from "@/shared/ButtonPrimary";
import ButtonSecondary from "@/shared/ButtonSecondary";
-import { Route } from "@/routers/types";
import PageAddListing1 from "./PageAddListing1";
-import { setHttpClientAndAgentOptions } from "next/dist/server/config";
+import validatePassenger from "@/hooks/passengerValidation";
+import { Context } from "@/components/contexts/tourDetails";
+import { useRouter } from "next/navigation";
+import axiosInstance from "@/components/api/axios";
export interface CommonLayoutProps {
children: React.ReactNode;
@@ -16,48 +18,114 @@ export interface CommonLayoutProps {
}
const CommonLayout: FC = ({ params }) => {
+ const router = useRouter();
+ const user = JSON.parse(localStorage.getItem("user"));
- const [passengers , setPassengers] =useState([])
- const [newPassenger , setNewPassenger] =useState({
- fullName :"",
- date : "" ,
- number : "",
- passport : "",
- image : ""
- })
-
- const nextHandler =()=>{
- setPassengers((prev) => [...prev, newPassenger]);
-
- }
- const index = Number(params.stepIndex) || 1;
- const nextHref = (
- index < 10 ? `/add-listing/${index + 1}` : `/add-listing/${1}`
- ) as Route;
- const backtHref = (
- index > 1 ? `/add-listing/${index - 1}` : `/add-listing/${1}`
- ) as Route;
- const nextBtnText = index > 9 ? "Publish listing" : "Continue";
+ const [index, setIndex] = useState(1);
+ const [passengers, setPassengers] = useState([]);
+ const [errors, setErrors] = useState({});
+ const [newPassenger, setNewPassenger] = useState({
+ fullname: "",
+ passport_number: "",
+ birthdate: "",
+ phone_number: "",
+ passport_image: "",
+ });
+ const [passengerID, setPassengerID] = useState([]);
+ const [selectedPassenger, setSelectedPassenger] = useState(null);
+
+ const tourID = params.stepIndex[0];
+ const totalPassengers = useContext(Context).passengers;
+
+ const nextHref = () => setIndex((prev) => prev + 1);
+ const backtHref = () => (index > 1 ? setIndex((prev) => prev - 1) : index);
+ const nextBtnText = index > totalPassengers ? "Save Passengers" : "Continue";
+
+ const sendPassengers = async () => {
+ try {
+ const response = await axiosInstance.post(
+ "/api/tours/orders/purchase/",
+ {
+ tour_id: tourID,
+ passengers: {
+ passenger_ids: passengerID,
+ new_passengers: passengers,
+ },
+ },
+ {
+ headers: {
+ Authorization: `token ${user.token}`,
+ 'Content-Type': 'application/json',
+ },
+ }
+ );
+ console.log(response);
+ router.replace("/my-trips");
+ } catch (error) {
+ console.error("Error submitting passengers:", error);
+ }
+ };
+
+ useEffect(() => {
+ if (index > totalPassengers && passengers.length === totalPassengers) {
+ sendPassengers();
+ }
+ }, [passengers, index, totalPassengers]);
+
+ const nextHandler = () => {
+ const validationErrors = validatePassenger(newPassenger);
+
+ if (Object.keys(validationErrors).length > 0) {
+ setErrors(validationErrors);
+ console.log("Validation errors:", validationErrors);
+ return;
+ }
+
+ setSelectedPassenger(null);
+
+ setPassengers((prevPassengers) => [...prevPassengers, newPassenger]);
+ setNewPassenger({
+ fullname: "",
+ passport_number: "",
+ birthdate: "",
+ phone_number: "",
+ passport_image: "",
+ });
+ setErrors({});
+
+ nextHref();
+
+ };
+
return (
-
+
{index} {" "}
- / 10
+ / {totalPassengers}
- {/* --------------------- */}
-
+ {/* Passenger Form */}
+
- {/* --------------------- */}
- {index > 1 && Go back }
-
- {nextBtnText || "Continue"}
+ {/* {index > 1 && (
+ Go back
+ )} */}
+
+ {nextBtnText}
diff --git a/src/app/custom-trip/page.tsx b/src/app/custom-trip/page.tsx
new file mode 100644
index 0000000..680fc46
--- /dev/null
+++ b/src/app/custom-trip/page.tsx
@@ -0,0 +1,175 @@
+"use client";
+
+import React, { useEffect, useState } from "react";
+import { FC } from "react";
+import ButtonPrimary from "@/shared/ButtonPrimary";
+import FormItem from "../add-listing/FormItem";
+import axiosInstance from "@/components/api/axios";
+import Select from "@/shared/Select";
+import Input from "@/shared/Input";
+
+interface City {
+ name: string;
+ slug: string;
+}
+
+interface Country {
+ name: string;
+ city: City[];
+}
+
+interface CommonLayoutProps {}
+
+const CommonLayout: FC
= () => {
+ const [countries, setCountries] = useState([]);
+ const [startCity, setStartCity] = useState("");
+ const [endCity, setEndCity] = useState("");
+ const [transport, setTransport] = useState([]);
+ const [hotel, setHotel] = useState([]);
+ const [selectedHotel, setSelectedHotel] = useState("");
+ const [selectedTransport, setSelectedTransport] = useState("");
+ const [destinations, setDestinations] = useState<{ endCity: string, transport: string, hotel: string }[]>([]);
+
+ useEffect(() => {
+ axiosInstance.get("/api/cityguide/countries/?service=custom_trip")
+ .then((response) => setCountries(response.data.results))
+ .catch((error) => console.error(error));
+ }, []);
+
+ useEffect(() => {
+
+ console.log(destinations[destinations.length-1]?.endCity);
+ if (destinations[destinations.length-1]?.endCity) {
+ axiosInstance.get(`/api/trip/custom/transport/?from_city=${startCity}&to_city=${destinations[destinations.length-1]?.endCity}`)
+ .then((response) => { console.log(response)
+ setTransport(response.data)})
+ .catch((error) => console.error(error));
+
+ axiosInstance.get(`/api/trip/hotels/${destinations[destinations.length-1]?.endCity}/`)
+ .then((response) =>{ console.log(response)
+ setHotel(response.data.results)})
+
+ .catch((error) => console.error(error));
+ }
+ }, [destinations]);
+
+ const addDestination = () => {
+ setDestinations([...destinations, { endCity: "", transport: "", hotel: "" }]);
+ };
+
+ const handleDestinationChange = (index: number, field: string, value: string) => {
+ const updatedDestinations = [...destinations];
+ updatedDestinations[index] = { ...updatedDestinations[index], [field]: value };
+ setDestinations(updatedDestinations);
+ };
+console.log(destinations);
+
+ return (
+
+ );
+};
+
+export default CommonLayout;
diff --git a/src/app/page.tsx b/src/app/page.tsx
index 3392b92..b1ce7c3 100644
--- a/src/app/page.tsx
+++ b/src/app/page.tsx
@@ -15,6 +15,7 @@ import SectionClientSay from "@/components/SectionClientSay";
import SectionCustomTour from "@/components/SectionCustomTour";
import axios from "axios";
import axiosInstance from "@/components/api/axios";
+import TourSuggestion from "@/components/TourSuggestion";
const DEMO_CATS: TaxonomyType[] = [
{
@@ -175,7 +176,7 @@ function PageHome() {
-
= () => {
const [confirmPassword, setConfirmPassword] = useState("");
const [loading, setLoading] = useState(false);
+ const [failed , setFailed ] =useState("")
+
const { errors, validateForm } = useFormValidation();
const countryCodeHandler = (e) => {
@@ -51,17 +53,20 @@ const PageSignUp: FC = () => {
setForm(form);
try {
const response = await axiosInstance.get(
- `/api/account/verification/?range_phone=${countryCode}&phone_number=${phoneNumber}`
+ `/api/account/verfication/?range_phone=${countryCode}&phone_number=${phoneNumber}`
);
setMethod(response.data.verification_method);
- router.push("/signup/methods");
+ router.push("/signup/methodes");
} catch (error) {
console.error("Error fetching data:", error);
+ setFailed(error.message)
+
} finally {
setLoading(false);
}
} else {
console.log("Form has errors:", errors);
+
}
};
@@ -137,6 +142,7 @@ const PageSignUp: FC = () => {
{errors.confirmPassword}
)}
+ {failed && (
{failed}
)}
= ({}) => {
//
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();
@@ -125,22 +141,10 @@ const ListingStayDetailPage: FC = ({}) => {
const renderSection2 = () => {
return (
-
Stay information
+
{details?.title}
{details?.description}
-
-
-
- There is a private bathroom with bidet in all units, along with a
- hairdryer and free toiletries.
-
-
-
- The Symphony 9 Tam Coc offers a terrace. Both a bicycle rental
- service and a car rental service are available at the accommodation,
- while cycling can be enjoyed nearby.
-
);
@@ -150,142 +154,133 @@ const ListingStayDetailPage: FC = ({}) => {
return (
-
Amenities
-
- {` About the property's amenities and services`}
-
+ Tour Features
{/* 6 */}
-
- {details && details.tour_features?.map((item) => (
-
- {item.title}
-
- ))}
+
+ {details &&
+ details.tour_features?.map((item) => (
+
+
+ {item.title}
+
+ ))}
-
+ {/*
View more 20 amenities
- {renderMotalAmenities()}
+ {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 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 = () => {
return (
{/* HEADING */}
-
Room Rates
-
- Prices may increase on weekends or holidays
-
+ Itinerary
{/* CONTENT */}
-
-
- Monday - Thursday
- $199
-
-
- Monday - Thursday
- $199
-
-
- Friday - Sunday
- $219
-
-
- Rent by month
- -8.34 %
-
-
- Minimum number of nights
- 1 night
-
-
- Max number of nights
- 90 nights
-
+
+ {iteneries?.map((item, index) => (
+
+
+
+ {totalIteneries !== index + 1 && (
+
+ )}
+
+
+
{item?.title}
+
+ {item?.started_at
+ .replaceAll("-", " ")
+ .replaceAll("T", " | ")}
+
+
{item.summary}
+
+
+ ))}
@@ -462,36 +457,7 @@ const ListingStayDetailPage: FC
= ({}) => {
return (
{/* HEADING */}
-
Things to know
-
-
- {/* CONTENT */}
-
-
Cancellation policy
-
- Refund 50% of the booking value when customers cancel the room
- within 48 hours after successful booking and 14 days before the
- check-in time.
- Then, cancel the room 14 days before the check-in time, get a 50%
- refund of the total amount paid (minus the service fee).
-
-
-
-
- {/* CONTENT */}
-
-
Check-in time
-
-
- Check-in
- 08:00 am - 12:00 am
-
-
- Check-out
- 02:00 pm - 04:00 pm
-
-
-
+
Travel guide and tips
{/* CONTENT */}
@@ -499,17 +465,13 @@ const ListingStayDetailPage: FC
= ({}) => {
Special Note
- {details && details.travel_tips.map((item)=>(
- <>
-
- {item.title}
-
-
- {item.description}
-
- >
- ))
- }
+ {details &&
+ details.travel_tips.map((item) => (
+
+ {item.title}
+ {item.description}
+
+ ))}
@@ -558,7 +520,7 @@ const ListingStayDetailPage: FC = ({}) => {
{/* SUBMIT */}
-
Reserve
+
Reserve
);
};
@@ -570,8 +532,8 @@ const ListingStayDetailPage: FC = ({}) => {
+
= ({}) => {
alt=""
sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 50vw"
/>
-
+ {/*
+
+
*/}
- {details?.images.filter((_, i) => i >= 1 && i < 5).map((item, index) => (
-
= 3 ? "hidden sm:block" : ""
- }`}
- >
-
-
-
-
- {/* OVERLAY */}
+ {details?.images
+ .filter((_, i) => i >= 1 && i < 5)
+ .map((item, index) => (
-
- ))}
+ key={index}
+ className={`relative rounded-md sm:rounded-xl overflow-hidden ${
+ index >= 3 ? "hidden sm:block" : ""
+ }`}
+ >
+
+
+
+ }
+ >
+ {(closeModal) => (
+
+
+
+ )}
+
+
+ {/* OVERLAY */}
+
+ ))}
{/* = ({}) => {
{/* CONTENT */}
- {renderSection1()}
+ {/* {renderSection1()} */}
{renderSection2()}
{renderSection3()}
+ {renderSection8()}
{renderSection4()}
- {renderSection5()}
+ {/* {renderSection5()} */}
{/* {renderSection6()} */}
- {renderSection7()}
- {renderSection8()}
+ {/* {renderSection7()} */}
{/* SIDEBAR */}
diff --git a/src/components/CardCategory1.tsx b/src/components/CardCategory1.tsx
index 6217b23..52d08dc 100644
--- a/src/components/CardCategory1.tsx
+++ b/src/components/CardCategory1.tsx
@@ -1,52 +1,47 @@
import React, { FC } from "react";
import { TaxonomyType } from "@/data/types";
+import convertNumbThousand from "@/utils/convertNumbThousand";
import Link from "next/link";
import Image from "next/image";
-export interface CardCategory1Props {
+
+export interface CardCategory3Props {
className?: string;
taxonomy: TaxonomyType;
- size?: "large" | "normal";
}
-const CardCategory1: FC = ({
+const CardCategory3: FC = ({
className = "",
- size = "normal",
taxonomy,
}) => {
- const { count, name, href = "/", thumbnail } = taxonomy;
+ const { count, title, href = "/", thumbnail } = taxonomy;
+
+
return (
-
+
+
-
+
+
-
-
+
- {name}
+ {title}
-
- {count} Articles
-
-
+
);
};
-export default CardCategory1;
+export default CardCategory3;
diff --git a/src/components/StayCard2.tsx b/src/components/StayCard2.tsx
index aad5474..6a2476b 100644
--- a/src/components/StayCard2.tsx
+++ b/src/components/StayCard2.tsx
@@ -57,7 +57,7 @@ const StayCard2: FC
= ({
/>
-
+ {/* */}
{ }
>
diff --git a/src/components/TourSuggestion.tsx b/src/components/TourSuggestion.tsx
new file mode 100644
index 0000000..9b6c162
--- /dev/null
+++ b/src/components/TourSuggestion.tsx
@@ -0,0 +1,252 @@
+"use client";
+
+import React, { FC, useContext, useEffect, useState } from "react";
+import { TaxonomyType } from "@/data/types";
+import CardCategory3 from "@/components/CardCategory3";
+import CardCategory4 from "@/components/CardCategory4";
+import CardCategory5 from "@/components/CardCategory5";
+import Heading from "@/shared/Heading";
+import { AnimatePresence, motion, MotionConfig } from "framer-motion";
+import { useSwipeable } from "react-swipeable";
+import PrevBtn from "./PrevBtn";
+import NextBtn from "./NextBtn";
+import { variants } from "@/utils/animationVariants";
+import { useWindowSize } from "react-use";
+import axiosInstance from "./api/axios";
+import { Context } from "./contexts/tourDetails";
+import CardCategory1 from "./CardCategory1";
+
+export interface TourSuggestionProps {
+ className?: string;
+ itemClassName?: string;
+ heading?: string;
+ subHeading?: string;
+ categories?: TaxonomyType[];
+ categoryCardType?: "card3" | "card4" | "card5";
+ itemPerRow?: 4 | 5;
+ sliderStyle?: "style1" | "style2";
+}
+
+const DEMO_CATS: TaxonomyType[] = [
+ {
+ id: "1",
+ href: "/listing-stay-map",
+ name: "Nature House",
+ taxonomy: "category",
+ count: 17288,
+ thumbnail:
+ "https://images.pexels.com/photos/2581922/pexels-photo-2581922.jpeg?auto=compress&cs=tinysrgb&dpr=3&h=750&w=1260",
+ },
+ {
+ id: "2",
+ href: "/listing-stay-map",
+ name: "Wooden house",
+ taxonomy: "category",
+ count: 2118,
+ thumbnail:
+ "https://images.pexels.com/photos/2351649/pexels-photo-2351649.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=750&w=1260",
+ },
+ {
+ id: "3",
+ href: "/listing-stay-map",
+ name: "Houseboat",
+ taxonomy: "category",
+ count: 36612,
+ thumbnail:
+ "https://images.pexels.com/photos/962464/pexels-photo-962464.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=750&w=1260",
+ },
+ {
+ id: "4",
+ href: "/listing-stay-map",
+ name: "Farm House",
+ taxonomy: "category",
+ count: 18188,
+ thumbnail:
+ "https://images.pexels.com/photos/248837/pexels-photo-248837.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=750&w=1260",
+ },
+ {
+ id: "5",
+ href: "/listing-stay-map",
+ name: "Dome House",
+ taxonomy: "category",
+ count: 22288,
+ thumbnail:
+ "https://images.pexels.com/photos/3613236/pexels-photo-3613236.jpeg?auto=compress&cs=tinysrgb&dpr=1&w=500",
+ },
+ {
+ id: "6",
+ href: "/listing-stay-map",
+ name: "Dome House",
+ taxonomy: "category",
+ count: 188288,
+ thumbnail:
+ "https://images.pexels.com/photos/14534337/pexels-photo-14534337.jpeg?auto=compress&cs=tinysrgb&w=1600&lazy=load",
+ },
+ {
+ id: "7",
+ href: "/listing-stay-map",
+ name: "Wooden house",
+ taxonomy: "category",
+ count: 2118,
+ thumbnail:
+ "https://images.pexels.com/photos/2351649/pexels-photo-2351649.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=750&w=1260",
+ },
+ {
+ id: "8",
+ href: "/listing-stay-map",
+ name: "Wooden Dome",
+ taxonomy: "category",
+ count: 515,
+ thumbnail:
+ "https://images.pexels.com/photos/9039238/pexels-photo-9039238.jpeg?auto=compress&cs=tinysrgb&w=1600&lazy=load",
+ },
+];
+
+const TourSuggestion: FC = ({
+ heading = "Countries ",
+ subHeading = "Popular places to recommends for you",
+ className = "",
+ itemClassName = "",
+ categories = DEMO_CATS,
+ itemPerRow = 5,
+ categoryCardType = "card3",
+ sliderStyle = "style1",
+}) => {
+ const [currentIndex, setCurrentIndex] = useState(0);
+ const [direction, setDirection] = useState(0);
+ const [numberOfItems, setNumberOfitem] = useState(0);
+ const { tours } = useContext(Context);
+
+
+ const windowWidth = useWindowSize().width;
+ useEffect(() => {
+ if (windowWidth < 320) {
+ return setNumberOfitem(1);
+ }
+ if (windowWidth < 500) {
+ return setNumberOfitem(itemPerRow - 3);
+ }
+ if (windowWidth < 1024) {
+ return setNumberOfitem(itemPerRow - 2);
+ }
+ if (windowWidth < 1280) {
+ return setNumberOfitem(itemPerRow - 1);
+ }
+
+ setNumberOfitem(itemPerRow);
+ }, [itemPerRow, windowWidth]);
+
+
+ // useEffect(() => {
+ // axiosInstance
+ // .get("/api/tours/")
+ // .then((response) => {
+ // setTours(response.data);
+ // })
+ // .catch((error) => {
+ // console.error("Error fetching data:", error);
+ // });
+ // }, []);
+
+
+ function changeItemId(newVal: number) {
+ if (newVal > currentIndex) {
+ setDirection(1);
+ } else {
+ setDirection(-1);
+ }
+ setCurrentIndex(newVal);
+ }
+
+ const handlers = useSwipeable({
+ onSwipedLeft: () => {
+ if (currentIndex < tours.results?.length - 1) {
+ changeItemId(currentIndex + 1);
+ }
+ },
+ onSwipedRight: () => {
+ if (currentIndex > 0) {
+ changeItemId(currentIndex - 1);
+ }
+ },
+ trackMouse: true,
+ });
+
+ // const renderCard = (item: TaxonomyType) => {
+ // switch (categoryCardType) {
+ // case "card3":
+ // return ;
+ // case "card4":
+ // return ;
+ // case "card5":
+ // return ;
+ // default:
+ // return ;
+ // }
+ // };
+
+ if (!numberOfItems) return null;
+
+ return (
+
+
+ {heading}
+
+
+
+
+
+
+ {tours.results?.map((item, indx) => (
+
+
+
+ ))}
+
+
+
+
+ {currentIndex ? (
+
changeItemId(currentIndex - 1)}
+ className="w-9 h-9 xl:w-12 xl:h-12 text-lg absolute -left-3 xl:-left-6 top-1/3 -translate-y-1/2 z-[1]"
+ />
+ ) : null}
+
+ {tours.results?.length > currentIndex + numberOfItems ? (
+ changeItemId(currentIndex + 1)}
+ className="w-9 h-9 xl:w-12 xl:h-12 text-lg absolute -right-3 xl:-right-6 top-1/3 -translate-y-1/2 z-[1]"
+ />
+ ) : null}
+
+
+
+ );
+};
+
+export default TourSuggestion;
diff --git a/src/hooks/FormValidation.ts b/src/hooks/FormValidation.ts
index 172c4a3..7283368 100644
--- a/src/hooks/FormValidation.ts
+++ b/src/hooks/FormValidation.ts
@@ -5,33 +5,27 @@ import { useState } from 'react';
const useFormValidation = () => {
const [errors, setErrors] = useState({});
- // Validate form fields
const validateForm = (form) => {
let newErrors = {};
- // Full Name validation
if (!form.name) {
newErrors.name = 'Full Name is required';
}
- // Country Code validation: must be a number and up to 3 digits
if (!form.countryCode || !/^\d{1,3}$/.test(form.countryCode)) {
newErrors.countryCode = 'Country Code must be a number with up to 3 digits';
}
- // Phone Number validation: must be a number and not empty
if (!form.phoneNumber || !/^\d+$/.test(form.phoneNumber)) {
newErrors.phoneNumber = 'Phone Number is required and must be a number';
}
- // Password validation
if (!form.password) {
newErrors.password = 'Password is required';
- } else if (form.password.length < 6) {
- newErrors.password = 'Password must be at least 6 characters';
+ } else if (form.password.length < 8) {
+ newErrors.password = 'Password must be at least 8 characters';
}
- // Confirm Password validation
if (!form.confirmPassword) {
newErrors.confirmPassword = 'Confirm Password is required';
} else if (form.password !== form.confirmPassword) {
@@ -39,7 +33,6 @@ const useFormValidation = () => {
}
setErrors(newErrors);
- // Return true if no errors
return Object.keys(newErrors).length === 0;
};
diff --git a/src/hooks/passengerValidation.ts b/src/hooks/passengerValidation.ts
new file mode 100644
index 0000000..a748d0c
--- /dev/null
+++ b/src/hooks/passengerValidation.ts
@@ -0,0 +1,26 @@
+// Validation function
+const validatePassenger = (passenger) => {
+ console.log(passenger);
+
+ const errors = {};
+ if (!passenger.fullname.trim()) {
+ errors.fullName = "Full Name is required";
+ }
+ if (!passenger.birthdate) {
+ errors.date = "Date of Birth is required";
+ }
+ if (!passenger.phone_number.trim()) {
+ errors.number = "Phone Number is required";
+ }
+ if (!passenger.passport_number.trim()) {
+ errors.passport = "Passport Number is required";
+ }
+ if (!passenger.passport_image) {
+ errors.image = "Passport image is required";
+ }
+ return errors;
+ };
+
+
+ export default validatePassenger
+
\ No newline at end of file
diff --git a/src/shared/Navigation/Navigation.tsx b/src/shared/Navigation/Navigation.tsx
index 2e88e9d..17f5a83 100644
--- a/src/shared/Navigation/Navigation.tsx
+++ b/src/shared/Navigation/Navigation.tsx
@@ -10,7 +10,7 @@ function Navigation() {
{NAVIGATION_DEMO.map((item) => (
))}
- Custoum Tour
+ Custoum Tour
);
}
diff --git a/src/shared/popUp.tsx b/src/shared/popUp.tsx
new file mode 100644
index 0000000..4da4ab2
--- /dev/null
+++ b/src/shared/popUp.tsx
@@ -0,0 +1,74 @@
+"use client";
+
+import { useEffect, useState } from "react";
+
+const ConfirmModal = ({ children, lable }) => {
+ const [isOpen, setIsOpen] = useState(false);
+
+ const openModal = () => {
+ setIsOpen(true);
+ document.body.classList.add("overflow-y-hidden");
+ };
+
+ const closeModal = () => {
+ setIsOpen(false);
+ document.body.classList.remove("overflow-y-hidden");
+ };
+
+ // Handle Esc key to close modal
+ const handleKeyDown = (event) => {
+ if (event.key === "Escape") {
+ closeModal();
+ }
+ };
+
+ // Add event listener for keydown on mount and cleanup on unmount
+ useEffect(() => {
+ document.addEventListener("keydown", handleKeyDown);
+ return () => {
+ document.removeEventListener("keydown", handleKeyDown);
+ };
+ }, []);
+
+ return (
+
+
+ {lable}
+
+
+ {isOpen && (
+
+
+
+
+ {children(closeModal)}
+
+
+ )}
+
+ );
+};
+
+export default ConfirmModal;