sina_sajjadi
2 months ago
18 changed files with 1098 additions and 445 deletions
-
2src/app/(account-pages)/(components)/Nav.tsx
-
88src/app/(account-pages)/account-savelists/page.tsx
-
34src/app/(account-pages)/account/page.tsx
-
69src/app/(account-pages)/my-trips/page.tsx
-
111src/app/add-listing/[[...stepIndex]]/PageAddListing1.tsx
-
128src/app/add-listing/[[...stepIndex]]/page.tsx
-
175src/app/custom-trip/page.tsx
-
3src/app/page.tsx
-
4src/app/signup/methodes/page.tsx
-
10src/app/signup/page.tsx
-
331src/app/tours/[slug]/page.tsx
-
51src/components/CardCategory1.tsx
-
2src/components/StayCard2.tsx
-
252src/components/TourSuggestion.tsx
-
11src/hooks/FormValidation.ts
-
26src/hooks/passengerValidation.ts
-
2src/shared/Navigation/Navigation.tsx
-
74src/shared/popUp.tsx
@ -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 ( |
|||
<div className="space-y-6 sm:space-y-8"> |
|||
<div> |
|||
<h2 className="text-3xl font-semibold">Save lists</h2> |
|||
</div> |
|||
<div className="w-14 border-b border-neutral-200 dark:border-neutral-700"></div> |
|||
|
|||
<div> |
|||
<Tab.Group> |
|||
<Tab.List className="flex space-x-1 overflow-x-auto"> |
|||
{categories.map((item) => ( |
|||
<Tab key={item} as={Fragment}> |
|||
{({ selected }) => ( |
|||
<button |
|||
className={`flex-shrink-0 block !leading-none font-medium px-5 py-2.5 text-sm sm:text-base sm:px-6 sm:py-3 capitalize rounded-full focus:outline-none ${ |
|||
selected |
|||
? "bg-secondary-900 text-secondary-50 " |
|||
: "text-neutral-500 dark:text-neutral-400 dark:hover:text-neutral-100 hover:text-neutral-900 hover:bg-neutral-100 dark:hover:bg-neutral-800" |
|||
} `}
|
|||
> |
|||
{item} |
|||
</button> |
|||
)} |
|||
</Tab> |
|||
))} |
|||
</Tab.List> |
|||
<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"> |
|||
{DEMO_STAY_LISTINGS.filter((_, i) => i < 8).map((stay) => ( |
|||
<StayCard key={stay.id} data={stay} /> |
|||
))} |
|||
</div> |
|||
<div className="flex mt-11 justify-center items-center"> |
|||
<ButtonSecondary>Show me more</ButtonSecondary> |
|||
</div> |
|||
</Tab.Panel> |
|||
<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"> |
|||
{DEMO_EXPERIENCES_LISTINGS.filter((_, i) => i < 8).map( |
|||
(stay) => ( |
|||
<ExperiencesCard key={stay.id} data={stay} /> |
|||
) |
|||
)} |
|||
</div> |
|||
<div className="flex mt-11 justify-center items-center"> |
|||
<ButtonSecondary>Show me more</ButtonSecondary> |
|||
</div> |
|||
</Tab.Panel> |
|||
<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"> |
|||
{DEMO_CAR_LISTINGS.filter((_, i) => i < 8).map((stay) => ( |
|||
<CarCard key={stay.id} data={stay} /> |
|||
))} |
|||
</div> |
|||
<div className="flex mt-11 justify-center items-center"> |
|||
<ButtonSecondary>Show me more</ButtonSecondary> |
|||
</div> |
|||
</Tab.Panel> |
|||
</Tab.Panels> |
|||
</Tab.Group> |
|||
</div> |
|||
</div> |
|||
); |
|||
}; |
|||
|
|||
return renderSection1(); |
|||
}; |
|||
|
|||
export default AccountSavelists; |
@ -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 ( |
|||
<div className="space-y-6 sm:space-y-8"> |
|||
<div> |
|||
<h2 className="text-3xl font-semibold">Save lists</h2> |
|||
</div> |
|||
<div className="w-14 border-b border-neutral-200 dark:border-neutral-700"></div> |
|||
|
|||
<div> |
|||
<Tab.Group> |
|||
<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) => ( |
|||
<StayCard2 key={stay.id} data={stay} /> |
|||
))) : (<h1 className="text-2xl"> You have no trips </h1>)} |
|||
</div> |
|||
<div className="flex mt-11 justify-center items-center"> |
|||
</div> |
|||
</Tab.Panel> |
|||
</Tab.Panels> |
|||
</Tab.Group> |
|||
</div> |
|||
</div> |
|||
); |
|||
}; |
|||
|
|||
return renderSection1(); |
|||
}; |
|||
|
|||
export default MyTrips; |
@ -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<CommonLayoutProps> = () => { |
|||
const [countries, setCountries] = useState<Country[]>([]); |
|||
const [startCity, setStartCity] = useState<string>(""); |
|||
const [endCity, setEndCity] = useState<string>(""); |
|||
const [transport, setTransport] = useState<any[]>([]); |
|||
const [hotel, setHotel] = useState<any[]>([]); |
|||
const [selectedHotel, setSelectedHotel] = useState<string>(""); |
|||
const [selectedTransport, setSelectedTransport] = useState<string>(""); |
|||
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 ( |
|||
<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">Custom Trip</h2> |
|||
<div className="w-14 border-b border-neutral-200 dark:border-neutral-700"></div> |
|||
|
|||
<h3 className="text-[#BD3F3F] font-medium">Guide</h3> |
|||
<p> |
|||
First, write the origin of your departure, then choose the first |
|||
destination of your trip, the number of nights of stay and the |
|||
means of travel, then choose your travel destinations if you |
|||
wish. |
|||
</p> |
|||
<div className="w-14 border-b border-neutral-200 dark:border-neutral-700"></div> |
|||
|
|||
<div className="space-y-8"> |
|||
<p className="text-bronze text-xs">Begin your trip</p> |
|||
<FormItem label="" desc=""> |
|||
<Select value={startCity} onChange={(e) => setStartCity(e.target.value)}> |
|||
<option value="">Select City</option> |
|||
{countries.flatMap((country) => |
|||
country.city.map((city) => ( |
|||
<option key={city.name} value={city.slug}> |
|||
{`${country.name} - ${city.name}`} |
|||
</option> |
|||
)) |
|||
)} |
|||
</Select> |
|||
</FormItem> |
|||
<div className="flex w-[100%]"> |
|||
<FormItem className="w-[50%]" label="Start Date"> |
|||
<Input type="date" /> |
|||
</FormItem> |
|||
<FormItem className="w-[50%] ml-4" label="Number Of Passengers"> |
|||
<Input type="number" /> |
|||
</FormItem> |
|||
</div> |
|||
</div> |
|||
<div className="w-14 border-b border-neutral-200 dark:border-neutral-700"></div> |
|||
|
|||
{destinations.map((destination, index) => ( |
|||
<div key={index} className="space-y-8"> |
|||
<p className="text-bronze text-xs">Destination {index + 1}</p> |
|||
<FormItem label="" desc=""> |
|||
<Select |
|||
value={destination.endCity} |
|||
onChange={(e) => handleDestinationChange(index, 'endCity', e.target.value)} |
|||
> |
|||
<option value="">Select City</option> |
|||
{countries.flatMap((country) => |
|||
country.city.map((city) => ( |
|||
<option key={city.name} value={city.slug}> |
|||
{`${country.name} - ${city.name}`} |
|||
</option> |
|||
)) |
|||
)} |
|||
</Select> |
|||
</FormItem> |
|||
<FormItem label="Transportation" desc=""> |
|||
<Select |
|||
value={destination.transport} |
|||
onChange={(e) => handleDestinationChange(index, 'transport', e.target.value)} |
|||
> |
|||
<option value="">Select Transport</option> |
|||
{transport?.map((item) => |
|||
<option key={item.transportaion.id} value={item.transportaion.name}> |
|||
{item.transportaion.name} |
|||
</option> |
|||
)} |
|||
</Select> |
|||
</FormItem> |
|||
<FormItem label="Hotel" desc=""> |
|||
<Select |
|||
value={destination.hotel} |
|||
onChange={(e) => handleDestinationChange(index, 'hotel', e.target.value)} |
|||
> |
|||
<option value="">Select Hotel</option> |
|||
{hotel?.map((hotelItem) => |
|||
<option key={hotelItem.name} value={hotelItem.name}> |
|||
{hotelItem.name} |
|||
</option> |
|||
)} |
|||
</Select> |
|||
</FormItem> |
|||
<div className="flex w-[100%]"> |
|||
<FormItem className="w-[50%]" label="Duration"> |
|||
<Input type="number" /> |
|||
</FormItem> |
|||
<FormItem className="w-[50%] ml-4" label="Finish date"> |
|||
<Input type="number" /> |
|||
</FormItem> |
|||
</div> |
|||
</div> |
|||
))} |
|||
</div> |
|||
|
|||
<div className="flex mt-4 justify-end space-x-5"> |
|||
<ButtonPrimary onClick={addDestination} type="button">Add Destination</ButtonPrimary> |
|||
<ButtonPrimary type="button">Continue</ButtonPrimary> |
|||
</div> |
|||
</form> |
|||
</div> |
|||
</div> |
|||
); |
|||
}; |
|||
|
|||
export default CommonLayout; |
51
src/components/CardCategory1.tsx
File diff suppressed because it is too large
View File
File diff suppressed because it is too large
View File
@ -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<TourSuggestionProps> = ({ |
|||
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 <CardCategory3 taxonomy={item} />;
|
|||
// case "card4":
|
|||
// return <CardCategory4 taxonomy={item} />;
|
|||
// case "card5":
|
|||
// return <CardCategory5 taxonomy={item} />;
|
|||
// default:
|
|||
// return <CardCategory3 taxonomy={item} />;
|
|||
// }
|
|||
// };
|
|||
|
|||
if (!numberOfItems) return null; |
|||
|
|||
return ( |
|||
<div className={`nc-SectionSliderNewCategories ${className}`}> |
|||
<Heading desc={subHeading} isCenter={sliderStyle === "style2"}> |
|||
{heading} |
|||
</Heading> |
|||
<MotionConfig |
|||
transition={{ |
|||
x: { type: "spring", stiffness: 300, damping: 30 }, |
|||
opacity: { duration: 0.2 }, |
|||
}} |
|||
> |
|||
<div className={`relative flow-root`} {...handlers}> |
|||
<div className={`flow-root overflow-hidden rounded-xl`}> |
|||
<motion.ul |
|||
initial={false} |
|||
className="relative whitespace-nowrap -mx-2 xl:-mx-4" |
|||
> |
|||
<AnimatePresence initial={false} custom={direction}> |
|||
{tours.results?.map((item, indx) => ( |
|||
<motion.li |
|||
className={`relative inline-block px-2 xl:px-4 ${itemClassName}`} |
|||
custom={direction} |
|||
initial={{ |
|||
x: `${(currentIndex - 1) * -100}%`, |
|||
}} |
|||
animate={{ |
|||
x: `${currentIndex * -100}%`, |
|||
}} |
|||
variants={variants(200, 1)} |
|||
key={indx} |
|||
style={{ |
|||
width: `calc(1/${numberOfItems} * 100%)`, |
|||
}} |
|||
> |
|||
<CardCategory1 taxonomy={item} /> |
|||
</motion.li> |
|||
))} |
|||
</AnimatePresence> |
|||
</motion.ul> |
|||
</div> |
|||
|
|||
{currentIndex ? ( |
|||
<PrevBtn |
|||
style={{ transform: "translate3d(0, 0, 0)" }} |
|||
onClick={() => 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 ? ( |
|||
<NextBtn |
|||
style={{ transform: "translate3d(0, 0, 0)" }} |
|||
onClick={() => 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} |
|||
</div> |
|||
</MotionConfig> |
|||
</div> |
|||
); |
|||
}; |
|||
|
|||
export default TourSuggestion; |
@ -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 |
|||
|
@ -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 ( |
|||
<div > |
|||
<div onClick={openModal}> |
|||
{lable} |
|||
</div> |
|||
|
|||
{isOpen && ( |
|||
<div |
|||
id="modelConfirm" |
|||
className="fixed z-50 inset-0 bg-gray-900 bg-opacity-60 overflow-y-auto h-full w-full flex items-center justify-center" |
|||
> |
|||
<div className="relative mx-auto shadow-xl rounded-md bg-white max-w-4xl w-full p-4"> |
|||
<div className="flex justify-end p-2"> |
|||
<button |
|||
onClick={closeModal} |
|||
type="button" |
|||
className="text-gray-400 z-40 bg-transparent hover:bg-gray-200 hover:text-gray-900 rounded-lg text-sm p-1.5 ml-auto inline-flex items-center" |
|||
> |
|||
<svg |
|||
className="w-5 h-5" |
|||
fill="currentColor" |
|||
viewBox="0 0 20 20" |
|||
xmlns="http://www.w3.org/2000/svg" |
|||
> |
|||
<path |
|||
fillRule="evenodd" |
|||
d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z" |
|||
clipRule="evenodd" |
|||
></path> |
|||
</svg> |
|||
</button> |
|||
</div> |
|||
|
|||
{children(closeModal)} |
|||
</div> |
|||
</div> |
|||
)} |
|||
</div> |
|||
); |
|||
}; |
|||
|
|||
export default ConfirmModal; |
Write
Preview
Loading…
Cancel
Save
Reference in new issue