2 Commits

  1. 21
      package-lock.json
  2. 2
      package.json
  3. 6
      src/app/(account-pages)/account/page.tsx
  4. 3
      src/app/(account-pages)/passengers-list/page.tsx
  5. 9
      src/app/(client-components)/(Header)/MainNav1.tsx
  6. 84
      src/app/(client-components)/(HeroSearchForm)/(stay-search-form)/StayDatesRangeInput.tsx
  7. 22
      src/app/(client-components)/(HeroSearchForm)/ButtonSubmit.tsx
  8. 4
      src/app/(client-components)/(HeroSearchForm)/GuestsInput.tsx
  9. 49
      src/app/(client-components)/(HeroSearchForm)/LocationInput.tsx
  10. 75
      src/app/(listing-detail)/(components)/MobileFooterSticky.tsx
  11. 11
      src/app/(server-components)/SectionHero.tsx
  12. 4
      src/app/about/page.tsx
  13. 20
      src/app/add-listing/[[...stepIndex]]/page.tsx
  14. 5
      src/app/add-new-passenger/page.tsx
  15. 42
      src/app/blog/[...slug]/Survey.tsx
  16. 40
      src/app/blog/[...slug]/page.tsx
  17. 4
      src/app/custom-history/page.tsx
  18. 35
      src/app/custom-trip/page.tsx
  19. 93
      src/app/faq/page.tsx
  20. 40
      src/app/faq/table.tsx
  21. 146
      src/app/forgot-password/page.tsx
  22. 13
      src/app/layout.tsx
  23. 103
      src/app/login/page.tsx
  24. 17
      src/app/page.tsx
  25. 2
      src/app/signup/methodes/page.tsx
  26. 6
      src/app/signup/otp-code/page.tsx
  27. 79
      src/app/signup/page.tsx
  28. 133
      src/app/tours/SectionGridFilterCard.tsx
  29. 29
      src/app/tours/[slug]/page.tsx
  30. 5
      src/app/tours/layout.tsx
  31. 25
      src/components/CardCategory3.tsx
  32. 151
      src/components/SectionGridFeaturePlaces.tsx
  33. 6
      src/components/SectionOurFeatures.tsx
  34. 2
      src/components/StayCard2.tsx
  35. 12
      src/components/TourSuggestion.tsx
  36. 10
      src/components/contexts/tourDetails.tsx
  37. 10
      src/data/navigation.ts
  38. 3
      src/shared/Navigation/Navigation.tsx
  39. 6
      src/shared/Navigation/NavigationItem.tsx
  40. 16
      yarn.lock

21
package-lock.json

@ -33,6 +33,7 @@
"react-hooks-global-state": "^2.1.0", "react-hooks-global-state": "^2.1.0",
"react-icons": "^5.3.0", "react-icons": "^5.3.0",
"react-swipeable": "^7.0.0", "react-swipeable": "^7.0.0",
"react-toastify": "^10.0.5",
"react-use": "^17.4.0", "react-use": "^17.4.0",
"react-use-keypress": "^1.3.1", "react-use-keypress": "^1.3.1",
"sass": "^1.62.1", "sass": "^1.62.1",
@ -1286,6 +1287,14 @@
"integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==", "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==",
"license": "MIT" "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": { "node_modules/color-convert": {
"version": "2.0.1", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
@ -4256,6 +4265,18 @@
"react": "^16.8.3 || ^17 || ^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": { "node_modules/react-universal-interface": {
"version": "0.6.2", "version": "0.6.2",
"resolved": "https://registry.npmjs.org/react-universal-interface/-/react-universal-interface-0.6.2.tgz", "resolved": "https://registry.npmjs.org/react-universal-interface/-/react-universal-interface-0.6.2.tgz",

2
package.json

@ -13,7 +13,6 @@
"clear-all": "rm -rf .next node_modules", "clear-all": "rm -rf .next node_modules",
"re-start": "rm -rf .next node_modules && yarn install && yarn dev", "re-start": "rm -rf .next node_modules && yarn install && yarn dev",
"re-build": "rm -rf .next node_modules && yarn install && yarn build" "re-build": "rm -rf .next node_modules && yarn install && yarn build"
}, },
"dependencies": { "dependencies": {
"@headlessui/react": "^1.7.14", "@headlessui/react": "^1.7.14",
@ -41,6 +40,7 @@
"react-hooks-global-state": "^2.1.0", "react-hooks-global-state": "^2.1.0",
"react-icons": "^5.3.0", "react-icons": "^5.3.0",
"react-swipeable": "^7.0.0", "react-swipeable": "^7.0.0",
"react-toastify": "^10.0.5",
"react-use": "^17.4.0", "react-use": "^17.4.0",
"react-use-keypress": "^1.3.1", "react-use-keypress": "^1.3.1",
"sass": "^1.62.1", "sass": "^1.62.1",

6
src/app/(account-pages)/account/page.tsx

@ -10,6 +10,7 @@ import axiosInstance from "@/components/api/axios";
import { useRouter } from "next/navigation"; import { useRouter } from "next/navigation";
import { useUserContext } from "@/components/contexts/userContext"; import { useUserContext } from "@/components/contexts/userContext";
import getImageURL from "@/components/api/getImageURL"; import getImageURL from "@/components/api/getImageURL";
import { toast } from "react-toastify";
export interface AccountPageProps {} export interface AccountPageProps {}
@ -70,12 +71,13 @@ const AccountPage: FC<AccountPageProps> = () => {
}); });
if (response.status === 204) { if (response.status === 204) {
clerUser(); clerUser();
toast.success("Your Delete account was successful")
} else { } else {
setError("Something went wrong"); setError("Something went wrong");
} }
} catch (error: unknown) { } catch (error: unknown) {
if (error instanceof Error) { if (error instanceof Error) {
setError(error.message);
toast.error(error.message);
} else { } else {
setError("An unknown error occurred"); setError("An unknown error occurred");
} }
@ -86,6 +88,7 @@ const AccountPage: FC<AccountPageProps> = () => {
const signOutHandler = (): void => { const signOutHandler = (): void => {
clerUser(); clerUser();
toast.success("Your sign out was successful")
}; };
const changeHandler = async (): Promise<void> => { const changeHandler = async (): Promise<void> => {
@ -111,6 +114,7 @@ const AccountPage: FC<AccountPageProps> = () => {
} }
); );
if (response.status === 200) { if (response.status === 200) {
toast.success("Updated successfully")
setUser({ setUser({
...user, ...user,
avatar: response.data.avatar, avatar: response.data.avatar,

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

@ -5,10 +5,11 @@ import PassengerTable from "./PassengerTable";
import { IoPersonAddOutline } from "react-icons/io5"; import { IoPersonAddOutline } from "react-icons/io5";
import axiosInstance from "@/components/api/axios"; import axiosInstance from "@/components/api/axios";
import Link from "next/link"; import Link from "next/link";
import { useUserContext } from "@/components/contexts/userContext";
const PassengersList = () => { const PassengersList = () => {
const [passengers , setPassenger ] = useState([]) const [passengers , setPassenger ] = useState([])
const {user} = useUserContext()
useEffect(()=>{ useEffect(()=>{
axiosInstance.get("/api/account/passengers/" ,{ axiosInstance.get("/api/account/passengers/" ,{

9
src/app/(client-components)/(Header)/MainNav1.tsx

@ -8,7 +8,7 @@ import ButtonPrimary from "@/shared/ButtonPrimary";
import MenuBar from "@/shared/MenuBar"; import MenuBar from "@/shared/MenuBar";
import SwitchDarkMode from "@/shared/SwitchDarkMode"; import SwitchDarkMode from "@/shared/SwitchDarkMode";
import HeroSearchForm2MobileFactory from "../(HeroSearchForm2Mobile)/HeroSearchForm2MobileFactory"; import HeroSearchForm2MobileFactory from "../(HeroSearchForm2Mobile)/HeroSearchForm2MobileFactory";
import { MdOutlineLanguage } from "react-icons/md";
import { MdOutlineLanguage , MdOutlineCardTravel } from "react-icons/md";
import Avatar from "@/shared/Avatar"; import Avatar from "@/shared/Avatar";
import Link from "next/link"; import Link from "next/link";
import { useUserContext } from "@/components/contexts/userContext"; import { useUserContext } from "@/components/contexts/userContext";
@ -24,8 +24,8 @@ const MainNav1: FC<MainNav1Props> = ({ className = "" }) => {
return ( return (
<div className={`nc-MainNav1 relative z-10 ${className}`}> <div className={`nc-MainNav1 relative z-10 ${className}`}>
<div className="px-4 lg:container h-20 relative flex justify-between"> <div className="px-4 lg:container h-20 relative flex justify-between">
<div className="hidden md:flex justify-start flex-1 space-x-4 sm:space-x-10">
<Logo className="w-24 self-center mr-28" />
<div className="flex">
<Logo className="w-24 self-center" />
<Navigation /> <Navigation />
</div> </div>
@ -40,6 +40,9 @@ const MainNav1: FC<MainNav1Props> = ({ className = "" }) => {
<div className="cursor-pointer self-center text-2xl md:text-[28px] w-12 h-12 rounded-full text-neutral-700 dark:text-neutral-300 hover:bg-neutral-100 dark:hover:bg-neutral-800 focus:outline-none flex items-center justify-center"> <div className="cursor-pointer self-center text-2xl md:text-[28px] w-12 h-12 rounded-full text-neutral-700 dark:text-neutral-300 hover:bg-neutral-100 dark:hover:bg-neutral-800 focus:outline-none flex items-center justify-center">
<MdOutlineLanguage size={25} /> <MdOutlineLanguage size={25} />
</div> </div>
<Link href={"/my-trips"} className="self-center text-2xl md:text-[28px] w-12 h-12 rounded-full text-neutral-700 dark:text-neutral-300 hover:bg-neutral-100 dark:hover:bg-neutral-800 focus:outline-none flex items-center justify-center">
<MdOutlineCardTravel size={25} />
</Link>
<SwitchDarkMode /> <SwitchDarkMode />
<SearchDropdown className="flex items-center" /> <SearchDropdown className="flex items-center" />
<div className="px-1" /> <div className="px-1" />

84
src/app/(client-components)/(HeroSearchForm)/(stay-search-form)/StayDatesRangeInput.tsx

@ -1,12 +1,8 @@
"use client"; "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 { 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"; import { Context } from "@/components/contexts/tourDetails";
export interface StayDatesRangeInputProps { export interface StayDatesRangeInputProps {
@ -20,44 +16,9 @@ const StayDatesRangeInput: FC<StayDatesRangeInputProps> = ({
}) => { }) => {
const { details } = useContext(Context); const { details } = useContext(Context);
const onChangeDate = (dates: [Date | null, Date | null]) => {
const [start, end] = dates;
setStartDate(start);
setEndDate(end);
};
// const renderInput = () => {
// return (
// <>
// <div className="text-neutral-300 dark:text-neutral-400">
// <CalendarIcon className="w-5 h-5 lg:w-7 lg:h-7" />
// </div>
// <div className="flex-grow text-left">
// <span className="block xl:text-lg font-semibold">
// {startDate ? startDate.toLocaleDateString("en-US", {
// month: "short",
// day: "2-digit",
// }) : "Tour period"}
// {" - " +
// endDate?.toLocaleDateString("en-US", {
// month: "short",
// day: "2-digit",
// }) || ""}
// </span>
// <span className="block mt-1 text-sm text-neutral-400 leading-none font-light">
// {"Start - End"}
// </span>
// </div>
// </>
// );
// };
return ( return (
<Popover className={`StayDatesRangeInput z-10 relative flex ${className}`}> <Popover className={`StayDatesRangeInput z-10 relative flex ${className}`}>
{({ open }) => ( {({ open }) => (
<>
<div <div
className={`flex-1 z-10 flex relative ${fieldClassName} items-center space-x-3 focus:outline-none`} className={`flex-1 z-10 flex relative ${fieldClassName} items-center space-x-3 focus:outline-none`}
> >
@ -65,7 +26,9 @@ const StayDatesRangeInput: FC<StayDatesRangeInputProps> = ({
<CalendarIcon className="w-5 h-5 lg:w-7 lg:h-7" /> <CalendarIcon className="w-5 h-5 lg:w-7 lg:h-7" />
</div> </div>
<div className="flex-grow text-left "> <div className="flex-grow text-left ">
<span className="block xl:text-lg font-semibold">
<span
className="block xl:text-lg font-semibold whitespace-nowrap "
>
{details?.started_at.replaceAll("-", "/") || "Tour period"} {details?.started_at.replaceAll("-", "/") || "Tour period"}
{details?.ended_at && " - " + details?.ended_at.replaceAll("-", "/")} {details?.ended_at && " - " + details?.ended_at.replaceAll("-", "/")}
</span> </span>
@ -74,45 +37,10 @@ const StayDatesRangeInput: FC<StayDatesRangeInputProps> = ({
</span> </span>
</div> </div>
</div> </div>
{/* {open && (
<div className="h-8 absolute self-center top-1/2 -translate-y-1/2 z-0 -inset-x-0.5 bg-white dark:bg-neutral-800"></div>
)} */}
{/* <Transition
as={Fragment}
enter="transition ease-out duration-200"
enterFrom="opacity-0 translate-y-1"
enterTo="opacity-100 translate-y-0"
leave="transition ease-in duration-150"
leaveFrom="opacity-100 translate-y-0"
leaveTo="opacity-0 translate-y-1"
>
<Popover.Panel className="absolute left-1/2 z-10 mt-3 top-full w-screen max-w-sm -translate-x-1/2 transform px-4 sm:px-0 lg:max-w-3xl">
<div className="overflow-hidden rounded-3xl shadow-lg ring-1 ring-black ring-opacity-5 bg-white dark:bg-neutral-800 p-8">
<DatePicker
selected={startDate}
onChange={onChangeDate}
startDate={startDate}
endDate={endDate}
selectsRange
monthsShown={2}
showPopperArrow={false}
inline
renderCustomHeader={(p) => (
<DatePickerCustomHeaderTwoMonth {...p} />
)}
renderDayContents={(day, date) => (
<DatePickerCustomDay dayOfMonth={day} date={date} />
)}
/>
</div>
</Popover.Panel>
</Transition> */}
</>
)} )}
</Popover> </Popover>
); );
}; };
export default StayDatesRangeInput; export default StayDatesRangeInput;

22
src/app/(client-components)/(HeroSearchForm)/ButtonSubmit.tsx

@ -1,33 +1,21 @@
import { PathName } from "@/routers/types"; import { PathName } from "@/routers/types";
import Link from "next/link"; import Link from "next/link";
import React, { FC } from "react"; import React, { FC } from "react";
import { FaArrowRight } from "react-icons/fa";
interface Props { interface Props {
href?: PathName; href?: PathName;
className? : string
} }
const ButtonSubmit: FC<Props> = ({ href = "/listing-stay-map" }) => {
const ButtonSubmit: FC<Props> = ({ className , href = "/listing-stay-map" }) => {
return ( return (
<Link <Link
href={href} href={href}
type="button" type="button"
className="h-14 md:h-16 w-full md:w-16 rounded-full bg-bronze hover:bg-bronze-secondary flex items-center justify-center text-neutral-50 hover:text-bronze focus:outline-none"
className={`h-14 md:h-16 w-full md:w-16 rounded-full bg-bronze hover:bg-bronze-secondary flex items-center justify-center text-neutral-50 hover:text-bronze focus:outline-none ${className} `}
> >
<span className="mr-3 md:hidden">Search</span>
<svg
xmlns="http://www.w3.org/2000/svg"
className="h-6 w-6"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={1.5}
d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"
/>
</svg>
<FaArrowRight size={20} />
</Link> </Link>
); );
}; };

4
src/app/(client-components)/(HeroSearchForm)/GuestsInput.tsx

@ -25,7 +25,7 @@ const GuestsInput: FC<GuestsInputProps> = ({
buttonSubmitHref = "/listing-stay-map", buttonSubmitHref = "/listing-stay-map",
hasButtonSubmit = true, hasButtonSubmit = true,
}) => { }) => {
const [guestAdultsInputValue, setGuestAdultsInputValue] = useState(2);
const [guestAdultsInputValue, setGuestAdultsInputValue] = useState(0);
const { passengers, details , setPassengers } = useContext(Context); const { passengers, details , setPassengers } = useContext(Context);
@ -80,7 +80,7 @@ const GuestsInput: FC<GuestsInputProps> = ({
{/* BUTTON SUBMIT OF FORM */} {/* BUTTON SUBMIT OF FORM */}
{hasButtonSubmit && ( {hasButtonSubmit && (
<div className="pr-2 xl:pr-4"> <div className="pr-2 xl:pr-4">
<ButtonSubmit href={`/tours/${details?.slug}-${details?.id}`} />
<ButtonSubmit className={`${!details && "opacity-40" }`} href={`${details ? `/tours/${details?.slug}-${details?.id}` : ""}`} />
</div> </div>
)} )}
</div> </div>

49
src/app/(client-components)/(HeroSearchForm)/LocationInput.tsx

@ -17,7 +17,7 @@ export interface LocationInputProps {
const LocationInput: FC<LocationInputProps> = ({ const LocationInput: FC<LocationInputProps> = ({
autoFocus = false, autoFocus = false,
placeHolder = "Tours",
placeHolder = "",
desc = "Select your tour", desc = "Select your tour",
className = "nc-flex-1.5", className = "nc-flex-1.5",
divHideVerticalLineClass = "left-10 -right-0.5", divHideVerticalLineClass = "left-10 -right-0.5",
@ -69,9 +69,6 @@ const LocationInput: FC<LocationInputProps> = ({
const renderRecentSearches = () => { const renderRecentSearches = () => {
return ( return (
<> <>
<h3 className="block mt-2 sm:mt-0 px-4 sm:px-8 font-semibold text-base sm:text-lg text-neutral-800 dark:text-neutral-100">
Tours
</h3>
<div className="mt-2"> <div className="mt-2">
{tours.results.map((item) => ( {tours.results.map((item) => (
<span <span
@ -89,33 +86,6 @@ const LocationInput: FC<LocationInputProps> = ({
); );
}; };
const renderSearchValue = () => {
return (
<>
{tours.results.filter((item) => {
return item.title.includes(value);
}) ? (
tours.results
.filter((item) => {
return item.title.includes(value);
})
.map((item) => (
<span
onClick={() => 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"
>
<span className="block font-medium text-neutral-700 dark:text-neutral-200">
{item.title}
</span>
</span>
))
) : (
<h3>No match found</h3>
)}
</>
);
};
return ( return (
<div className={`relative flex ${className}`} ref={containerRef}> <div className={`relative flex ${className}`} ref={containerRef}>
@ -129,18 +99,11 @@ const LocationInput: FC<LocationInputProps> = ({
<MapPinIcon className="w-5 h-5 lg:w-7 lg:h-7" /> <MapPinIcon className="w-5 h-5 lg:w-7 lg:h-7" />
</div> </div>
<div className="flex-grow"> <div className="flex-grow">
<input
<h2
className={`block w-full bg-transparent border-none focus:ring-0 p-0 focus:outline-none focus:placeholder-neutral-300 xl:text-lg font-semibold placeholder-neutral-800 dark:placeholder-neutral-200 truncate`} className={`block w-full bg-transparent border-none focus:ring-0 p-0 focus:outline-none focus:placeholder-neutral-300 xl:text-lg font-semibold placeholder-neutral-800 dark:placeholder-neutral-200 truncate`}
placeholder={placeHolder}
value={value}
autoFocus={showPopover}
onChange={(e) => {
setValue(e.currentTarget.value);
}}
ref={inputRef}
/>
<span className="block mt-0.5 text-sm text-neutral-400 font-light ">
<span className="line-clamp-1">{!!value ? placeHolder : desc}</span>
>{value}</h2>
<span className="block mt-0.5 text-sm text-neutral-400 ">
<span className="text-black text-lg font-semibold">{!!value ? placeHolder : desc}</span>
</span> </span>
{value && showPopover && ( {value && showPopover && (
<ClearDataButton <ClearDataButton
@ -162,7 +125,7 @@ const LocationInput: FC<LocationInputProps> = ({
{showPopover && ( {showPopover && (
<div className="absolute left-0 z-40 w-full min-w-[300px] sm:min-w-[500px] bg-white dark:bg-neutral-800 top-full mt-3 py-3 sm:py-6 rounded-3xl shadow-xl max-h-96 overflow-y-auto"> <div className="absolute left-0 z-40 w-full min-w-[300px] sm:min-w-[500px] bg-white dark:bg-neutral-800 top-full mt-3 py-3 sm:py-6 rounded-3xl shadow-xl max-h-96 overflow-y-auto">
{value ? renderSearchValue() : renderRecentSearches()}
{renderRecentSearches()}
</div> </div>
)} )}
</div> </div>

75
src/app/(listing-detail)/(components)/MobileFooterSticky.tsx

@ -1,48 +1,83 @@
import React, { useState } from "react";
import React, { useContext, useState } from "react";
import ModalSelectDate from "@/components/ModalSelectDate"; import ModalSelectDate from "@/components/ModalSelectDate";
import ButtonPrimary from "@/shared/ButtonPrimary"; import ButtonPrimary from "@/shared/ButtonPrimary";
import converSelectedDateToString from "@/utils/converSelectedDateToString"; import converSelectedDateToString from "@/utils/converSelectedDateToString";
import ModalReserveMobile from "./ModalReserveMobile"; 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<Date | null>( const [startDate, setStartDate] = useState<Date | null>(
new Date("2023/02/06") new Date("2023/02/06")
); );
const [endDate, setEndDate] = useState<Date | null>(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 ( return (
<div className="block lg:hidden fixed bottom-0 inset-x-0 py-2 sm:py-3 bg-white dark:bg-neutral-800 border-t border-neutral-200 dark:border-neutral-6000 z-40"> <div className="block lg:hidden fixed bottom-0 inset-x-0 py-2 sm:py-3 bg-white dark:bg-neutral-800 border-t border-neutral-200 dark:border-neutral-6000 z-40">
<div className="container flex items-center justify-between"> <div className="container flex items-center justify-between">
<div className="flex gap-4">
<div className=""> <div className="">
<span className="block text-xl font-semibold">
$311
<span className="ml-1 text-sm font-normal text-neutral-500 dark:text-neutral-400">
/night
</span>
</span>
<ModalSelectDate
renderChildren={({ openModal }) => (
<span
onClick={openModal}
className="block text-sm underline font-medium"
>
{converSelectedDateToString([startDate, endDate])}
</span>
)}
<NcInputNumber
className="w-full"
defaultValue={guestAdultsInputValue}
onChange={(value) => handleChangeData(value)}
max={10}
min={1}
label="Passsengers"
desc="Ages 13 or above"
/> />
</div> </div>
</div>
<div className="flex items-center gap-2">
<span className="block text-xl font-semibold">
{data?.price * passengers}
</span>
<ModalReserveMobile <ModalReserveMobile
renderChildren={({ openModal }) => ( renderChildren={({ openModal }) => (
<>
<ButtonPrimary <ButtonPrimary
sizeClass="px-5 sm:px-7 py-3 !rounded-2xl"
onClick={openModal}
disabled={true}
className={`${
data?.status === "AVAILABLE" && passengers
? ""
: "opacity-60 cursor-not-allowed"
}`}
href={`${
data?.status === "AVAILABLE" && passengers
? `/add-listing/${id}`
: `/tours/${data?.slug}-${id}`
}`}
> >
Reserve Reserve
</ButtonPrimary> </ButtonPrimary>
</>
)} )}
/> />
</div> </div>
</div> </div>
</div>
); );
}; };

11
src/app/(server-components)/SectionHero.tsx

@ -16,14 +16,15 @@ const SectionHero: FC<SectionHeroProps> = ({ className = "" }) => {
<div className="flex flex-col lg:flex-row lg:items-center"> <div className="flex flex-col lg:flex-row lg:items-center">
<div className="flex-shrink-0 lg:w-1/2 flex flex-col items-start space-y-8 sm:space-y-10 pb-14 lg:pb-64 xl:pr-14 lg:mr-10 xl:mr-0"> <div className="flex-shrink-0 lg:w-1/2 flex flex-col items-start space-y-8 sm:space-y-10 pb-14 lg:pb-64 xl:pr-14 lg:mr-10 xl:mr-0">
<h2 className="font-medium text-4xl md:text-5xl xl:text-7xl !leading-[114%] "> <h2 className="font-medium text-4xl md:text-5xl xl:text-7xl !leading-[114%] ">
Hotel, car & experiences
Begin your spiritual adventure
</h2> </h2>
<span className="text-base md:text-lg text-neutral-500 dark:text-neutral-400"> <span className="text-base md:text-lg text-neutral-500 dark:text-neutral-400">
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
</span> </span>
<ButtonPrimary href="/listing-stay-map" sizeClass="px-5 py-4 sm:px-7">
Start your search
<ButtonPrimary href="/tours" sizeClass="px-5 py-4 sm:px-7">
Start your journey
</ButtonPrimary> </ButtonPrimary>
</div> </div>
<div className="flex-grow"> <div className="flex-grow">

4
src/app/about/page.tsx

@ -21,10 +21,10 @@ const PageAbout: FC<PageAboutProps> = ({}) => {
rightImg={rightImg} rightImg={rightImg}
heading="👋 About Us." heading="👋 About Us."
btnText="" 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!"
/> />
<SectionFounder />
{/* <SectionFounder /> */}
<div className="relative py-16"> <div className="relative py-16">
<BackgroundSection /> <BackgroundSection />
<SectionClientSay /> <SectionClientSay />

20
src/app/add-listing/[[...stepIndex]]/page.tsx

@ -9,6 +9,7 @@ import { Context } from "@/components/contexts/tourDetails";
import { useRouter } from "next/navigation"; import { useRouter } from "next/navigation";
import axiosInstance from "@/components/api/axios"; import axiosInstance from "@/components/api/axios";
import { useUserContext } from "@/components/contexts/userContext"; import { useUserContext } from "@/components/contexts/userContext";
import { toast } from "react-toastify";
export interface CommonLayoutProps { export interface CommonLayoutProps {
children: React.ReactNode; children: React.ReactNode;
@ -42,6 +43,21 @@ const CommonLayout: FC<CommonLayoutProps> = ({ params }) => {
const backtHref = () => (index > 1 ? setIndex((prev) => prev - 1) : index); const backtHref = () => (index > 1 ? setIndex((prev) => prev - 1) : index);
const nextBtnText = index === totalPassengers ? "Save Passengers" : "Continue"; 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 () => { const sendPassengers = async () => {
try { try {
const response = await axiosInstance.post( const response = await axiosInstance.post(
@ -61,17 +77,17 @@ const CommonLayout: FC<CommonLayoutProps> = ({ params }) => {
} }
); );
console.log(response); console.log(response);
setRedirecting(true); // Set redirecting state to true
setRedirecting(true);
} catch (error) { } catch (error) {
backtHref(); backtHref();
console.error("Error submitting passengers:", error); console.error("Error submitting passengers:", error);
} }
}; };
// Use useEffect to handle the redirect after setting redirecting to true
useEffect(() => { useEffect(() => {
if (redirecting) { if (redirecting) {
router.replace("/my-trips"); router.replace("/my-trips");
toast.success("Purchased Successfully ")
} }
}, [redirecting, router]); }, [redirecting, router]);

5
src/app/add-new-passenger/page.tsx

@ -35,12 +35,16 @@ const CommonLayout: FC<CommonLayoutProps> = () => {
image: "", image: "",
}); });
const [loading , setLoading] = useState(false)
const handleFileChange = async (e: React.ChangeEvent<HTMLInputElement>) => { const handleFileChange = async (e: React.ChangeEvent<HTMLInputElement>) => {
setLoading(true)
const file = e.target.files[0]; const file = e.target.files[0];
if (file) { if (file) {
const image = await getImageURL(file); const image = await getImageURL(file);
setPassenger((prev) => ({ ...prev, image: image.url })); setPassenger((prev) => ({ ...prev, image: image.url }));
setErrors((prev) => ({ ...prev, image: "" })); // Clear image error setErrors((prev) => ({ ...prev, image: "" })); // Clear image error
setLoading(false)
} }
}; };
@ -207,6 +211,7 @@ const CommonLayout: FC<CommonLayoutProps> = () => {
placeholder="Passport" placeholder="Passport"
/> />
{errors.image && <p className="text-red-500 text-xs">{errors.image}</p>} {errors.image && <p className="text-red-500 text-xs">{errors.image}</p>}
{loading && <p>Loading ...</p>}
</FormItem> </FormItem>
</div> </div>
</> </>

42
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 (
<div className="max-w-screen-md mx-auto flex items-center gap-6 ">
{!checked ? (
<>
<h1 className="mb-2">Was this article helpfull ?</h1>
<div className="flex">
<div
onClick={() => {
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"
>
<p>Yes</p>
<BiLike />
</div>
<div
onClick={() => {
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"
>
<p>No</p>
<BiDislike />
</div>
</div>
</>
) : (
<h1>Thank you</h1>
)}
</div>
);
}
export default Survey;

40
src/app/blog/[...slug]/page.tsx

@ -12,6 +12,8 @@ import Image from "next/image";
import travelhero2Image from "@/images/travelhero2.png"; import travelhero2Image from "@/images/travelhero2.png";
import Link from "next/link"; import Link from "next/link";
import { Route } from "@/routers/types"; import { Route } from "@/routers/types";
import { BiLike , BiDislike } from "react-icons/bi";
import Survey from "./Survey";
const Page = ({ const Page = ({
params, params,
@ -39,7 +41,7 @@ const Page = ({
<div className="w-full border-b border-neutral-100 dark:border-neutral-800"></div> <div className="w-full border-b border-neutral-100 dark:border-neutral-800"></div>
<div className="flex flex-col items-baseline sm:flex-row sm:justify-between"> <div className="flex flex-col items-baseline sm:flex-row sm:justify-between">
<div className="nc-PostMeta2 flex items-center flex-wrap text-neutral-700 text-left dark:text-neutral-200 text-sm leading-none flex-shrink-0">
{/* <div className="nc-PostMeta2 flex items-center flex-wrap text-neutral-700 text-left dark:text-neutral-200 text-sm leading-none flex-shrink-0">
<Avatar <Avatar
containerClassName="flex-shrink-0" containerClassName="flex-shrink-0"
sizeClass="w-8 h-8 sm:h-11 sm:w-11 " sizeClass="w-8 h-8 sm:h-11 sm:w-11 "
@ -60,10 +62,10 @@ const Page = ({
</span> </span>
</div> </div>
</div> </div>
</div>
<div className="mt-3 sm:mt-0 sm:ml-3">
</div> */}
{/* <div className="mt-3 sm:mt-0 sm:ml-3">
<SocialsList /> <SocialsList />
</div>
</div> */}
</div> </div>
</div> </div>
</header> </header>
@ -184,30 +186,6 @@ const Page = ({
); );
}; };
const renderAuthor = () => {
return (
<div className="max-w-screen-md mx-auto ">
<div className="nc-SingleAuthor flex">
<Avatar sizeClass="w-11 h-11 md:w-24 md:h-24" />
<div className="flex flex-col ml-3 max-w-lg sm:ml-5 space-y-1">
<span className="text-xs text-neutral-400 uppercase tracking-wider">
WRITEN BY
</span>
<h2 className="text-lg font-semibold text-neutral-900 dark:text-neutral-200">
<a href="/">Fones Mimi</a>
</h2>
<span className="text-sm text-neutral-500 sm:text-base dark:text-neutral-300">
Theres no stopping the tech giant. Apple now opens its 100th
store in China.Theres no stopping the tech giant.
<a className="text-primary-6000 font-medium ml-1" href="/">
Readmore
</a>
</span>
</div>
</div>
</div>
);
};
const renderCommentForm = () => { const renderCommentForm = () => {
return ( return (
@ -298,9 +276,9 @@ const Page = ({
{renderContent()} {renderContent()}
{renderTags()} {renderTags()}
<div className="max-w-screen-md mx-auto border-b border-t border-neutral-100 dark:border-neutral-700"></div> <div className="max-w-screen-md mx-auto border-b border-t border-neutral-100 dark:border-neutral-700"></div>
{renderAuthor()}
{renderCommentForm()}
{renderCommentLists()}
<Survey/>
{/* {renderCommentForm()} */}
{/* {renderCommentLists()} */}
</div> </div>
<div className="relative bg-neutral-100 dark:bg-neutral-800 py-16 lg:py-28 mt-16 lg:mt-24"> <div className="relative bg-neutral-100 dark:bg-neutral-800 py-16 lg:py-28 mt-16 lg:mt-24">
<div className="container "> <div className="container ">

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

@ -47,7 +47,7 @@ const PageAddListing10: FC<PageAddListing10Props> = () => {
const selected = tours.find((item)=>{ const selected = tours.find((item)=>{
return JSON.parse(item.detail) === tour 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); console.log(tours);
@ -56,9 +56,7 @@ console.log(tours);
<div className="px-4 max-w-md mx-auto pb-24 pt-14 sm:py-24 lg:pb-32"> <div className="px-4 max-w-md mx-auto pb-24 pt-14 sm:py-24 lg:pb-32">
<div className="flex items-center justify-between mb-4"> <div className="flex items-center justify-between mb-4">
<h2 className="text-2xl font-semibold">Custom Trip History</h2> <h2 className="text-2xl font-semibold">Custom Trip History</h2>
<span className="text-gray-500 text-sm">2023 Dec 02 | 16:17</span>
</div> </div>
<p className="text-green-600 text-sm mb-4">Successfully Registered</p>
{toursDetail.length > 0 ? ( {toursDetail.length > 0 ? (
toursDetail.map((item, index) => ( toursDetail.map((item, index) => (
<div <div

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

@ -8,6 +8,7 @@ import Select from "@/shared/Select";
import Input from "@/shared/Input"; import Input from "@/shared/Input";
import { useRouter } from "next/navigation"; import { useRouter } from "next/navigation";
import { useUserContext } from "@/components/contexts/userContext"; import { useUserContext } from "@/components/contexts/userContext";
import { toast } from "react-toastify";
interface City { interface City {
name: string; name: string;
@ -148,22 +149,26 @@ const CommonLayout: FC<CommonLayoutProps> = () => {
); );
setIsFormValid(isValid); 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 = const isValid =
destinations.length > 0 &&
isFormValid &&
startCity && startCity &&
startDate && 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); setIsContinueValid(isValid);
};
}, [destinations, startCity, startDate, passengers, isFormValid]);
const addDestination = () => { const addDestination = () => {
setDestinations([ setDestinations([
@ -172,7 +177,7 @@ const CommonLayout: FC<CommonLayoutProps> = () => {
endCity: "", endCity: "",
transport: "", transport: "",
hotel: "", hotel: "",
duration: 1,
duration: 0,
finishDate: "", finishDate: "",
transportCost: 0, transportCost: 0,
hotelCost: 0, hotelCost: 0,
@ -219,7 +224,7 @@ const CommonLayout: FC<CommonLayoutProps> = () => {
setDestinations(updatedDestinations); setDestinations(updatedDestinations);
validateForm(); validateForm();
validateContinue();
// validateContinue();
}; };
console.log(destinations); console.log(destinations);
@ -272,7 +277,7 @@ const CommonLayout: FC<CommonLayoutProps> = () => {
Accept: "application/json", Accept: "application/json",
}, },
}); });
console.log("Response:", response.data);
toast.success("Successfully Registered")
router.push("/custom-history"); router.push("/custom-history");
} catch (error) { } catch (error) {
console.error("Error sending trip details:", error); console.error("Error sending trip details:", error);

93
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<number | null>(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 (
<div className="container mx-auto py-12 max-w-6xl">
<h1 className="text-center text-3xl mb-5 font-bold">Frequently Asked Questions</h1>
<p className="text-center mb-16 ">Have Questions ? We are here to help you !</p>
<div className="space-y-8">
{faqs.map((faq, index) => (
<Table
key={index}
faq={faq}
isActive={activeIndex === index}
onClick={() => toggleFAQ(index)}
/>
))}
</div>
</div>
);
};
export default FAQ;

40
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<TableProps> = ({ faq, isActive, onClick }) => {
return (
<div className="border-b pb-4">
<h3
className="text-xl font-semibold flex justify-between items-center cursor-pointer"
onClick={onClick}
>
{faq.question}
<span>{isActive ? "-" : "+"}</span>
</h3>
<motion.div
initial={false}
animate={{ height: isActive ? "auto" : 0 }}
transition={{ duration: 0.4, ease: [0.6, 0.01, -0.05, 0.95] }}
style={{ overflow: "hidden" }}
>
<p className={`mt-2 text-gray-700 ${isActive ? "block" : "hidden"}`}>
{faq.answer}
</p>
</motion.div>
</div>
);
};
export default Table;

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

@ -1,48 +1,74 @@
"use client"; "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 Input from "@/shared/Input";
import ButtonPrimary from "@/shared/ButtonPrimary"; import ButtonPrimary from "@/shared/ButtonPrimary";
import Image from "next/image";
import Link from "next/link"; import Link from "next/link";
import axiosInstance from "@/components/api/axios"; import axiosInstance from "@/components/api/axios";
import { useUserContext } from "@/components/contexts/userContext";
import useFormValidation from "@/hooks/FormValidation";
import { useRouter } from "next/navigation"; 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 {} export interface PageSignUpProps {}
const PageSignUp: FC<PageSignUpProps> = ({}) => {
const router = useRouter()
const PageSignUp: FC<PageSignUpProps> = () => {
const router = useRouter();
const { setForm, setMethod } = useUserContext(); const { setForm, setMethod } = useUserContext();
const [name, setName] = useState(''); const [name, setName] = useState('');
const [countryCode, setCountryCode] = useState('');
const [countryCode, setCountryCode] = useState('968');
const [phoneNumber, setPhoneNumber] = useState(''); const [phoneNumber, setPhoneNumber] = useState('');
const [password, setPassword] = useState(''); const [password, setPassword] = useState('');
const [confirmPassword, setConfirmPassword] = 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<HTMLInputElement>) => {
if (e.target.value.length <= 3) { if (e.target.value.length <= 3) {
setCountryCode(e.target.value); setCountryCode(e.target.value);
} }
}; };
const submitHandler = async (
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.";
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 = { const form = {
verification_methodes : "" ,
verification_methodes: response.data.verification_method,
method: "reset", method: "reset",
countryCode, countryCode,
phoneNumber, phoneNumber,
@ -50,16 +76,21 @@ const PageSignUp: FC<PageSignUpProps> = ({}) => {
confirmPassword, confirmPassword,
}; };
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) => {
setForm(form);
router.push("/signup/methodes");
} catch (error) {
console.error("Error fetching data:", 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 ( return (
@ -70,74 +101,55 @@ const PageSignUp: FC<PageSignUpProps> = ({}) => {
</h2> </h2>
<div className="max-w-md mx-auto space-y-6"> <div className="max-w-md mx-auto space-y-6">
{/* FORM */} {/* FORM */}
<form className="grid grid-cols-1 gap-6" action="#" method="post">
<form className="grid grid-cols-1 gap-6" onSubmit={(e) => e.preventDefault()}>
<label className="block"> <label className="block">
<span className="text-neutral-800 dark:text-neutral-200">Phone Number</span>
<div className="flex items-center mt-1 rounded-2xl border border-neutral-200 bg-white dark:border-neutral-700 dark:bg-neutral-900 focus-within:ring-0">
<span className="px-2 mr-[-15px] text-neutral-800 dark:text-neutral-200">+</span>
<span className="text-neutral-800 dark:text-neutral-200">
Phone Number
</span>
<div className={`flex items-center mt-1 rounded-2xl ${errors.countryCode || errors.phoneNumber ? "border border-red-600" : "border border-neutral-200" } bg-white dark:border-neutral-700 dark:bg-neutral-900 focus-within:border-primary-300 focus-within:ring focus-within:ring-primary-200 focus-within:ring-opacity-50`}>
<span className="px-2 mr-[-15px] text-neutral-800 dark:text-neutral-200">
+
</span>
<input <input
value={countryCode} value={countryCode}
onChange={countryCodeHandler} onChange={countryCodeHandler}
type="number" type="number"
placeholder="98"
placeholder="000"
maxLength={3} maxLength={3}
className="w-[50px] [appearance:textfield] [&::-webkit-outer-spin-button]:appearance-none [&::-webkit-inner-spin-button]:appearance-none p-2 mr-[-10px] text-center border-none outline-none focus:ring-0 focus:border-none bg-transparent text-neutral-800 dark:text-neutral-200"
className="w-[50px] [appearance:textfield] [&::-webkit-outer-spin-button]:appearance-none [&::-webkit-inner-spin-button]:appearance-none no-border-on-focus p-2 mr-[-10px] shadow-none text-center border-none outline-none bg-transparent text-neutral-800 dark:text-neutral-200"
/> />
<span className="px-2 text-neutral-500">|</span> <span className="px-2 text-neutral-500">|</span>
<input <input
value={phoneNumber} value={phoneNumber}
onChange={(e) => setPhoneNumber(e.target.value)} onChange={(e) => setPhoneNumber(e.target.value)}
type="number" type="number"
placeholder="26363687"
className="[appearance:textfield] [&::-webkit-outer-spin-button]:appearance-none [&::-webkit-inner-spin-button]:appearance-none flex-1 p-2 border-none outline-none focus:ring-0 focus:border-none bg-transparent text-neutral-800 dark:text-neutral-200"
className="[appearance:textfield] rounded-full [&::-webkit-outer-spin-button]:appearance-none [&::-webkit-inner-spin-button]:appearance-none flex-1 p-2 no-border-on-focus border-none outline-none bg-transparent text-neutral-800 dark:text-neutral-200"
/> />
</div> </div>
{errors.countryCode && <p className="text-xs text-red-600">{errors.countryCode}</p>}
{errors.phoneNumber && <p className="text-xs text-red-600">{errors.phoneNumber}</p>}
</label> </label>
<label className="block"> <label className="block">
<span className="flex justify-between items-center text-neutral-800 dark:text-neutral-200">
Password
</span>
<span className="flex justify-between items-center text-neutral-800 dark:text-neutral-200">Password</span>
<Input <Input
value={password} value={password}
onChange={(e) => setPassword(e.target.value)} onChange={(e) => setPassword(e.target.value)}
type="text"
className="secure-input unselectable-input mt-1"
onCopy={(e)=>{
e.preventDefault()
}}
type="password"
className={`mt-1 ${errors.password ? "border-red-600" : ""}`}
/> />
{errors.password && <p className="text-xs text-red-600">{errors.password}</p>}
</label> </label>
<label className="block"> <label className="block">
<span className="flex justify-between items-center text-neutral-800 dark:text-neutral-200">
Confirm Password
</span>
<span className="flex justify-between items-center text-neutral-800 dark:text-neutral-200">Confirm Password</span>
<Input <Input
value={confirmPassword} value={confirmPassword}
onChange={(e) => setConfirmPassword(e.target.value)} onChange={(e) => setConfirmPassword(e.target.value)}
placeholder="Password"
type="text"
className="secure-input unselectable-input mt-1"
onCopy={(e)=>{
e.preventDefault()
}}
type="password"
className={`mt-1 ${errors.confirmPassword ? "border-red-600" : ""}`}
/> />
{errors.confirmPassword && <p className="text-xs text-red-600">{errors.confirmPassword}</p>}
</label> </label>
<ButtonPrimary
onClick={(e) => {
e.preventDefault();
submitHandler(countryCode, phoneNumber, password, confirmPassword);
}}
>
Continue
</ButtonPrimary>
<ButtonPrimary onClick={submitHandler}>Continue</ButtonPrimary>
</form> </form>
</div> </div>
</div> </div>
</div> </div>
); );
}; };

13
src/app/layout.tsx

@ -11,6 +11,7 @@ import "rc-slider/assets/index.css";
import Footer from "@/components/Footer"; import Footer from "@/components/Footer";
import FooterNav from "@/components/FooterNav"; import FooterNav from "@/components/FooterNav";
import { UserProvider } from "@/components/contexts/userContext"; import { UserProvider } from "@/components/contexts/userContext";
import { ToastContainer } from "react-toastify";
const poppins = Poppins({ const poppins = Poppins({
subsets: ["latin"], subsets: ["latin"],
@ -32,6 +33,18 @@ export default function RootLayout({
<UserProvider> <UserProvider>
<ClientCommons /> <ClientCommons />
<SiteHeader /> <SiteHeader />
<ToastContainer
position="top-right"
autoClose={5000}
hideProgressBar={false}
newestOnTop={false}
closeOnClick
rtl={false}
pauseOnFocusLoss
draggable
pauseOnHover
theme="light"
/>
{children} {children}
<FooterNav /> <FooterNav />
<Footer /> <Footer />

103
src/app/login/page.tsx

@ -7,6 +7,8 @@ import Link from "next/link";
import axiosInstance from "@/components/api/axios"; import axiosInstance from "@/components/api/axios";
import { useRouter } from "next/navigation"; import { useRouter } from "next/navigation";
import { useUserContext } from "@/components/contexts/userContext"; import { useUserContext } from "@/components/contexts/userContext";
import { ToastContainer, toast } from "react-toastify";
import "react-toastify/dist/ReactToastify.css";
export interface PageLoginProps {} export interface PageLoginProps {}
@ -16,8 +18,8 @@ const PageLogin: FC<PageLoginProps> = () => {
const [phoneNumber, setPhoneNumber] = useState<string>(""); const [phoneNumber, setPhoneNumber] = useState<string>("");
const [password, setPassword] = useState<string>(""); const [password, setPassword] = useState<string>("");
const [countryCode, setCountryCode] = useState<string>("");
const [error, setError] = useState<string>("");
const [countryCode, setCountryCode] = useState<string>("968");
const [errors, setErrors] = useState<{ phoneNumber?: string; password?: string }>({});
// Redirect to home if the user is already logged in // Redirect to home if the user is already logged in
useEffect(() => { useEffect(() => {
@ -26,14 +28,48 @@ const PageLogin: FC<PageLoginProps> = () => {
} }
}, [user, router]); }, [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<HTMLInputElement>) => { const countryCodeHandler = (e: React.ChangeEvent<HTMLInputElement>) => {
if (e.target.value.length <= 3) { if (e.target.value.length <= 3) {
setCountryCode(e.target.value); 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 () => { const submitHandler = async () => {
setError("");
setErrors({}); // Clear previous errors
if (!validateForm()) {
return; // Prevent submission if there are validation errors
}
try { try {
const response = await axiosInstance.post(`/api/account/login/`, { const response = await axiosInstance.post(`/api/account/login/`, {
@ -42,15 +78,43 @@ const PageLogin: FC<PageLoginProps> = () => {
}); });
if (response.status === 201) { if (response.status === 201) {
toast.success("Your login was successful")
setUser(response.data); setUser(response.data);
} else { } 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) { } catch (error) {
if (error instanceof 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 { } 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",
});
} }
} }
}; };
@ -64,27 +128,31 @@ const PageLogin: FC<PageLoginProps> = () => {
<div className="max-w-md mx-auto space-y-6"> <div className="max-w-md mx-auto space-y-6">
<form className="grid grid-cols-1 gap-6" onSubmit={(e) => e.preventDefault()}> <form className="grid grid-cols-1 gap-6" onSubmit={(e) => e.preventDefault()}>
<label className="block"> <label className="block">
<span className="text-neutral-800 dark:text-neutral-200">Phone Number</span>
<div className="flex items-center mt-1 rounded-2xl border border-neutral-200 bg-white dark:border-neutral-700 dark:bg-neutral-900 focus-within:ring-0">
<span className="px-2 mr-[-15px] text-neutral-800 dark:text-neutral-200">+</span>
<span className="text-neutral-800 dark:text-neutral-200">
Phone Number
</span>
<div className={`flex items-center mt-1 rounded-2xl ${errors.countryCode || errors.phoneNumber ? "border border-red-600" : "border border-neutral-200" } bg-white dark:border-neutral-700 dark:bg-neutral-900 focus-within:border-primary-300 focus-within:ring focus-within:ring-primary-200 focus-within:ring-opacity-50`}>
<span className="px-2 mr-[-15px] text-neutral-800 dark:text-neutral-200">
+
</span>
<input <input
value={countryCode || ""}
value={countryCode}
onChange={countryCodeHandler} onChange={countryCodeHandler}
type="number" type="number"
placeholder="98"
placeholder="000"
maxLength={3} maxLength={3}
className="w-[50px] [appearance:textfield] [&::-webkit-outer-spin-button]:appearance-none [&::-webkit-inner-spin-button]:appearance-none p-2 mr-[-10px] text-center border-none outline-none focus:ring-0 focus:border-none bg-transparent text-neutral-800 dark:text-neutral-200"
className="w-[50px] [appearance:textfield] [&::-webkit-outer-spin-button]:appearance-none [&::-webkit-inner-spin-button]:appearance-none no-border-on-focus p-2 mr-[-10px] shadow-none text-center border-none outline-none bg-transparent text-neutral-800 dark:text-neutral-200"
/> />
<span className="px-2 text-neutral-500">|</span> <span className="px-2 text-neutral-500">|</span>
<input <input
value={phoneNumber} value={phoneNumber}
onChange={(e) => setPhoneNumber(e.target.value)} onChange={(e) => setPhoneNumber(e.target.value)}
type="number" type="number"
placeholder="26363687"
className="[appearance:textfield] [&::-webkit-outer-spin-button]:appearance-none [&::-webkit-inner-spin-button]:appearance-none flex-1 p-2 border-none outline-none focus:ring-0 focus:border-none bg-transparent text-neutral-800 dark:text-neutral-200"
className="[appearance:textfield] rounded-full [&::-webkit-outer-spin-button]:appearance-none [&::-webkit-inner-spin-button]:appearance-none flex-1 p-2 no-border-on-focus border-none outline-none bg-transparent text-neutral-800 dark:text-neutral-200"
/> />
</div> </div>
</label> </label>
<label className="block"> <label className="block">
<span className="flex justify-between items-center text-neutral-800 dark:text-neutral-200"> <span className="flex justify-between items-center text-neutral-800 dark:text-neutral-200">
Password Password
@ -93,13 +161,13 @@ const PageLogin: FC<PageLoginProps> = () => {
</Link> </Link>
</span> </span>
<Input <Input
type="password"
className="mt-1"
value={password} value={password}
onChange={(e) => setPassword(e.target.value)} onChange={(e) => setPassword(e.target.value)}
type="password"
className={`secure-input mt-1 ${errors.password ? "border-red-600" : "border-neutral-300"}`}
/> />
</label> </label>
{error && <p className="text-red-500 text-xs">{error}</p>}
<ButtonPrimary onClick={submitHandler}>Continue</ButtonPrimary> <ButtonPrimary onClick={submitHandler}>Continue</ButtonPrimary>
</form> </form>
@ -110,6 +178,7 @@ const PageLogin: FC<PageLoginProps> = () => {
</span> </span>
</div> </div>
</div> </div>
</div> </div>
); );
}; };

17
src/app/page.tsx

@ -16,6 +16,7 @@ import SectionCustomTour from "@/components/SectionCustomTour";
import axios from "axios"; import axios from "axios";
import axiosInstance from "@/components/api/axios"; import axiosInstance from "@/components/api/axios";
import TourSuggestion from "@/components/TourSuggestion"; import TourSuggestion from "@/components/TourSuggestion";
import SectionDowloadApp from "./(home)/SectionDowloadApp";
const DEMO_CATS: TaxonomyType[] = [ const DEMO_CATS: TaxonomyType[] = [
{ {
@ -170,8 +171,8 @@ function PageHome() {
{/* <SectionCustomTour /> */} {/* <SectionCustomTour /> */}
<SectionSliderNewCategories categories={DEMO_CATS} />
{/* <SectionSliderNewCategories categories={DEMO_CATS} /> */}
<SectionDowloadApp/>
<SectionHowItWork /> <SectionHowItWork />
<div className="relative py-16"> <div className="relative py-16">
@ -180,8 +181,8 @@ function PageHome() {
categories={DEMO_CATS_2} categories={DEMO_CATS_2}
categoryCardType="card4" categoryCardType="card4"
itemPerRow={4} 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" sliderStyle="style2"
/> />
@ -196,17 +197,17 @@ function PageHome() {
{/* <SectionGridCategoryBox /> */} {/* <SectionGridCategoryBox /> */}
<div className="relative py-16">
{/* <div className="relative py-16">
<BackgroundSection /> <BackgroundSection />
<SectionBecomeAnAuthor /> <SectionBecomeAnAuthor />
</div>
</div> */}
<SectionSliderNewCategories
{/* <SectionSliderNewCategories
heading="Explore by types of stays" heading="Explore by types of stays"
subHeading="Explore houses based on 10 types of stays" subHeading="Explore houses based on 10 types of stays"
categoryCardType="card5" categoryCardType="card5"
itemPerRow={5} itemPerRow={5}
/>
/> */}
{/* <SectionVideos /> */} {/* <SectionVideos /> */}

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

@ -16,11 +16,9 @@ function SelectMethods() {
const [error, setError] = useState(""); const [error, setError] = useState("");
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
// Initialize 'enabled' with default values
const [enabled, setEnabled] = useState({ watsapp: false, sms: false }); const [enabled, setEnabled] = useState({ watsapp: false, sms: false });
useEffect(() => { useEffect(() => {
// Safely access 'form.verification_methods' and update 'enabled'
setEnabled({ setEnabled({
watsapp: form.verification_methodes?.includes("watsapp") ?? false, watsapp: form.verification_methodes?.includes("watsapp") ?? false,
sms: form.verification_methodes?.includes("sms") ?? false, sms: form.verification_methodes?.includes("sms") ?? false,

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

@ -5,6 +5,7 @@ import ButtonPrimary from "@/shared/ButtonPrimary";
import axiosInstance from "@/components/api/axios"; import axiosInstance from "@/components/api/axios";
import { useUserContext } from "@/components/contexts/userContext"; import { useUserContext } from "@/components/contexts/userContext";
import { useRouter } from "next/navigation"; import { useRouter } from "next/navigation";
import { toast } from "react-toastify";
export interface PageSignUpProps {} export interface PageSignUpProps {}
@ -68,11 +69,12 @@ const PageSignUp: FC<PageSignUpProps> = () => {
if (response.status === 201) { if (response.status === 201) {
setUser(response.data); setUser(response.data);
toast.success("Your Sign In was successful")
} else { } 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 } 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.");
} }
}; };

79
src/app/signup/page.tsx

@ -8,24 +8,16 @@ import axiosInstance from "@/components/api/axios";
import { useUserContext } from "@/components/contexts/userContext"; import { useUserContext } from "@/components/contexts/userContext";
import useFormValidation from "@/hooks/FormValidation"; import useFormValidation from "@/hooks/FormValidation";
import { useRouter } from "next/navigation"; import { useRouter } from "next/navigation";
import { ToastContainer, toast } from "react-toastify";
import "react-toastify/dist/ReactToastify.css";
export interface PageSignUpProps {} export interface PageSignUpProps {}
const PageSignUp: FC<PageSignUpProps> = () => { const PageSignUp: FC<PageSignUpProps> = () => {
const router = useRouter(); 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<string>(""); const [name, setName] = useState<string>("");
const [countryCode, setCountryCode] = useState<string>("");
const [countryCode, setCountryCode] = useState<string>("968");
const [phoneNumber, setPhoneNumber] = useState<string>(""); const [phoneNumber, setPhoneNumber] = useState<string>("");
const [password, setPassword] = useState<string>(""); const [password, setPassword] = useState<string>("");
const [confirmPassword, setConfirmPassword] = useState<string>(""); const [confirmPassword, setConfirmPassword] = useState<string>("");
@ -40,6 +32,27 @@ const PageSignUp: FC<PageSignUpProps> = () => {
} }
}; };
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 submitHandler = async () => {
const form = { const form = {
name, name,
@ -66,8 +79,6 @@ const PageSignUp: FC<PageSignUpProps> = () => {
} finally { } finally {
setLoading(false); setLoading(false);
} }
} else {
console.log("Form has errors:", errors);
} }
}; };
@ -78,7 +89,10 @@ const PageSignUp: FC<PageSignUpProps> = () => {
Signup Signup
</h2> </h2>
<div className="max-w-md mx-auto space-y-6"> <div className="max-w-md mx-auto space-y-6">
<form className="grid grid-cols-1 gap-6" onSubmit={(e) => e.preventDefault()}>
<form
className="grid grid-cols-1 gap-6"
onSubmit={(e) => e.preventDefault()}
>
<label className="block"> <label className="block">
<span className="text-neutral-800 dark:text-neutral-200">Full Name</span> <span className="text-neutral-800 dark:text-neutral-200">Full Name</span>
<Input <Input
@ -86,14 +100,18 @@ const PageSignUp: FC<PageSignUpProps> = () => {
onChange={(e) => setName(e.target.value)} onChange={(e) => setName(e.target.value)}
type="text" type="text"
placeholder="Full Name" placeholder="Full Name"
className="mt-1"
className={`mt-1 ${errors.name ? "border-red-600" : "border-neutral-300"}`}
/> />
{errors.name && <p className="text-xs text-red-600">{errors.name}</p>}
</label> </label>
<label className="block"> <label className="block">
<span className="text-neutral-800 dark:text-neutral-200">Phone Number</span>
<div className="flex items-center mt-1 rounded-2xl border border-neutral-200 bg-white dark:border-neutral-700 dark:bg-neutral-900 focus-within:ring-0">
<span className="px-2 mr-[-15px] text-neutral-800 dark:text-neutral-200">+</span>
<span className="text-neutral-800 dark:text-neutral-200">
Phone Number
</span>
<div className={`flex items-center mt-1 rounded-2xl ${errors.countryCode || errors.phoneNumber ? "border border-red-600" : "border border-neutral-200" } bg-white dark:border-neutral-700 dark:bg-neutral-900 focus-within:border-primary-300 focus-within:ring focus-within:ring-primary-200 focus-within:ring-opacity-50`}>
<span className="px-2 mr-[-15px] text-neutral-800 dark:text-neutral-200">
+
</span>
<input <input
value={countryCode} value={countryCode}
onChange={countryCodeHandler} onChange={countryCodeHandler}
@ -107,37 +125,27 @@ const PageSignUp: FC<PageSignUpProps> = () => {
value={phoneNumber} value={phoneNumber}
onChange={(e) => setPhoneNumber(e.target.value)} onChange={(e) => setPhoneNumber(e.target.value)}
type="number" type="number"
className="[appearance:textfield] [&::-webkit-outer-spin-button]:appearance-none [&::-webkit-inner-spin-button]:appearance-none flex-1 p-2 no-border-on-focus border-none outline-none bg-transparent text-neutral-800 dark:text-neutral-200"
className="[appearance:textfield] rounded-full [&::-webkit-outer-spin-button]:appearance-none [&::-webkit-inner-spin-button]:appearance-none flex-1 p-2 no-border-on-focus border-none outline-none bg-transparent text-neutral-800 dark:text-neutral-200"
/> />
</div> </div>
{errors.countryCode && <p className="text-xs text-red-600">{errors.countryCode}</p>}
{errors.phoneNumber && <p className="text-xs text-red-600">{errors.phoneNumber}</p>}
</label> </label>
<label className="block"> <label className="block">
<span className="flex justify-between items-center text-neutral-800 dark:text-neutral-200">
Password
</span>
<span className="flex justify-between items-center text-neutral-800 dark:text-neutral-200">Password</span>
<Input <Input
value={password} value={password}
onChange={(e) => setPassword(e.target.value)} onChange={(e) => setPassword(e.target.value)}
type="password" type="password"
className="secure-input mt-1"
className={`secure-input mt-1 ${errors.password ? "border-red-600" : "border-neutral-300"}`}
/> />
{errors.password && <p className="text-xs text-red-600">{errors.password}</p>}
</label> </label>
<label className="block"> <label className="block">
<span className="flex justify-between items-center text-neutral-800 dark:text-neutral-200">
Confirm Password
</span>
<span className="flex justify-between items-center text-neutral-800 dark:text-neutral-200">Confirm Password</span>
<Input <Input
value={confirmPassword} value={confirmPassword}
onChange={(e) => setConfirmPassword(e.target.value)} onChange={(e) => setConfirmPassword(e.target.value)}
type="password" type="password"
className="secure-input mt-1"
className={`secure-input mt-1 ${errors.confirmPassword ? "border-red-600" : "border-neutral-300"}`}
/> />
{errors.confirmPassword && (
<p className="text-xs text-red-600">{errors.confirmPassword}</p>
)}
</label> </label>
{failed && <p className="text-xs text-red-600">{failed}</p>} {failed && <p className="text-xs text-red-600">{failed}</p>}
<ButtonPrimary <ButtonPrimary
@ -156,6 +164,7 @@ const PageSignUp: FC<PageSignUpProps> = () => {
</span> </span>
</div> </div>
</div> </div>
</div> </div>
); );
}; };

133
src/app/tours/SectionGridFilterCard.tsx

@ -7,8 +7,14 @@ import TabFilters from "./TabFilters";
import Heading2 from "@/shared/Heading2"; import Heading2 from "@/shared/Heading2";
import StayCard2 from "@/components/StayCard2"; import StayCard2 from "@/components/StayCard2";
import { Context } from "@/components/contexts/tourDetails"; 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 { export interface SectionGridFilterCardProps {
className?: string; className?: string;
@ -22,24 +28,23 @@ const SectionGridFilterCard: FC<SectionGridFilterCardProps> = ({
const { countries, tours } = useContext(Context); const { countries, tours } = useContext(Context);
const [countryTours, setCountryTours] = useState(tours.results || []); const [countryTours, setCountryTours] = useState(tours.results || []);
const [checked, setChecked] = useState<{ [key: string]: boolean }>({}); 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 // Get the list of selected countries
const filteredCountries = Object.keys(checked).filter((countryName) => checked[countryName]);
const filteredCountries = Object.keys(checked).filter(
(countryName) => checked[countryName]
);
const windowWidth = useWindowSize().width;
useEffect(() => { useEffect(() => {
const country = searchParams.get("country")
if (searchParams.has("country")){
setChecked({
[country] : true
})
const country = searchParams.get("country");
if (country) {
setChecked({ [country]: true });
} }
} , [])
console.log(checked);
}, []);
useEffect(() => { useEffect(() => {
if (!tours.results) return; if (!tours.results) return;
@ -60,22 +65,108 @@ console.log(checked);
setCountryTours(filteredTours); setCountryTours(filteredTours);
} }
setCurrentIndex(0)
}, [checked, countries, tours.results]); }, [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 ( return (
<div className={`nc-SectionGridFilterCard container ${className}`} data-nc-id="SectionGridFilterCard">
<div
className={`nc-SectionGridFilterCard container ${className}`}
data-nc-id="SectionGridFilterCard"
>
<Heading2 /> <Heading2 />
<div className="mb-8 lg:mb-11"> <div className="mb-8 lg:mb-11">
<TabFilters onChangeCountry={setChecked} data={countries} /> <TabFilters onChangeCountry={setChecked} data={countries} />
</div> </div>
<div className="grid grid-cols-1 gap-6 md:gap-8 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4">
{countryTours.length > 0 ? (
countryTours.map((stay) => <StayCard2 key={stay.id} data={stay} />)
) : (
<h2>No tours Available</h2>
<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}>
{countryTours.map((stay, indx) => (
<motion.li
className={`relative inline-block px-2 xl:px-4`}
custom={direction}
initial={{ x: `${(currentIndex - 1) * -100}%` }}
animate={{ x: `${currentIndex * -100}%` }}
variants={variants(200, 1)}
key={indx}
style={{ width: `calc(1/${numberOfItems} * 100%)` }}
>
<StayCard2 key={stay.id} data={stay} />
</motion.li>
))}
</AnimatePresence>
</motion.ul>
</div>
{currentIndex > 0 && (
<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]"
/>
)}
{countryTours.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]"
/>
)} )}
</div> </div>
</MotionConfig>
{countryTours.length === 0 && <h2>No tours Available</h2>}
</div> </div>
); );
}; };

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

@ -25,6 +25,7 @@ import axiosInstance from "@/components/api/axios";
import { FaCheckCircle } from "react-icons/fa"; import { FaCheckCircle } from "react-icons/fa";
import ConfirmModal from "@/shared/popUp"; import ConfirmModal from "@/shared/popUp";
import { IoPersonAddOutline } from "react-icons/io5"; import { IoPersonAddOutline } from "react-icons/io5";
import MobileFooterSticky from "@/app/(listing-detail)/(components)/MobileFooterSticky";
export interface ListingStayDetailPageProps {} export interface ListingStayDetailPageProps {}
@ -265,13 +266,15 @@ const ListingStayDetailPage: FC<ListingStayDetailPageProps> = ({}) => {
{iteneries?.map((item, index) => ( {iteneries?.map((item, index) => (
<div className="flex"> <div className="flex">
<div className="mt-2"> <div className="mt-2">
<div className=" mx-4 transform -translate-x-1/2 w-8 h-8 bg-white border-secondery-bronze border-[3px] rounded-full"></div>
<div className="dark:bg-black mx-4 transform -translate-x-1/2 w-8 h-8 bg-white border-secondery-bronze border-[3px] rounded-full"></div>
{totalIteneries !== index + 1 && ( {totalIteneries !== index + 1 && (
<div className=" mx-4 mt-[-2px] transform -translate-x-1/2 w-[1px] h-full border-secondery-bronze border-[2px]"></div> <div className=" mx-4 mt-[-2px] transform -translate-x-1/2 w-[1px] h-full border-secondery-bronze border-[2px]"></div>
)} )}
</div> </div>
<div className="p-5 rounded-3xl mb-4 border-2"> <div className="p-5 rounded-3xl mb-4 border-2">
<h1 className="text-black font-bold mb-1">{item?.title}</h1>
<h1 className="text-black font-bold mb-1 dark:text-white">
{item?.title}
</h1>
<span> <span>
{item?.started_at {item?.started_at
.replaceAll("-", " ") .replaceAll("-", " ")
@ -468,7 +471,9 @@ const ListingStayDetailPage: FC<ListingStayDetailPageProps> = ({}) => {
{details && {details &&
details.travel_tips.map((item) => ( details.travel_tips.map((item) => (
<li> <li>
<h4>{item.title}</h4>
<h4 className="dark:text-neutral-400 space-y-2">
{item.title}
</h4>
<p>{item.description}</p> <p>{item.description}</p>
</li> </li>
))} ))}
@ -520,7 +525,15 @@ const ListingStayDetailPage: FC<ListingStayDetailPageProps> = ({}) => {
</div> </div>
{/* SUBMIT */} {/* SUBMIT */}
<ButtonPrimary href={`/add-listing/${id}`}>Reserve</ButtonPrimary>
<ButtonPrimary
disabled = {true}
className={`${details?.status === "AVAILABLE" && passengers ? "" : "opacity-60 cursor-not-allowed"}`}
href={`${
details?.status && passengers === "AVAILABLE" ? `/add-listing/${id}` : `/tours/${details?.slug}-${id}`
}`}
>
Reserve
</ButtonPrimary>
</div> </div>
); );
}; };
@ -530,10 +543,7 @@ const ListingStayDetailPage: FC<ListingStayDetailPageProps> = ({}) => {
{/* HEADER */} {/* HEADER */}
<header className="rounded-md sm:rounded-xl"> <header className="rounded-md sm:rounded-xl">
<div className="relative grid grid-cols-3 sm:grid-cols-4 gap-1 sm:gap-2"> <div className="relative grid grid-cols-3 sm:grid-cols-4 gap-1 sm:gap-2">
<div
className="col-span-2 row-span-3 sm:row-span-2 relative rounded-md sm:rounded-xl overflow-hidden cursor-pointer"
>
<div className="col-span-2 row-span-3 sm:row-span-2 relative rounded-md sm:rounded-xl overflow-hidden cursor-pointer">
<Image <Image
fill fill
className="object-cover rounded-md sm:rounded-xl" className="object-cover rounded-md sm:rounded-xl"
@ -611,6 +621,7 @@ const ListingStayDetailPage: FC<ListingStayDetailPageProps> = ({}) => {
{renderSection3()} {renderSection3()}
{renderSection8()} {renderSection8()}
{renderSection4()} {renderSection4()}
{/* {renderSection5()} */} {/* {renderSection5()} */}
{/* {renderSection6()} */} {/* {renderSection6()} */}
{/* {renderSection7()} */} {/* {renderSection7()} */}
@ -621,6 +632,8 @@ const ListingStayDetailPage: FC<ListingStayDetailPageProps> = ({}) => {
<div className="sticky top-28">{renderSidebar()}</div> <div className="sticky top-28">{renderSidebar()}</div>
</div> </div>
</main> </main>
<MobileFooterSticky passengers = {passengers} data={details} />
</div> </div>
); );
}; };

5
src/app/tours/layout.tsx

@ -48,7 +48,7 @@ const DetailtLayout = ({ children }: { children: ReactNode }) => {
{/* OTHER SECTION */} {/* OTHER SECTION */}
<div className="container py-24 lg:py-32"> <div className="container py-24 lg:py-32">
<div className="relative py-16">
{/* <div className="relative py-16">
<BackgroundSection /> <BackgroundSection />
<SectionSliderNewCategories <SectionSliderNewCategories
heading="Explore by types of stays" heading="Explore by types of stays"
@ -57,12 +57,11 @@ const DetailtLayout = ({ children }: { children: ReactNode }) => {
itemPerRow={5} itemPerRow={5}
sliderStyle="style2" sliderStyle="style2"
/> />
</div>
</div> */}
{/* <SectionSubscribe2 className="pt-24 lg:pt-32" /> */} {/* <SectionSubscribe2 className="pt-24 lg:pt-32" /> */}
</div> </div>
{/* STICKY FOOTER MOBILE */} {/* STICKY FOOTER MOBILE */}
<MobileFooterSticky />
</div> </div>
); );
}; };

25
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 { TaxonomyType } from "@/data/types";
import convertNumbThousand from "@/utils/convertNumbThousand"; import convertNumbThousand from "@/utils/convertNumbThousand";
import Link from "next/link"; import Link from "next/link";
@ -8,13 +10,32 @@ import Image from "next/image";
export interface CardCategory3Props { export interface CardCategory3Props {
className?: string; className?: string;
taxonomy: TaxonomyType; taxonomy: TaxonomyType;
countries : any
tours : any
} }
const CardCategory3: FC<CardCategory3Props> = ({ const CardCategory3: FC<CardCategory3Props> = ({
className = "", className = "",
taxonomy, taxonomy,
countries,
tours
}) => { }) => {
const { count, name, href = "/", thumbnail } = taxonomy; 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 ( return (
@ -41,7 +62,7 @@ const CardCategory3: FC<CardCategory3Props> = ({
<span <span
className={`block mt-1.5 text-sm text-neutral-6000 dark:text-neutral-400`} className={`block mt-1.5 text-sm text-neutral-6000 dark:text-neutral-400`}
> >
{convertNumbThousand(taxonomy.city.length || 0)} PROPERTIES
{convertNumbThousand(countryTours.length || 0)} Tours
</span> </span>
</div> </div>
</Link> </Link>

151
src/components/SectionGridFeaturePlaces.tsx

@ -1,20 +1,19 @@
"use client"; "use client";
import React, { FC, ReactNode, useContext, useEffect, useState } from "react"; import React, { FC, ReactNode, useContext, useEffect, useState } from "react";
import { DEMO_STAY_LISTINGS } from "@/data/listings";
import { StayDataType } from "@/data/types"; import { StayDataType } from "@/data/types";
import ButtonPrimary from "@/shared/ButtonPrimary"; import ButtonPrimary from "@/shared/ButtonPrimary";
import HeaderFilter from "./HeaderFilter"; import HeaderFilter from "./HeaderFilter";
import StayCard from "./StayCard";
import StayCard2 from "./StayCard2"; 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 { 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 { export interface SectionGridFeaturePlacesProps {
stayListings?: StayDataType[]; stayListings?: StayDataType[];
gridClass?: string; gridClass?: string;
@ -28,27 +27,41 @@ export interface SectionGridFeaturePlacesProps {
const SectionGridFeaturePlaces: FC<SectionGridFeaturePlacesProps> = ({ const SectionGridFeaturePlaces: FC<SectionGridFeaturePlacesProps> = ({
gridClass = "", gridClass = "",
heading = "List of Tours", 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 { countries, tours } = useContext(Context);
const [countryTours, setCountryTours] = useState();
const [limit, setLimit] = useState(7);
const [countryTours, setCountryTours] = useState<any>([]);
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(() => { useEffect(() => {
handleChange("All")
} , [tours , countries])
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 = async (item) => {
const handleChange = (item: string) => {
if (item === "All") { if (item === "All") {
setCountryTours(tours.results); setCountryTours(tours.results);
} else { } else {
const selected = countries.find((country) => country.name === item); const selected = countries.find((country) => country.name === item);
const selectedTours = tours.results?.filter( const selectedTours = tours.results?.filter(
@ -57,22 +70,33 @@ const SectionGridFeaturePlaces: FC<SectionGridFeaturePlacesProps> = ({
setCountryTours(selectedTours); 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 <CardName key={stay.id} data={stay} />;
// };
function changeItemId(newVal: number) {
if (newVal > currentIndex) {
setDirection(1);
} else {
setDirection(-1);
}
console.log(`Changing index from ${currentIndex} to ${newVal}`); // Debug log
setCurrentIndex(newVal);
}
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 ( return (
<div className="nc-SectionGridFeaturePlaces relative"> <div className="nc-SectionGridFeaturePlaces relative">
@ -83,17 +107,58 @@ const SectionGridFeaturePlaces: FC<SectionGridFeaturePlacesProps> = ({
heading={heading} heading={heading}
onClickTab={(item) => handleChange(item)} onClickTab={(item) => handleChange(item)}
/> />
<div
className={`grid gap-6 md:gap-8 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 ${gridClass}`}
<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}>
{countryTours.map((stay, indx) => (
<motion.li
className={`relative inline-block px-2 xl:px-4`}
custom={direction}
initial={{ x: `${(currentIndex - 1) * -100}%` }}
animate={{ x: `${currentIndex * -100}%` }}
variants={variants(200, 1)}
key={indx}
style={{ width: `calc(1/${numberOfItems} * 100%)` }}
> >
{countryTours?.length ? (
countryTours?.map((stay , idx) => limit >= idx && <StayCard2 key={stay.id} data={stay} />)
) : (
<h2>No tours Available</h2>
<StayCard2 key={stay.id} data={stay} />
</motion.li>
))}
</AnimatePresence>
</motion.ul>
</div>
{currentIndex > 0 && (
<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]"
/>
)}
{countryTours.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]"
/>
)} )}
</div> </div>
</MotionConfig>
<div className="flex mt-16 justify-center items-center"> <div className="flex mt-16 justify-center items-center">
<ButtonPrimary onClick={()=>{setLimit((prev)=> prev + 8)}}>Show me more</ButtonPrimary>
<ButtonPrimary onClick={() => router.push("/tours")}>
Show me more
</ButtonPrimary>
</div> </div>
</div> </div>
); );

6
src/components/SectionOurFeatures.tsx

@ -36,7 +36,7 @@ const SectionOurFeatures: FC<SectionOurFeaturesProps> = ({
<ul className="space-y-10 mt-16"> <ul className="space-y-10 mt-16">
<li className="space-y-4"> <li className="space-y-4">
<Badge name="Advertising" />
{/* <Badge name="Advertising" /> */}
<span className="block text-xl font-semibold"> <span className="block text-xl font-semibold">
Cost-effective advertising Cost-effective advertising
</span> </span>
@ -46,7 +46,7 @@ const SectionOurFeatures: FC<SectionOurFeaturesProps> = ({
</span> </span>
</li> </li>
<li className="space-y-4"> <li className="space-y-4">
<Badge color="green" name="Exposure " />
{/* <Badge color="green" name="Exposure " /> */}
<span className="block text-xl font-semibold"> <span className="block text-xl font-semibold">
Reach millions with Chisfis Reach millions with Chisfis
</span> </span>
@ -56,7 +56,7 @@ const SectionOurFeatures: FC<SectionOurFeaturesProps> = ({
</span> </span>
</li> </li>
<li className="space-y-4"> <li className="space-y-4">
<Badge color="red" name="Secure" />
{/* <Badge color="red" name="Secure" />/ */}
<span className="block text-xl font-semibold"> <span className="block text-xl font-semibold">
Secure and simple Secure and simple
</span> </span>

2
src/components/StayCard2.tsx

@ -47,7 +47,7 @@ const StayCard2: FC<StayCard2Props> = ({
href={`/tours/${data?.slug}-${data?.id}`} href={`/tours/${data?.slug}-${data?.id}`}
className="relative w-full" className="relative w-full"
> >
<div className="h-[220px] overflow-hidden rounded-xl">
<div className="h-40 lg:h-56 sm:h-40 overflow-hidden rounded-xl">
<Image <Image
className="w-full h-full object-cover" className="w-full h-full object-cover"
src={data?.image?.image_url?.md || "/default-image.jpg"} src={data?.image?.image_url?.md || "/default-image.jpg"}

12
src/components/TourSuggestion.tsx

@ -115,7 +115,8 @@ const TourSuggestion: FC<TourSuggestionProps> = ({
const [currentIndex, setCurrentIndex] = useState(0); const [currentIndex, setCurrentIndex] = useState(0);
const [direction, setDirection] = useState(0); const [direction, setDirection] = useState(0);
const [numberOfItems, setNumberOfitem] = useState(0); const [numberOfItems, setNumberOfitem] = useState(0);
const { tours } = useContext(Context);
const { tours , countries } = useContext(Context);
const windowWidth = useWindowSize().width; const windowWidth = useWindowSize().width;
@ -148,6 +149,7 @@ const TourSuggestion: FC<TourSuggestionProps> = ({
// }); // });
// }, []); // }, []);
console.log(countries);
function changeItemId(newVal: number) { function changeItemId(newVal: number) {
if (newVal > currentIndex) { if (newVal > currentIndex) {
@ -160,7 +162,7 @@ const TourSuggestion: FC<TourSuggestionProps> = ({
const handlers = useSwipeable({ const handlers = useSwipeable({
onSwipedLeft: () => { onSwipedLeft: () => {
if (currentIndex < tours.results?.length - 1) {
if (currentIndex < countries?.length - 1) {
changeItemId(currentIndex + 1); changeItemId(currentIndex + 1);
} }
}, },
@ -205,7 +207,7 @@ const TourSuggestion: FC<TourSuggestionProps> = ({
className="relative whitespace-nowrap -mx-2 xl:-mx-4" className="relative whitespace-nowrap -mx-2 xl:-mx-4"
> >
<AnimatePresence initial={false} custom={direction}> <AnimatePresence initial={false} custom={direction}>
{tours.results?.map((item, indx) => (
{countries?.map((item, indx) => (
<motion.li <motion.li
className={`relative inline-block px-2 xl:px-4 ${itemClassName}`} className={`relative inline-block px-2 xl:px-4 ${itemClassName}`}
custom={direction} custom={direction}
@ -221,7 +223,7 @@ const TourSuggestion: FC<TourSuggestionProps> = ({
width: `calc(1/${numberOfItems} * 100%)`, width: `calc(1/${numberOfItems} * 100%)`,
}} }}
> >
<CardCategory1 taxonomy={item} />
<CardCategory3 tours = {tours} countries = {countries} taxonomy={item} />
</motion.li> </motion.li>
))} ))}
</AnimatePresence> </AnimatePresence>
@ -236,7 +238,7 @@ const TourSuggestion: FC<TourSuggestionProps> = ({
/> />
) : null} ) : null}
{tours.results?.length > currentIndex + numberOfItems ? (
{countries?.length > currentIndex + numberOfItems ? (
<NextBtn <NextBtn
style={{ transform: "translate3d(0, 0, 0)" }} style={{ transform: "translate3d(0, 0, 0)" }}
onClick={() => changeItemId(currentIndex + 1)} onClick={() => changeItemId(currentIndex + 1)}

10
src/components/contexts/tourDetails.tsx

@ -1,6 +1,6 @@
"use client"; "use client";
import axiosInstance from "../api/axios"; import axiosInstance from "../api/axios";
import React, { createContext, useEffect, useState } from "react";
import React, { createContext, useContext, useEffect, useState } from "react";
export const Context = createContext(); export const Context = createContext();
@ -68,3 +68,11 @@ export const ContextProvider = ({ children }) => {
</Context.Provider> </Context.Provider>
); );
}; };
export const useToursContext = () => {
const context = useContext(Context);
if (!context) {
throw new Error("useUserContext must be used within a UserProvider");
}
return context;
};

10
src/data/navigation.ts

@ -3,6 +3,7 @@ import ncNanoId from "@/utils/ncNanoId";
import { Route } from "@/routers/types"; import { Route } from "@/routers/types";
import __megamenu from "./jsons/__megamenu.json"; import __megamenu from "./jsons/__megamenu.json";
const megaMenuDemo: MegamenuItem[] = [ const megaMenuDemo: MegamenuItem[] = [
{ {
id: ncNanoId(), id: ncNanoId(),
@ -168,8 +169,6 @@ export const NAVIGATION_DEMO: NavItemType[] = [
id: ncNanoId(), id: ncNanoId(),
href: "/", href: "/",
name: "Home", name: "Home",
type: "none",
isNew: true,
}, },
{ {
id: ncNanoId(), id: ncNanoId(),
@ -177,26 +176,21 @@ export const NAVIGATION_DEMO: NavItemType[] = [
name: "All Tours", name: "All Tours",
type: "dropdown", type: "dropdown",
children: demoChildMenus, children: demoChildMenus,
isNew: true,
}, },
{ {
id: ncNanoId(), id: ncNanoId(),
href: "/blog", href: "/blog",
name: "Blogs", name: "Blogs",
type: "megaMenu",
megaMenu: megaMenuDemo,
}, },
{ {
id: ncNanoId(), id: ncNanoId(),
href: "/contact",
href: "/faq",
name: "FAQ", name: "FAQ",
type: "none",
}, },
{ {
id: ncNanoId(), id: ncNanoId(),
href: "/about", href: "/about",
name: "AboutUs", name: "AboutUs",
type: "none",
}, },
// { // {
// id: ncNanoId(), // id: ncNanoId(),

3
src/shared/Navigation/Navigation.tsx

@ -3,6 +3,7 @@ import NavigationItem from "./NavigationItem";
import { NAVIGATION_DEMO } from "@/data/navigation"; import { NAVIGATION_DEMO } from "@/data/navigation";
import ButtonPrimary from "../ButtonPrimary"; import ButtonPrimary from "../ButtonPrimary";
import ButtonSecondary from "../ButtonSecondary"; import ButtonSecondary from "../ButtonSecondary";
import Link from "next/link";
function Navigation() { function Navigation() {
return ( return (
@ -10,7 +11,7 @@ function Navigation() {
{NAVIGATION_DEMO.map((item) => ( {NAVIGATION_DEMO.map((item) => (
<NavigationItem key={item.id} menuItem={item} /> <NavigationItem key={item.id} menuItem={item} />
))} ))}
<ButtonSecondary href="/custom-trip" className="m-5">Custoum Tour</ButtonSecondary>
<Link href="/custom-trip" className="m-5 self-center border border-gray-300 p-1.5 rounded-full px-4 hover:bg-bronze hover:text-white">Custom Tour</Link>
</ul> </ul>
); );
} }

6
src/shared/Navigation/NavigationItem.tsx

@ -1,5 +1,7 @@
"use client"; "use client";
import { useToursContext } from "@/components/contexts/tourDetails";
import { useUserContext } from "@/components/contexts/userContext";
import { PathName } from "@/routers/types"; import { PathName } from "@/routers/types";
import { Popover, Transition } from "@headlessui/react"; import { Popover, Transition } from "@headlessui/react";
import { ChevronDownIcon } from "@heroicons/react/24/solid"; import { ChevronDownIcon } from "@heroicons/react/24/solid";
@ -34,6 +36,7 @@ type NavigationItemWithRouterProps = NavigationItemProps;
const NavigationItem: FC<NavigationItemWithRouterProps> = ({ menuItem }) => { const NavigationItem: FC<NavigationItemWithRouterProps> = ({ menuItem }) => {
const [menuCurrentHovers, setMenuCurrentHovers] = useState<string[]>([]); const [menuCurrentHovers, setMenuCurrentHovers] = useState<string[]>([]);
const {countries} = useToursContext()
// CLOSE ALL MENU OPENING WHEN CHANGE HISTORY // CLOSE ALL MENU OPENING WHEN CHANGE HISTORY
const locationPathName = usePathname(); const locationPathName = usePathname();
@ -160,7 +163,7 @@ const NavigationItem: FC<NavigationItemWithRouterProps> = ({ menuItem }) => {
className="sub-menu will-change-transform absolute transform z-10 w-56 top-full left-0" className="sub-menu will-change-transform absolute transform z-10 w-56 top-full left-0"
> >
<ul className="rounded-lg shadow-lg ring-1 ring-black ring-opacity-5 dark:ring-white dark:ring-opacity-10 text-sm relative bg-white dark:bg-neutral-900 py-4 grid space-y-1"> <ul className="rounded-lg shadow-lg ring-1 ring-black ring-opacity-5 dark:ring-white dark:ring-opacity-10 text-sm relative bg-white dark:bg-neutral-900 py-4 grid space-y-1">
{menuDropdown.children?.map((i) => {
{ countries?.map((i) => {
if (i.type) { if (i.type) {
return renderDropdownMenuNavlinkHasChild(i); return renderDropdownMenuNavlinkHasChild(i);
} else { } else {
@ -268,6 +271,7 @@ const NavigationItem: FC<NavigationItemWithRouterProps> = ({ menuItem }) => {
</Link> </Link>
); );
}; };
console.log(menuItem.type);
switch (menuItem.type) { switch (menuItem.type) {
case "megaMenu": case "megaMenu":

16
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" resolved "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz"
integrity sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA== 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: color-convert@^2.0.1:
version "2.0.1" version "2.0.1"
resolved "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz" 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-onclickoutside "^6.12.2"
react-popper "^2.3.0" 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" version "18.2.0"
resolved "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz" resolved "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz"
integrity sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g== 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" resolved "https://registry.npmjs.org/react-swipeable/-/react-swipeable-7.0.0.tgz"
integrity sha512-NI7KGfQ6gwNFN0Hor3vytYW3iRfMMaivGEuxcADOOfBCx/kqwXE8IfHFxEcxSUkxCYf38COLKYd9EMYZghqaUA== 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: react-universal-interface@^0.6.2:
version "0.6.2" version "0.6.2"
resolved "https://registry.npmjs.org/react-universal-interface/-/react-universal-interface-0.6.2.tgz" 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" ts-easing "^0.2.0"
tslib "^2.1.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" version "18.2.0"
resolved "https://registry.npmjs.org/react/-/react-18.2.0.tgz" resolved "https://registry.npmjs.org/react/-/react-18.2.0.tgz"
integrity sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ== integrity sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==

Loading…
Cancel
Save