diff --git a/package-lock.json b/package-lock.json index 9cf304f..929369c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -33,6 +33,7 @@ "react-hooks-global-state": "^2.1.0", "react-icons": "^5.3.0", "react-swipeable": "^7.0.0", + "react-toastify": "^10.0.5", "react-use": "^17.4.0", "react-use-keypress": "^1.3.1", "sass": "^1.62.1", @@ -1286,6 +1287,14 @@ "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==", "license": "MIT" }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "engines": { + "node": ">=6" + } + }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -4256,6 +4265,18 @@ "react": "^16.8.3 || ^17 || ^18" } }, + "node_modules/react-toastify": { + "version": "10.0.5", + "resolved": "https://registry.npmjs.org/react-toastify/-/react-toastify-10.0.5.tgz", + "integrity": "sha512-mNKt2jBXJg4O7pSdbNUfDdTsK9FIdikfsIE/yUCxbAEXl4HMyJaivrVFcn3Elvt5xvCQYhUZm+hqTIu1UXM3Pw==", + "dependencies": { + "clsx": "^2.1.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + } + }, "node_modules/react-universal-interface": { "version": "0.6.2", "resolved": "https://registry.npmjs.org/react-universal-interface/-/react-universal-interface-0.6.2.tgz", diff --git a/package.json b/package.json index c9bc2ff..14b6411 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "scripts": { "dev": "next dev", "lint:es": "eslint --ext .js,.jsx .", - "lint:fix": "eslint --fix --ext .js,.jsx .", + "lint:fix": "eslint --fix --ext .js,.jsx .", "build": "next build", "start": "next start", "lint": "next lint", @@ -13,7 +13,6 @@ "clear-all": "rm -rf .next node_modules", "re-start": "rm -rf .next node_modules && yarn install && yarn dev", "re-build": "rm -rf .next node_modules && yarn install && yarn build" - }, "dependencies": { "@headlessui/react": "^1.7.14", @@ -41,6 +40,7 @@ "react-hooks-global-state": "^2.1.0", "react-icons": "^5.3.0", "react-swipeable": "^7.0.0", + "react-toastify": "^10.0.5", "react-use": "^17.4.0", "react-use-keypress": "^1.3.1", "sass": "^1.62.1", diff --git a/src/app/(account-pages)/account/page.tsx b/src/app/(account-pages)/account/page.tsx index 32a856f..e65cfca 100644 --- a/src/app/(account-pages)/account/page.tsx +++ b/src/app/(account-pages)/account/page.tsx @@ -10,6 +10,7 @@ import axiosInstance from "@/components/api/axios"; import { useRouter } from "next/navigation"; import { useUserContext } from "@/components/contexts/userContext"; import getImageURL from "@/components/api/getImageURL"; +import { toast } from "react-toastify"; export interface AccountPageProps {} @@ -70,12 +71,13 @@ const AccountPage: FC = () => { }); if (response.status === 204) { clerUser(); + toast.success("Your Delete account was successful") } else { setError("Something went wrong"); } } catch (error: unknown) { if (error instanceof Error) { - setError(error.message); + toast.error(error.message); } else { setError("An unknown error occurred"); } @@ -86,6 +88,7 @@ const AccountPage: FC = () => { const signOutHandler = (): void => { clerUser(); + toast.success("Your sign out was successful") }; const changeHandler = async (): Promise => { @@ -111,6 +114,7 @@ const AccountPage: FC = () => { } ); if (response.status === 200) { + toast.success("Updated successfully") setUser({ ...user, avatar: response.data.avatar, diff --git a/src/app/(account-pages)/passengers-list/page.tsx b/src/app/(account-pages)/passengers-list/page.tsx index d34edf5..60762fc 100644 --- a/src/app/(account-pages)/passengers-list/page.tsx +++ b/src/app/(account-pages)/passengers-list/page.tsx @@ -5,10 +5,11 @@ import PassengerTable from "./PassengerTable"; import { IoPersonAddOutline } from "react-icons/io5"; import axiosInstance from "@/components/api/axios"; import Link from "next/link"; +import { useUserContext } from "@/components/contexts/userContext"; const PassengersList = () => { const [passengers , setPassenger ] = useState([]) - +const {user} = useUserContext() useEffect(()=>{ axiosInstance.get("/api/account/passengers/" ,{ diff --git a/src/app/(client-components)/(Header)/MainNav1.tsx b/src/app/(client-components)/(Header)/MainNav1.tsx index b406366..77c7eaa 100644 --- a/src/app/(client-components)/(Header)/MainNav1.tsx +++ b/src/app/(client-components)/(Header)/MainNav1.tsx @@ -8,7 +8,7 @@ import ButtonPrimary from "@/shared/ButtonPrimary"; import MenuBar from "@/shared/MenuBar"; import SwitchDarkMode from "@/shared/SwitchDarkMode"; import HeroSearchForm2MobileFactory from "../(HeroSearchForm2Mobile)/HeroSearchForm2MobileFactory"; -import { MdOutlineLanguage } from "react-icons/md"; +import { MdOutlineLanguage , MdOutlineCardTravel } from "react-icons/md"; import Avatar from "@/shared/Avatar"; import Link from "next/link"; import { useUserContext } from "@/components/contexts/userContext"; @@ -24,8 +24,8 @@ const MainNav1: FC = ({ className = "" }) => { return (
-
- +
+
@@ -40,6 +40,9 @@ const MainNav1: FC = ({ className = "" }) => {
+ + +
diff --git a/src/app/(client-components)/(HeroSearchForm)/(stay-search-form)/StayDatesRangeInput.tsx b/src/app/(client-components)/(HeroSearchForm)/(stay-search-form)/StayDatesRangeInput.tsx index 13b9d22..9590207 100644 --- a/src/app/(client-components)/(HeroSearchForm)/(stay-search-form)/StayDatesRangeInput.tsx +++ b/src/app/(client-components)/(HeroSearchForm)/(stay-search-form)/StayDatesRangeInput.tsx @@ -1,12 +1,8 @@ "use client"; -import React, { Fragment, useState, FC, useContext } from "react"; -import { Popover, Transition } from "@headlessui/react"; +import React, { FC, useContext } from "react"; +import { Popover } from "@headlessui/react"; import { CalendarIcon } from "@heroicons/react/24/outline"; -import DatePickerCustomHeaderTwoMonth from "@/components/DatePickerCustomHeaderTwoMonth"; -import DatePickerCustomDay from "@/components/DatePickerCustomDay"; -import DatePicker from "react-datepicker"; -import ClearDataButton from "../ClearDataButton"; import { Context } from "@/components/contexts/tourDetails"; export interface StayDatesRangeInputProps { @@ -18,101 +14,33 @@ const StayDatesRangeInput: FC = ({ className = "[ lg:nc-flex-2 ]", fieldClassName = "[ nc-hero-field-padding ]", }) => { - const { details} = useContext(Context); - - - - const onChangeDate = (dates: [Date | null, Date | null]) => { - const [start, end] = dates; - setStartDate(start); - setEndDate(end); - }; - - // const renderInput = () => { - // return ( - // <> - //
- // - //
- //
- // - // {startDate ? startDate.toLocaleDateString("en-US", { - // month: "short", - // day: "2-digit", - // }) : "Tour period"} - // {" - " + - // endDate?.toLocaleDateString("en-US", { - // month: "short", - // day: "2-digit", - // }) || ""} - // - // - // {"Start - End"} - // - //
- // - // ); - // }; + const { details } = useContext(Context); return ( {({ open }) => ( - <> -
-
- -
-
- - {details?.started_at.replaceAll("-", "/") || "Tour period"} - {details?.ended_at && " - " + details?.ended_at.replaceAll("-", "/")} - - - {"Start - End"} - -
+
+
+
- - {/* {open && ( -
- )} */} - - {/* - -
- ( - - )} - renderDayContents={(day, date) => ( - - )} - /> -
-
-
*/} - +
+ + {details?.started_at.replaceAll("-", "/") || "Tour period"} + {details?.ended_at && " - " + details?.ended_at.replaceAll("-", "/")} + + + {"Start - End"} + +
+
)} ); }; + export default StayDatesRangeInput; diff --git a/src/app/(client-components)/(HeroSearchForm)/ButtonSubmit.tsx b/src/app/(client-components)/(HeroSearchForm)/ButtonSubmit.tsx index 17fda48..f3bca60 100644 --- a/src/app/(client-components)/(HeroSearchForm)/ButtonSubmit.tsx +++ b/src/app/(client-components)/(HeroSearchForm)/ButtonSubmit.tsx @@ -1,33 +1,21 @@ import { PathName } from "@/routers/types"; import Link from "next/link"; import React, { FC } from "react"; +import { FaArrowRight } from "react-icons/fa"; interface Props { href?: PathName; + className? : string } -const ButtonSubmit: FC = ({ href = "/listing-stay-map" }) => { +const ButtonSubmit: FC = ({ className , href = "/listing-stay-map" }) => { return ( - Search - - - + ); }; diff --git a/src/app/(client-components)/(HeroSearchForm)/GuestsInput.tsx b/src/app/(client-components)/(HeroSearchForm)/GuestsInput.tsx index bcda547..004800f 100644 --- a/src/app/(client-components)/(HeroSearchForm)/GuestsInput.tsx +++ b/src/app/(client-components)/(HeroSearchForm)/GuestsInput.tsx @@ -25,7 +25,7 @@ const GuestsInput: FC = ({ buttonSubmitHref = "/listing-stay-map", hasButtonSubmit = true, }) => { - const [guestAdultsInputValue, setGuestAdultsInputValue] = useState(2); + const [guestAdultsInputValue, setGuestAdultsInputValue] = useState(0); const { passengers, details , setPassengers } = useContext(Context); @@ -80,7 +80,7 @@ const GuestsInput: FC = ({ {/* BUTTON SUBMIT OF FORM */} {hasButtonSubmit && (
- +
)}
diff --git a/src/app/(client-components)/(HeroSearchForm)/LocationInput.tsx b/src/app/(client-components)/(HeroSearchForm)/LocationInput.tsx index 2be657c..db7e63a 100644 --- a/src/app/(client-components)/(HeroSearchForm)/LocationInput.tsx +++ b/src/app/(client-components)/(HeroSearchForm)/LocationInput.tsx @@ -17,7 +17,7 @@ export interface LocationInputProps { const LocationInput: FC = ({ autoFocus = false, - placeHolder = "Tours", + placeHolder = "", desc = "Select your tour", className = "nc-flex-1.5", divHideVerticalLineClass = "left-10 -right-0.5", @@ -69,9 +69,6 @@ const LocationInput: FC = ({ const renderRecentSearches = () => { return ( <> -

- Tours -

{tours.results.map((item) => ( = ({ ); }; - const renderSearchValue = () => { - return ( - <> - {tours.results.filter((item) => { - return item.title.includes(value); - }) ? ( - tours.results - .filter((item) => { - return item.title.includes(value); - }) - .map((item) => ( - handleSelectLocation(item)} - key={item.id} - className="flex px-4 sm:px-8 items-center space-x-3 sm:space-x-4 py-4 hover:bg-neutral-100 dark:hover:bg-neutral-700 cursor-pointer" - > - - {item.title} - - - )) - ) : ( -

No match found

- )} - - ); - }; return (
@@ -129,18 +99,11 @@ const LocationInput: FC = ({
- { - setValue(e.currentTarget.value); - }} - ref={inputRef} - /> - - {!!value ? placeHolder : desc} + >{value} + + {!!value ? placeHolder : desc} {value && showPopover && ( = ({ {showPopover && (
- {value ? renderSearchValue() : renderRecentSearches()} + {renderRecentSearches()}
)}
diff --git a/src/app/(listing-detail)/(components)/MobileFooterSticky.tsx b/src/app/(listing-detail)/(components)/MobileFooterSticky.tsx index 7f48336..a6ad8fc 100644 --- a/src/app/(listing-detail)/(components)/MobileFooterSticky.tsx +++ b/src/app/(listing-detail)/(components)/MobileFooterSticky.tsx @@ -1,46 +1,81 @@ -import React, { useState } from "react"; +import React, { useContext, useState } from "react"; import ModalSelectDate from "@/components/ModalSelectDate"; import ButtonPrimary from "@/shared/ButtonPrimary"; import converSelectedDateToString from "@/utils/converSelectedDateToString"; import ModalReserveMobile from "./ModalReserveMobile"; +import { useParams } from "next/navigation"; +import GuestsInput from "../listing-experiences-detail/GuestsInput"; +import ClearDataButton from "@/app/(client-components)/(HeroSearchForm)/ClearDataButton"; +import { UserPlusIcon } from "@heroicons/react/24/solid"; +import { useToursContext } from "@/components/contexts/tourDetails"; +import NcInputNumber from "@/components/NcInputNumber"; -const MobileFooterSticky = () => { +const MobileFooterSticky = ({ data }) => { const [startDate, setStartDate] = useState( new Date("2023/02/06") ); - const [endDate, setEndDate] = useState(new Date("2023/02/23")); + const { setPassengers, passengers } = useToursContext(); + + const [guestAdultsInputValue, setGuestAdultsInputValue] = + useState(passengers); + + const handleChangeData = (value: number) => { + let newValue = { + guestAdults: guestAdultsInputValue, + }; + setGuestAdultsInputValue(value); + newValue.guestAdults = value; + setPassengers(value); + }; + console.log(data); + const r = /-?(\d+)$/; + + const id: string = useParams().slug.match(r)[1]; + const totalGuests = guestAdultsInputValue; return (
-
+
+
+ handleChangeData(value)} + max={10} + min={1} + label="Passsengers" + desc="Ages 13 or above" + /> +
+
+
- $311 - - /night - + {data?.price * passengers} - ( - - {converSelectedDateToString([startDate, endDate])} - + <> + + Reserve + + )} />
- ( - - Reserve - - )} - />
); diff --git a/src/app/(server-components)/SectionHero.tsx b/src/app/(server-components)/SectionHero.tsx index 5e43b05..bf81450 100644 --- a/src/app/(server-components)/SectionHero.tsx +++ b/src/app/(server-components)/SectionHero.tsx @@ -16,14 +16,15 @@ const SectionHero: FC = ({ className = "" }) => {

- Hotel, car & experiences + Begin your spiritual adventure

- Accompanying us, you have a trip full of experiences. With Chisfis, - booking accommodation, resort villas, hotels + Plan your pilgrimage with ease. Find the best accommodations, + transportation, and guided experiences to Shia shrines around the + world - - Start your search + + Start your journey
diff --git a/src/app/about/page.tsx b/src/app/about/page.tsx index a3d7815..8f85261 100644 --- a/src/app/about/page.tsx +++ b/src/app/about/page.tsx @@ -21,10 +21,10 @@ const PageAbout: FC = ({}) => { rightImg={rightImg} heading="👋 About Us." btnText="" - subHeading="We’re impartial and independent, and every day we create distinctive, world-class programmes and content which inform, educate and entertain millions of people in the around the world." + subHeading="We are a passionate team dedicated to crafting unforgettable travel experiences for explorers and dreamers alike. From serene escapes on tropical beaches to adrenaline-fueled adventures in exotic locales, we curate journeys that are as unique as you are. Join us and let’s explore the world, one adventure at a time!" /> - + {/* */}
diff --git a/src/app/add-listing/[[...stepIndex]]/page.tsx b/src/app/add-listing/[[...stepIndex]]/page.tsx index 68409ca..bf1ce2d 100644 --- a/src/app/add-listing/[[...stepIndex]]/page.tsx +++ b/src/app/add-listing/[[...stepIndex]]/page.tsx @@ -9,6 +9,7 @@ import { Context } from "@/components/contexts/tourDetails"; import { useRouter } from "next/navigation"; import axiosInstance from "@/components/api/axios"; import { useUserContext } from "@/components/contexts/userContext"; +import { toast } from "react-toastify"; export interface CommonLayoutProps { children: React.ReactNode; @@ -42,6 +43,21 @@ const CommonLayout: FC = ({ params }) => { const backtHref = () => (index > 1 ? setIndex((prev) => prev - 1) : index); const nextBtnText = index === totalPassengers ? "Save Passengers" : "Continue"; + useEffect(()=>{ + Object.values(errors).forEach((error) => { + toast.error(error, { + position: "top-right", + autoClose: 5000, + hideProgressBar: false, + closeOnClick: true, + pauseOnHover: true, + draggable: true, + progress: undefined, + theme: "light", + }); + }); + } , [errors]) + const sendPassengers = async () => { try { const response = await axiosInstance.post( @@ -61,17 +77,17 @@ const CommonLayout: FC = ({ params }) => { } ); console.log(response); - setRedirecting(true); // Set redirecting state to true + setRedirecting(true); } catch (error) { backtHref(); console.error("Error submitting passengers:", error); } }; - // Use useEffect to handle the redirect after setting redirecting to true useEffect(() => { if (redirecting) { router.replace("/my-trips"); + toast.success("Purchased Successfully ") } }, [redirecting, router]); diff --git a/src/app/add-new-passenger/page.tsx b/src/app/add-new-passenger/page.tsx index f9a7393..5b6884f 100644 --- a/src/app/add-new-passenger/page.tsx +++ b/src/app/add-new-passenger/page.tsx @@ -35,12 +35,16 @@ const CommonLayout: FC = () => { image: "", }); + const [loading , setLoading] = useState(false) + const handleFileChange = async (e: React.ChangeEvent) => { + setLoading(true) const file = e.target.files[0]; if (file) { const image = await getImageURL(file); setPassenger((prev) => ({ ...prev, image: image.url })); setErrors((prev) => ({ ...prev, image: "" })); // Clear image error + setLoading(false) } }; @@ -207,6 +211,7 @@ const CommonLayout: FC = () => { placeholder="Passport" /> {errors.image &&

{errors.image}

} + {loading &&

Loading ...

}
diff --git a/src/app/blog/[...slug]/Survey.tsx b/src/app/blog/[...slug]/Survey.tsx new file mode 100644 index 0000000..682d3b3 --- /dev/null +++ b/src/app/blog/[...slug]/Survey.tsx @@ -0,0 +1,42 @@ +"use client"; + +import { useState } from "react"; +import { BiDislike, BiLike } from "react-icons/bi"; + +function Survey() { + const [checked, setChecked] = useState(false); + + return ( +
+ {!checked ? ( + <> +

Was this article helpfull ?

+
+
{ + setChecked(true); + }} + className="cursor-pointer hover:bg-green-200 nc-Tag flex items-center gap-1 bg-white text-sm text-neutral-600 dark:text-neutral-300 py-2 rounded-lg border border-neutral-100 md:px-4 dark:bg-neutral-700 dark:border-neutral-700 hover:border-neutral-200 dark:hover:border-neutral-6000 mr-2 mb-2" + > +

Yes

+ +
+
{ + setChecked(true); + }} + className="cursor-pointer hover:bg-red-300 nc-Tag flex items-center gap-1 bg-white text-sm text-neutral-600 dark:text-neutral-300 py-2 rounded-lg border border-neutral-100 md:px-4 dark:bg-neutral-700 dark:border-neutral-700 hover:border-neutral-200 dark:hover:border-neutral-6000 mr-2 mb-2" + > +

No

+ +
+
+ + ) : ( +

Thank you

+ )} +
+ ); +} + +export default Survey; diff --git a/src/app/blog/[...slug]/page.tsx b/src/app/blog/[...slug]/page.tsx index 2a8b0ad..85c24d4 100644 --- a/src/app/blog/[...slug]/page.tsx +++ b/src/app/blog/[...slug]/page.tsx @@ -12,6 +12,8 @@ import Image from "next/image"; import travelhero2Image from "@/images/travelhero2.png"; import Link from "next/link"; import { Route } from "@/routers/types"; +import { BiLike , BiDislike } from "react-icons/bi"; +import Survey from "./Survey"; const Page = ({ params, @@ -39,7 +41,7 @@ const Page = ({
-
+ {/*
-
-
+
*/} + {/*
-
+
*/}
@@ -184,30 +186,6 @@ const Page = ({ ); }; - const renderAuthor = () => { - return ( -
-
- -
- - WRITEN BY - -

- Fones Mimi -

- - There’s no stopping the tech giant. Apple now opens its 100th - store in China.There’s no stopping the tech giant. - - Readmore - - -
-
-
- ); - }; const renderCommentForm = () => { return ( @@ -298,9 +276,9 @@ const Page = ({ {renderContent()} {renderTags()}
- {renderAuthor()} - {renderCommentForm()} - {renderCommentLists()} + + {/* {renderCommentForm()} */} + {/* {renderCommentLists()} */}
diff --git a/src/app/custom-history/page.tsx b/src/app/custom-history/page.tsx index 5a7c9c0..1320cba 100644 --- a/src/app/custom-history/page.tsx +++ b/src/app/custom-history/page.tsx @@ -47,7 +47,7 @@ const PageAddListing10: FC = () => { const selected = tours.find((item)=>{ return JSON.parse(item.detail) === tour }) - console.log(JSON.parse(tours[0].detail ) == tour.title[1].title ,JSON.parse(tours[0].detail ) , tour) + console.log(tour[1].title , tours) } console.log(tours); @@ -56,9 +56,7 @@ console.log(tours);

Custom Trip History

- 2023 Dec 02 | 16:17
-

Successfully Registered

{toursDetail.length > 0 ? ( toursDetail.map((item, index) => (
= () => { ); setIsFormValid(isValid); }; - const validateContinue = () => { + // 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 && - passengers && - destinations.length > 0 && - destinations.every( - (destination) => - destination.endCity && - destination.transport && - destination.hotel && - destination.duration > 0 && - destination.finishDate - ); + passengers > 0; + + console.log(destinations.length, isFormValid, isValid); + setIsContinueValid(isValid); - }; + }, [destinations, startCity, startDate, passengers, isFormValid]); const addDestination = () => { setDestinations([ @@ -172,7 +177,7 @@ const CommonLayout: FC = () => { endCity: "", transport: "", hotel: "", - duration: 1, + duration: 0, finishDate: "", transportCost: 0, hotelCost: 0, @@ -219,7 +224,7 @@ const CommonLayout: FC = () => { setDestinations(updatedDestinations); validateForm(); - validateContinue(); + // validateContinue(); }; console.log(destinations); @@ -272,7 +277,7 @@ const CommonLayout: FC = () => { Accept: "application/json", }, }); - console.log("Response:", response.data); + toast.success("Successfully Registered") router.push("/custom-history"); } catch (error) { console.error("Error sending trip details:", error); diff --git a/src/app/faq/page.tsx b/src/app/faq/page.tsx new file mode 100644 index 0000000..3f5fe9e --- /dev/null +++ b/src/app/faq/page.tsx @@ -0,0 +1,93 @@ +"use client" + +import React, { useState } from "react"; +import Table from "./Table"; + +interface FAQItem { + question: string; + answer: string; +} + +const FAQ: React.FC = () => { + const [activeIndex, setActiveIndex] = useState(null); + + const faqs: FAQItem[] = [ + { + question: "How can I book a tour on your site?", + answer: + "To book a tour, simply select your desired destination from the 'List of Tours', choose your dates, and follow the steps to complete the booking process." + }, + { + question: "Can I customize my tour?", + answer: + "Yes, our 'Customize Tour' feature allows you to tailor your journey based on your preferences. Click on 'Customize Tour' at the top of the page to start personalizing your experience." + }, + { + question: "What payment methods do you accept?", + answer: + "We accept all major credit cards, PayPal, and bank transfers. You can choose your preferred method during the checkout process." + }, + { + question: "How do I know my booking is confirmed?", + answer: + "Once you complete the payment process, you will receive a confirmation email with all the details of your booking. You can also view your booking details in your account dashboard." + }, + { + question: "Can I cancel or modify my booking?", + answer: + "Yes, you can cancel or modify your booking from your account. Please note that cancellations must be made at least 24 hours before the tour starts to be eligible for a refund." + }, + { + question: "Are there any hidden fees?", + answer: + "No, all fees are transparent and shown upfront before you complete your booking. We ensure there are no hidden fees." + }, + { + question: "Do you offer group discounts?", + answer: + "Yes, we offer group discounts for larger bookings. Please contact our support team for more information on group rates." + }, + { + question: "Is travel insurance included in the booking?", + answer: + "Yes, all our tour packages include basic travel insurance. You can choose to upgrade your insurance package during the checkout process." + }, + { + question: "What happens if the tour is canceled by the provider?", + answer: + "If the tour is canceled by the provider due to unforeseen circumstances, you will receive a full refund or the option to reschedule your tour." + }, + { + question: "How can I contact customer support?", + answer: + "You can contact customer support by clicking the 'Contact Us' button at the bottom of the page or by visiting our support section." + } + ]; + + const toggleFAQ = (index: number) => { + if (activeIndex === index) { + setActiveIndex(null); // Close the active one + } else { + setActiveIndex(index); // Open the clicked one + } + }; + + return ( +
+

Frequently Asked Questions

+

Have Questions ? We are here to help you !

+
+ {faqs.map((faq, index) => ( + toggleFAQ(index)} + /> + ))} + + + ); +}; + +export default FAQ; diff --git a/src/app/faq/table.tsx b/src/app/faq/table.tsx new file mode 100644 index 0000000..c52548f --- /dev/null +++ b/src/app/faq/table.tsx @@ -0,0 +1,40 @@ +"use client" + +import React from "react"; +import { motion } from "framer-motion"; + +interface TableProps { + faq: { + question: string; + answer: string; + }; + isActive: boolean; + onClick: () => void; +} + +const Table: React.FC = ({ faq, isActive, onClick }) => { + return ( +
+

+ {faq.question} + {isActive ? "-" : "+"} +

+ +

+ {faq.answer} +

+
+
+ ); +}; + +export default Table; + diff --git a/src/app/forgot-password/page.tsx b/src/app/forgot-password/page.tsx index d889809..0bc6cad 100644 --- a/src/app/forgot-password/page.tsx +++ b/src/app/forgot-password/page.tsx @@ -1,65 +1,96 @@ "use client"; -import React, { FC, useContext, useState } from "react"; -import facebookSvg from "@/images/Facebook.svg"; -import twitterSvg from "@/images/Twitter.svg"; -import googleSvg from "@/images/Google.svg"; +import React, { FC, useState, useEffect } from "react"; import Input from "@/shared/Input"; import ButtonPrimary from "@/shared/ButtonPrimary"; -import Image from "next/image"; import Link from "next/link"; import axiosInstance from "@/components/api/axios"; -import { useUserContext } from "@/components/contexts/userContext"; -import useFormValidation from "@/hooks/FormValidation"; import { useRouter } from "next/navigation"; +import { useUserContext } from "@/components/contexts/userContext"; +import { ToastContainer, toast } from "react-toastify"; +import "react-toastify/dist/ReactToastify.css"; export interface PageSignUpProps {} -const PageSignUp: FC = ({}) => { - - const router = useRouter() - - const { setForm , setMethod } = useUserContext(); +const PageSignUp: FC = () => { + const router = useRouter(); + const { setForm, setMethod } = useUserContext(); const [name, setName] = useState(''); - const [countryCode, setCountryCode] = useState(''); + const [countryCode, setCountryCode] = useState('968'); const [phoneNumber, setPhoneNumber] = useState(''); const [password, setPassword] = useState(''); const [confirmPassword, setConfirmPassword] = useState(''); - const { errors, validateForm } = useFormValidation(); + const [errors, setErrors] = useState<{ phoneNumber?: string; password?: string; confirmPassword?: string }>({}); - const countryCodeHandler = (e) => { + const countryCodeHandler = (e: React.ChangeEvent) => { if (e.target.value.length <= 3) { setCountryCode(e.target.value); } }; - const submitHandler = async ( - countryCode, - phoneNumber, - password, - confirmPassword - ) => { - const form = { - verification_methodes : "" , - method : "reset" , - countryCode, - phoneNumber, - password, - confirmPassword, - }; + const validateForm = () => { + const newErrors: { phoneNumber?: string; password?: string; confirmPassword?: string } = {}; + + if (!phoneNumber) newErrors.phoneNumber = "Phone number is required."; + if (!password) newErrors.password = "Password is required."; + if (password !== confirmPassword) newErrors.confirmPassword = "Passwords do not match."; - await axiosInstance - .get(`/api/account/verfication/?range_phone=${countryCode}&phone_number=${phoneNumber}`) - .then((response) => { - form.verification_methodes = response.data.verification_method - setForm(form) - router.push("/signup/methodes") - }) - .catch((error) => { - console.error("Error fetching data:", error); - }); + setErrors(newErrors); + return Object.keys(newErrors).length === 0; // Return true if no errors + }; + useEffect(() => { + Object.values(errors).forEach((error) => { + toast.error(error, { + position: "top-right", + autoClose: 5000, + hideProgressBar: false, + closeOnClick: true, + pauseOnHover: true, + draggable: true, + progress: undefined, + theme: "light", + }); + }); + }, [errors]); + const submitHandler = async () => { + setErrors({}); // Clear previous errors + + + if (!validateForm()) { + return; // Prevent submission if there are validation errors + } + + try { + const response = await axiosInstance.get( + `/api/account/verfication/?range_phone=${countryCode}&phone_number=${phoneNumber}` + ); + + const form = { + verification_methodes: response.data.verification_method, + method: "reset", + countryCode, + phoneNumber, + password, + confirmPassword, + }; + + setForm(form); + router.push("/signup/methodes"); + } catch (error) { + console.error("Error fetching data:", error); + toast.error("An error occurred during verification!", { + position: "top-right", + autoClose: 5000, + hideProgressBar: false, + closeOnClick: true, + pauseOnHover: true, + draggable: true, + progress: undefined, + theme: "light", + }); + } }; return ( @@ -70,74 +101,55 @@ const PageSignUp: FC = ({}) => {
{/* FORM */} -
-
+ ); }; diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 6d3a110..d3d6e73 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -11,6 +11,7 @@ import "rc-slider/assets/index.css"; import Footer from "@/components/Footer"; import FooterNav from "@/components/FooterNav"; import { UserProvider } from "@/components/contexts/userContext"; +import { ToastContainer } from "react-toastify"; const poppins = Poppins({ subsets: ["latin"], @@ -28,15 +29,27 @@ export default function RootLayout({ return ( - - - - - {children} - -
- - + + + + + + {children} + +
+ + ); diff --git a/src/app/login/page.tsx b/src/app/login/page.tsx index b8cd078..ce678f4 100644 --- a/src/app/login/page.tsx +++ b/src/app/login/page.tsx @@ -7,6 +7,8 @@ import Link from "next/link"; import axiosInstance from "@/components/api/axios"; import { useRouter } from "next/navigation"; import { useUserContext } from "@/components/contexts/userContext"; +import { ToastContainer, toast } from "react-toastify"; +import "react-toastify/dist/ReactToastify.css"; export interface PageLoginProps {} @@ -16,8 +18,8 @@ const PageLogin: FC = () => { const [phoneNumber, setPhoneNumber] = useState(""); const [password, setPassword] = useState(""); - const [countryCode, setCountryCode] = useState(""); - const [error, setError] = useState(""); + const [countryCode, setCountryCode] = useState("968"); + const [errors, setErrors] = useState<{ phoneNumber?: string; password?: string }>({}); // Redirect to home if the user is already logged in useEffect(() => { @@ -26,14 +28,48 @@ const PageLogin: FC = () => { } }, [user, router]); + useEffect(()=>{ + Object.values(errors).forEach((error) => { + toast.error(error, { + position: "top-right", + autoClose: 5000, + hideProgressBar: false, + closeOnClick: true, + pauseOnHover: true, + draggable: true, + progress: undefined, + theme: "light", + }); + }); + } , [errors]) + const countryCodeHandler = (e: React.ChangeEvent) => { if (e.target.value.length <= 3) { setCountryCode(e.target.value); } }; + const validateForm = () => { + const newErrors: { phoneNumber?: string; password?: string } = {}; + + if (!phoneNumber) { + newErrors.phoneNumber = "Phone number is required."; + } + if (!password) { + newErrors.password = "Password is required."; + } + + setErrors(newErrors); + return Object.keys(newErrors).length === 0; // Return true if no errors + }; + const submitHandler = async () => { - setError(""); + setErrors({}); // Clear previous errors + + if (!validateForm()) { + + return; // Prevent submission if there are validation errors + } try { const response = await axiosInstance.post(`/api/account/login/`, { @@ -42,15 +78,43 @@ const PageLogin: FC = () => { }); if (response.status === 201) { + toast.success("Your login was successful") setUser(response.data); } else { - setError("Something went wrong"); + toast.error("Something went wrong", { + position: "top-right", + autoClose: 5000, + hideProgressBar: false, + closeOnClick: true, + pauseOnHover: true, + draggable: true, + progress: undefined, + theme: "light", + }); } } catch (error) { if (error instanceof Error) { - setError(error.message); + toast.error(error.message, { + position: "top-right", + autoClose: 5000, + hideProgressBar: false, + closeOnClick: true, + pauseOnHover: true, + draggable: true, + progress: undefined, + theme: "light", + }); } else { - setError("An unknown error occurred"); + toast.error("An unknown error occurred", { + position: "top-right", + autoClose: 5000, + hideProgressBar: false, + closeOnClick: true, + pauseOnHover: true, + draggable: true, + progress: undefined, + theme: "light", + }); } } }; @@ -63,43 +127,47 @@ const PageLogin: FC = () => {
e.preventDefault()}> -
+ ); }; diff --git a/src/app/page.tsx b/src/app/page.tsx index b1ce7c3..12de2cc 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -16,6 +16,7 @@ import SectionCustomTour from "@/components/SectionCustomTour"; import axios from "axios"; import axiosInstance from "@/components/api/axios"; import TourSuggestion from "@/components/TourSuggestion"; +import SectionDowloadApp from "./(home)/SectionDowloadApp"; const DEMO_CATS: TaxonomyType[] = [ { @@ -170,8 +171,8 @@ function PageHome() { {/* */} - - + {/* */} +
@@ -180,8 +181,8 @@ function PageHome() { categories={DEMO_CATS_2} categoryCardType="card4" itemPerRow={4} - heading="Tour Suggestions" - subHeading="Popular places to stay that Chisfis recommends for you" + heading="Countries" + subHeading="Popular places recommended for you" sliderStyle="style2" /> @@ -196,17 +197,17 @@ function PageHome() { {/* */} -
+ {/*
-
+
*/} - + /> */} {/* */} diff --git a/src/app/signup/methodes/page.tsx b/src/app/signup/methodes/page.tsx index 2ba409a..964426c 100644 --- a/src/app/signup/methodes/page.tsx +++ b/src/app/signup/methodes/page.tsx @@ -16,11 +16,9 @@ function SelectMethods() { const [error, setError] = useState(""); const [loading, setLoading] = useState(false); - // Initialize 'enabled' with default values const [enabled, setEnabled] = useState({ watsapp: false, sms: false }); useEffect(() => { - // Safely access 'form.verification_methods' and update 'enabled' setEnabled({ watsapp: form.verification_methodes?.includes("watsapp") ?? false, sms: form.verification_methodes?.includes("sms") ?? false, diff --git a/src/app/signup/otp-code/page.tsx b/src/app/signup/otp-code/page.tsx index f8a0f1e..df987ba 100644 --- a/src/app/signup/otp-code/page.tsx +++ b/src/app/signup/otp-code/page.tsx @@ -5,6 +5,7 @@ import ButtonPrimary from "@/shared/ButtonPrimary"; import axiosInstance from "@/components/api/axios"; import { useUserContext } from "@/components/contexts/userContext"; import { useRouter } from "next/navigation"; +import { toast } from "react-toastify"; export interface PageSignUpProps {} @@ -68,11 +69,12 @@ const PageSignUp: FC = () => { if (response.status === 201) { setUser(response.data); + toast.success("Your Sign In was successful") } else { - setError("Something went wrong. Please try again."); + toast.error("Something went wrong. Please try again."); } } catch (error: any) { // Cast to 'any' or a specific error type - setError(error.response?.data?.message || "An error occurred."); + toast.error(error.response?.data?.message || "An error occurred."); } }; diff --git a/src/app/signup/page.tsx b/src/app/signup/page.tsx index 5c6923e..ef1b628 100644 --- a/src/app/signup/page.tsx +++ b/src/app/signup/page.tsx @@ -8,24 +8,16 @@ import axiosInstance from "@/components/api/axios"; import { useUserContext } from "@/components/contexts/userContext"; import useFormValidation from "@/hooks/FormValidation"; import { useRouter } from "next/navigation"; +import { ToastContainer, toast } from "react-toastify"; +import "react-toastify/dist/ReactToastify.css"; export interface PageSignUpProps {} const PageSignUp: FC = () => { const router = useRouter(); - const { user, setForm, setMethod } = useUserContext(); - - // Redirect if user is already logged in - useEffect(() => { - if (Object.keys(user).length) { - router.replace("/"); - } - }, [user, router]); - - - + const { user, setForm } = useUserContext(); const [name, setName] = useState(""); - const [countryCode, setCountryCode] = useState(""); + const [countryCode, setCountryCode] = useState("968"); const [phoneNumber, setPhoneNumber] = useState(""); const [password, setPassword] = useState(""); const [confirmPassword, setConfirmPassword] = useState(""); @@ -40,6 +32,27 @@ const PageSignUp: FC = () => { } }; + useEffect(() => { + if (Object.keys(user).length) { + router.replace("/"); + } + }, [user, router]); + + useEffect(()=>{ + Object.values(errors).forEach((error) => { + toast.error(error, { + position: "top-right", + autoClose: 5000, + hideProgressBar: false, + closeOnClick: true, + pauseOnHover: true, + draggable: true, + progress: undefined, + theme: "light", + }); + }); + } , [errors]) + const submitHandler = async () => { const form = { name, @@ -66,8 +79,6 @@ const PageSignUp: FC = () => { } finally { setLoading(false); } - } else { - console.log("Form has errors:", errors); } }; @@ -78,7 +89,10 @@ const PageSignUp: FC = () => { Signup
-
e.preventDefault()}> + e.preventDefault()} + >
+
); }; diff --git a/src/app/tours/SectionGridFilterCard.tsx b/src/app/tours/SectionGridFilterCard.tsx index 58e7f5a..c8d16a7 100644 --- a/src/app/tours/SectionGridFilterCard.tsx +++ b/src/app/tours/SectionGridFilterCard.tsx @@ -7,8 +7,14 @@ import TabFilters from "./TabFilters"; import Heading2 from "@/shared/Heading2"; import StayCard2 from "@/components/StayCard2"; import { Context } from "@/components/contexts/tourDetails"; -import { useParams, useSearchParams } from "next/navigation"; -import { useRouter } from "next/navigation"; +import { useSearchParams } from "next/navigation"; +import { motion, AnimatePresence, MotionConfig } from "framer-motion"; +import { useSwipeable } from "react-swipeable"; +import ButtonPrimary from "@/shared/ButtonPrimary"; +import NextBtn from "@/components/NextBtn"; +import PrevBtn from "@/components/PrevBtn"; +import { variants } from "@/utils/animationVariants"; +import { useWindowSize } from "react-use"; export interface SectionGridFilterCardProps { className?: string; @@ -22,24 +28,23 @@ const SectionGridFilterCard: FC = ({ const { countries, tours } = useContext(Context); const [countryTours, setCountryTours] = useState(tours.results || []); const [checked, setChecked] = useState<{ [key: string]: boolean }>({}); - - const searchParams = useSearchParams() -console.log(searchParams); + const searchParams = useSearchParams(); + const [currentIndex, setCurrentIndex] = useState(0); + const [numberOfItems, setNumberOfItems] = useState(4); // Adjust as needed + const [direction, setDirection] = useState(0); // Get the list of selected countries - const filteredCountries = Object.keys(checked).filter((countryName) => checked[countryName]); - - useEffect(()=>{ - const country = searchParams.get("country") - if (searchParams.has("country")){ - setChecked({ - [country] : true - }) + const filteredCountries = Object.keys(checked).filter( + (countryName) => checked[countryName] + ); + const windowWidth = useWindowSize().width; + + useEffect(() => { + const country = searchParams.get("country"); + if (country) { + setChecked({ [country]: true }); } - } , []) -console.log(checked); - - + }, []); useEffect(() => { if (!tours.results) return; @@ -60,22 +65,108 @@ console.log(checked); setCountryTours(filteredTours); } + setCurrentIndex(0) }, [checked, countries, tours.results]); + useEffect(() => { + if (windowWidth < 320) { + return setNumberOfItems(1); + } + if (windowWidth < 500) { + return setNumberOfItems(2); + } + if (windowWidth < 1024) { + return setNumberOfItems(3); + } + if (windowWidth < 1280) { + return setNumberOfItems(4); + } + + setNumberOfItems(4); + }, [windowWidth]); + + function changeItemId(newVal: number) { + if (newVal > currentIndex) { + setDirection(1); + } else { + setDirection(-1); + } + setCurrentIndex(newVal); + } + + const handlers = useSwipeable({ + onSwipedLeft: () => { + if (currentIndex < countryTours.length - numberOfItems) { + changeItemId(currentIndex + 1); + } + }, + onSwipedRight: () => { + if (currentIndex > 0) { + changeItemId(currentIndex - 1); + } + }, + trackMouse: true, + }); + return ( -
+
-
- {countryTours.length > 0 ? ( - countryTours.map((stay) => ) - ) : ( -

No tours Available

- )} -
+ +
+
+ + + {countryTours.map((stay, indx) => ( + + + + ))} + + +
+ + {currentIndex > 0 && ( + 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]" + /> + )} + + {countryTours.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]" + /> + )} +
+
+ + {countryTours.length === 0 &&

No tours Available

}
); }; diff --git a/src/app/tours/[slug]/page.tsx b/src/app/tours/[slug]/page.tsx index 7227e75..d1aa8ac 100644 --- a/src/app/tours/[slug]/page.tsx +++ b/src/app/tours/[slug]/page.tsx @@ -25,6 +25,7 @@ import axiosInstance from "@/components/api/axios"; import { FaCheckCircle } from "react-icons/fa"; import ConfirmModal from "@/shared/popUp"; import { IoPersonAddOutline } from "react-icons/io5"; +import MobileFooterSticky from "@/app/(listing-detail)/(components)/MobileFooterSticky"; export interface ListingStayDetailPageProps {} @@ -265,13 +266,15 @@ const ListingStayDetailPage: FC = ({}) => { {iteneries?.map((item, index) => (
-
+
{totalIteneries !== index + 1 && (
)}
-

{item?.title}

+

+ {item?.title} +

{item?.started_at .replaceAll("-", " ") @@ -468,7 +471,9 @@ const ListingStayDetailPage: FC = ({}) => { {details && details.travel_tips.map((item) => (
  • -

    {item.title}

    +

    + {item.title} +

    {item.description}

  • ))} @@ -520,7 +525,15 @@ const ListingStayDetailPage: FC = ({}) => {
    {/* SUBMIT */} - Reserve + + Reserve +
    ); }; @@ -530,10 +543,7 @@ const ListingStayDetailPage: FC = ({}) => { {/* HEADER */}
    -
    - +
    = ({}) => { alt="" sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 50vw" /> - {/*
    + {/*
    = ({}) => { index >= 3 ? "hidden sm:block" : "" }`} > - - -
    - } - > - {(closeModal) => ( -
    - -
    - )} - + + +
    + } + > + {(closeModal) => ( +
    + +
    + )} + {/* OVERLAY */}
    @@ -611,6 +621,7 @@ const ListingStayDetailPage: FC = ({}) => { {renderSection3()} {renderSection8()} {renderSection4()} + {/* {renderSection5()} */} {/* {renderSection6()} */} {/* {renderSection7()} */} @@ -621,6 +632,8 @@ const ListingStayDetailPage: FC = ({}) => {
    {renderSidebar()}
    + +
    ); }; diff --git a/src/app/tours/layout.tsx b/src/app/tours/layout.tsx index bdfd627..552f006 100644 --- a/src/app/tours/layout.tsx +++ b/src/app/tours/layout.tsx @@ -48,7 +48,7 @@ const DetailtLayout = ({ children }: { children: ReactNode }) => { {/* OTHER SECTION */}
    -
    + {/*
    { itemPerRow={5} sliderStyle="style2" /> -
    +
    */} {/* */}
    {/* STICKY FOOTER MOBILE */} -
    ); }; diff --git a/src/components/CardCategory3.tsx b/src/components/CardCategory3.tsx index 5add487..f320895 100644 --- a/src/components/CardCategory3.tsx +++ b/src/components/CardCategory3.tsx @@ -1,4 +1,6 @@ -import React, { FC } from "react"; +"use client" + +import React, { FC, useEffect, useState } from "react"; import { TaxonomyType } from "@/data/types"; import convertNumbThousand from "@/utils/convertNumbThousand"; import Link from "next/link"; @@ -8,14 +10,33 @@ import Image from "next/image"; export interface CardCategory3Props { className?: string; taxonomy: TaxonomyType; + countries : any + tours : any } const CardCategory3: FC = ({ className = "", taxonomy, + countries, + tours }) => { const { count, name, href = "/", thumbnail } = taxonomy; + const [countryTours , setCountryTours] = useState([]) + + useEffect(()=>{ + + const selected = countries?.find((country) => country.name === taxonomy.name); + if (selected) { + + const selectedTours = tours?.results.filter( + (tour) => tour.destination_country === selected?.code + ); + setCountryTours(selectedTours) + } + } , []) + + return ( @@ -41,7 +62,7 @@ const CardCategory3: FC = ({ - {convertNumbThousand(taxonomy.city.length || 0)} PROPERTIES + {convertNumbThousand(countryTours.length || 0)} Tours diff --git a/src/components/SectionGridFeaturePlaces.tsx b/src/components/SectionGridFeaturePlaces.tsx index 42e0e51..aa3103b 100644 --- a/src/components/SectionGridFeaturePlaces.tsx +++ b/src/components/SectionGridFeaturePlaces.tsx @@ -1,20 +1,19 @@ "use client"; import React, { FC, ReactNode, useContext, useEffect, useState } from "react"; -import { DEMO_STAY_LISTINGS } from "@/data/listings"; import { StayDataType } from "@/data/types"; import ButtonPrimary from "@/shared/ButtonPrimary"; import HeaderFilter from "./HeaderFilter"; -import StayCard from "./StayCard"; import StayCard2 from "./StayCard2"; -import axiosInstance from "./api/axios"; +import { AnimatePresence, motion, MotionConfig } from "framer-motion"; +import { useSwipeable } from "react-swipeable"; +import { useWindowSize } from "react-use"; import { Context } from "./contexts/tourDetails"; -import { useRouter } from "next/router"; +import { variants } from "@/utils/animationVariants"; +import PrevBtn from "./PrevBtn"; +import NextBtn from "./NextBtn"; +import { useRouter } from "next/navigation"; -// OTHER DEMO WILL PASS PROPS -const DEMO_DATA: StayDataType[] = DEMO_STAY_LISTINGS.filter((_, i) => i < 8); - -// export interface SectionGridFeaturePlacesProps { stayListings?: StayDataType[]; gridClass?: string; @@ -28,51 +27,76 @@ export interface SectionGridFeaturePlacesProps { const SectionGridFeaturePlaces: FC = ({ gridClass = "", heading = "List of Tours", - subHeading = "Popular places to stay that Chisfis recommends for you", - + subHeading = "Explore tours and accommodations tailored for a spiritual and memorable journey", }) => { + const { countries, tours } = useContext(Context); + const [countryTours, setCountryTours] = useState([]); + const [currentIndex, setCurrentIndex] = useState(0); + const [direction, setDirection] = useState(0); + const [numberOfItems, setNumberOfItems] = useState(0); + const router = useRouter(); + + const windowWidth = useWindowSize().width; + + useEffect(() => { + handleChange("All"); + }, [tours, countries]); + + useEffect(() => { + if (windowWidth < 320) { + return setNumberOfItems(1); + } + if (windowWidth < 500) { + return setNumberOfItems(2); + } + if (windowWidth < 1024) { + return setNumberOfItems(3); + } + if (windowWidth < 1280) { + return setNumberOfItems(4); + } + + setNumberOfItems(4); + }, [windowWidth]); + + const handleChange = (item: string) => { + if (item === "All") { + setCountryTours(tours.results); + } else { + const selected = countries.find((country) => country.name === item); + const selectedTours = tours.results?.filter( + (tour) => tour.destination_country === selected?.code + ); + setCountryTours(selectedTours); + } + }; - const { countries , tours } = useContext(Context); - const [countryTours, setCountryTours] = useState(); - const [limit, setLimit] = useState(7); - + function changeItemId(newVal: number) { + if (newVal > currentIndex) { + setDirection(1); + } else { + setDirection(-1); + } + console.log(`Changing index from ${currentIndex} to ${newVal}`); // Debug log + setCurrentIndex(newVal); + } - - - - useEffect(()=>{ - handleChange("All") - } , [tours , countries]) - - const handleChange = async (item) => { - if (item ==="All"){ - setCountryTours(tours.results); - - } else{ - const selected = countries.find((country) => country.name === item); - const selectedTours = tours.results?.filter( - (tour) => tour.destination_country === selected?.code - ); - setCountryTours(selectedTours); -} - }; - // const renderCard = (stay: StayDataType) => { - // let CardName = StayCard; - // switch (cardType) { - // case "card1": - // CardName = StayCard; - // break; - // case "card2": - // CardName = StayCard2; - // break; - - // default: - // CardName = StayCard; - // } - - // return ; - // }; + const handlers = useSwipeable({ + onSwipedLeft: () => { + if (currentIndex < countryTours?.length - 1) { + changeItemId(currentIndex + 1); + } + }, + onSwipedRight: () => { + if (currentIndex > 0) { + changeItemId(currentIndex - 1); + } + }, + trackMouse: true, + }); + + if (!numberOfItems || !countryTours?.length) return null; return (
    @@ -83,17 +107,58 @@ const SectionGridFeaturePlaces: FC = ({ heading={heading} onClickTab={(item) => handleChange(item)} /> -
    - {countryTours?.length ? ( - countryTours?.map((stay , idx) => limit >= idx && ) - ) : ( -

    No tours Available

    - )} -
    +
    +
    + + + {countryTours.map((stay, indx) => ( + + + + ))} + + +
    + + {currentIndex > 0 && ( + 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]" + /> + )} + + {countryTours.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]" + /> + )} +
    + +
    - {setLimit((prev)=> prev + 8)}}>Show me more + router.push("/tours")}> + Show me more +
    ); diff --git a/src/components/SectionOurFeatures.tsx b/src/components/SectionOurFeatures.tsx index 96dc104..ec05948 100644 --- a/src/components/SectionOurFeatures.tsx +++ b/src/components/SectionOurFeatures.tsx @@ -36,7 +36,7 @@ const SectionOurFeatures: FC = ({
    • - + {/* */} Cost-effective advertising @@ -46,7 +46,7 @@ const SectionOurFeatures: FC = ({
    • - + {/* */} Reach millions with Chisfis @@ -56,7 +56,7 @@ const SectionOurFeatures: FC = ({
    • - + {/* / */} Secure and simple diff --git a/src/components/StayCard2.tsx b/src/components/StayCard2.tsx index 6a2476b..514374d 100644 --- a/src/components/StayCard2.tsx +++ b/src/components/StayCard2.tsx @@ -47,7 +47,7 @@ const StayCard2: FC = ({ href={`/tours/${data?.slug}-${data?.id}`} className="relative w-full" > -
      +
      = ({ const [currentIndex, setCurrentIndex] = useState(0); const [direction, setDirection] = useState(0); const [numberOfItems, setNumberOfitem] = useState(0); - const { tours } = useContext(Context); + const { tours , countries } = useContext(Context); + const windowWidth = useWindowSize().width; @@ -148,6 +149,7 @@ const TourSuggestion: FC = ({ // }); // }, []); +console.log(countries); function changeItemId(newVal: number) { if (newVal > currentIndex) { @@ -160,7 +162,7 @@ const TourSuggestion: FC = ({ const handlers = useSwipeable({ onSwipedLeft: () => { - if (currentIndex < tours.results?.length - 1) { + if (currentIndex < countries?.length - 1) { changeItemId(currentIndex + 1); } }, @@ -205,7 +207,7 @@ const TourSuggestion: FC = ({ className="relative whitespace-nowrap -mx-2 xl:-mx-4" > - {tours.results?.map((item, indx) => ( + {countries?.map((item, indx) => ( = ({ width: `calc(1/${numberOfItems} * 100%)`, }} > - + ))} @@ -236,7 +238,7 @@ const TourSuggestion: FC = ({ /> ) : null} - {tours.results?.length > currentIndex + numberOfItems ? ( + {countries?.length > currentIndex + numberOfItems ? ( changeItemId(currentIndex + 1)} diff --git a/src/components/contexts/tourDetails.tsx b/src/components/contexts/tourDetails.tsx index 94e228b..4b207d3 100644 --- a/src/components/contexts/tourDetails.tsx +++ b/src/components/contexts/tourDetails.tsx @@ -1,6 +1,6 @@ "use client"; import axiosInstance from "../api/axios"; -import React, { createContext, useEffect, useState } from "react"; +import React, { createContext, useContext, useEffect, useState } from "react"; export const Context = createContext(); @@ -51,7 +51,7 @@ export const ContextProvider = ({ children }) => { // console.error("Error fetching data:", error); // }); }; - + return ( { ); }; + +export const useToursContext = () => { + const context = useContext(Context); + if (!context) { + throw new Error("useUserContext must be used within a UserProvider"); + } + return context; +}; diff --git a/src/data/navigation.ts b/src/data/navigation.ts index a63d98a..446f0aa 100644 --- a/src/data/navigation.ts +++ b/src/data/navigation.ts @@ -3,6 +3,7 @@ import ncNanoId from "@/utils/ncNanoId"; import { Route } from "@/routers/types"; import __megamenu from "./jsons/__megamenu.json"; + const megaMenuDemo: MegamenuItem[] = [ { id: ncNanoId(), @@ -168,8 +169,6 @@ export const NAVIGATION_DEMO: NavItemType[] = [ id: ncNanoId(), href: "/", name: "Home", - type: "none", - isNew: true, }, { id: ncNanoId(), @@ -177,26 +176,21 @@ export const NAVIGATION_DEMO: NavItemType[] = [ name: "All Tours", type: "dropdown", children: demoChildMenus, - isNew: true, }, { id: ncNanoId(), href: "/blog", name: "Blogs", - type: "megaMenu", - megaMenu: megaMenuDemo, }, { id: ncNanoId(), - href: "/contact", + href: "/faq", name: "FAQ", - type: "none", }, { id: ncNanoId(), href: "/about", name: "AboutUs", - type: "none", }, // { // id: ncNanoId(), diff --git a/src/shared/Navigation/Navigation.tsx b/src/shared/Navigation/Navigation.tsx index 17f5a83..0c5b008 100644 --- a/src/shared/Navigation/Navigation.tsx +++ b/src/shared/Navigation/Navigation.tsx @@ -3,14 +3,15 @@ import NavigationItem from "./NavigationItem"; import { NAVIGATION_DEMO } from "@/data/navigation"; import ButtonPrimary from "../ButtonPrimary"; import ButtonSecondary from "../ButtonSecondary"; +import Link from "next/link"; function Navigation() { return ( -
        +
          {NAVIGATION_DEMO.map((item) => ( ))} - Custoum Tour + Custom Tour
        ); } diff --git a/src/shared/Navigation/NavigationItem.tsx b/src/shared/Navigation/NavigationItem.tsx index da04757..1be76ff 100644 --- a/src/shared/Navigation/NavigationItem.tsx +++ b/src/shared/Navigation/NavigationItem.tsx @@ -1,5 +1,7 @@ "use client"; +import { useToursContext } from "@/components/contexts/tourDetails"; +import { useUserContext } from "@/components/contexts/userContext"; import { PathName } from "@/routers/types"; import { Popover, Transition } from "@headlessui/react"; import { ChevronDownIcon } from "@heroicons/react/24/solid"; @@ -34,6 +36,7 @@ type NavigationItemWithRouterProps = NavigationItemProps; const NavigationItem: FC = ({ menuItem }) => { const [menuCurrentHovers, setMenuCurrentHovers] = useState([]); + const {countries} = useToursContext() // CLOSE ALL MENU OPENING WHEN CHANGE HISTORY const locationPathName = usePathname(); @@ -160,7 +163,7 @@ const NavigationItem: FC = ({ menuItem }) => { className="sub-menu will-change-transform absolute transform z-10 w-56 top-full left-0" >
          - {menuDropdown.children?.map((i) => { + { countries?.map((i) => { if (i.type) { return renderDropdownMenuNavlinkHasChild(i); } else { @@ -268,6 +271,7 @@ const NavigationItem: FC = ({ menuItem }) => { ); }; +console.log(menuItem.type); switch (menuItem.type) { case "megaMenu": diff --git a/yarn.lock b/yarn.lock index 6d91d05..c0d5092 100644 --- a/yarn.lock +++ b/yarn.lock @@ -616,6 +616,11 @@ client-only@^0.0.1, client-only@0.0.1: resolved "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz" integrity sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA== +clsx@^2.1.0: + version "2.1.1" + resolved "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz" + integrity sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA== + color-convert@^2.0.1: version "2.0.1" resolved "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz" @@ -2353,7 +2358,7 @@ react-datepicker@^4.11.0: react-onclickoutside "^6.12.2" react-popper "^2.3.0" -react-dom@*, "react-dom@^15.5.x || ^16.x || ^17.x || ^18.x", "react-dom@^16 || ^17 || ^18", "react-dom@^16.0.0 || ^17.0.0 || ^18.0.0", "react-dom@^16.8.0 || ^17.0.0 || ^18.0.0", "react-dom@^16.8.0 || ^17 || ^18", "react-dom@^16.9.0 || ^17 || ^18", react-dom@^18.0.0, react-dom@^18.2.0, react-dom@>=16.9.0: +react-dom@*, "react-dom@^15.5.x || ^16.x || ^17.x || ^18.x", "react-dom@^16 || ^17 || ^18", "react-dom@^16.0.0 || ^17.0.0 || ^18.0.0", "react-dom@^16.8.0 || ^17.0.0 || ^18.0.0", "react-dom@^16.8.0 || ^17 || ^18", "react-dom@^16.9.0 || ^17 || ^18", react-dom@^18.0.0, react-dom@^18.2.0, react-dom@>=16.9.0, react-dom@>=18: version "18.2.0" resolved "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz" integrity sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g== @@ -2401,6 +2406,13 @@ react-swipeable@^7.0.0: resolved "https://registry.npmjs.org/react-swipeable/-/react-swipeable-7.0.0.tgz" integrity sha512-NI7KGfQ6gwNFN0Hor3vytYW3iRfMMaivGEuxcADOOfBCx/kqwXE8IfHFxEcxSUkxCYf38COLKYd9EMYZghqaUA== +react-toastify@^10.0.5: + version "10.0.5" + resolved "https://registry.npmjs.org/react-toastify/-/react-toastify-10.0.5.tgz" + integrity sha512-mNKt2jBXJg4O7pSdbNUfDdTsK9FIdikfsIE/yUCxbAEXl4HMyJaivrVFcn3Elvt5xvCQYhUZm+hqTIu1UXM3Pw== + dependencies: + clsx "^2.1.0" + react-universal-interface@^0.6.2: version "0.6.2" resolved "https://registry.npmjs.org/react-universal-interface/-/react-universal-interface-0.6.2.tgz" @@ -2433,7 +2445,7 @@ react-use@^17.4.0: ts-easing "^0.2.0" tslib "^2.1.0" -react@*, "react@^15.5.x || ^16.x || ^17.x || ^18.x", "react@^16 || ^17 || ^18", "react@^16.0.0 || ^17.0.0 || ^18.0.0", "react@^16.8.0 || ^17.0.0 || ^18.0.0", "react@^16.8.0 || ^17 || ^18", "react@^16.8.0 || ^17.0.0 || ^18.0.0", "react@^16.8.0 || 17 || 18", "react@^16.8.3 || ^17 || ^18", "react@^16.9.0 || ^17 || ^18", react@^18.0.0, react@^18.2.0, "react@>= 16", "react@>= 16.8.0 || 17.x.x || ^18.0.0-0", react@>=16.8, react@>=16.8.0, react@>=16.9.0: +react@*, "react@^15.5.x || ^16.x || ^17.x || ^18.x", "react@^16 || ^17 || ^18", "react@^16.0.0 || ^17.0.0 || ^18.0.0", "react@^16.8.0 || ^17.0.0 || ^18.0.0", "react@^16.8.0 || ^17 || ^18", "react@^16.8.0 || ^17.0.0 || ^18.0.0", "react@^16.8.0 || 17 || 18", "react@^16.8.3 || ^17 || ^18", "react@^16.9.0 || ^17 || ^18", react@^18.0.0, react@^18.2.0, "react@>= 16", "react@>= 16.8.0 || 17.x.x || ^18.0.0-0", react@>=16.8, react@>=16.8.0, react@>=16.9.0, react@>=18: version "18.2.0" resolved "https://registry.npmjs.org/react/-/react-18.2.0.tgz" integrity sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==