Browse Source

validation with libphonenumber added , serchbar added

main
sina_sajjadi 2 months ago
parent
commit
a81356e412
  1. 22
      package-lock.json
  2. 3
      package.json
  3. 2
      src/app/(account-pages)/my-trips/page.tsx
  4. 2
      src/app/(account-pages)/passengers-list/page.tsx
  5. 8
      src/app/(client-components)/(Header)/LangDropdown.tsx
  6. 33
      src/app/(client-components)/(Header)/MainNav1.tsx
  7. 58
      src/app/(client-components)/(Header)/SearchDropdown.tsx
  8. 4
      src/app/(client-components)/(HeroSearchForm2Mobile)/(stay-search-form)/StaySearchForm.tsx
  9. 21
      src/app/(client-components)/(HeroSearchForm2Mobile)/HeroSearchForm2Mobile.tsx
  10. 47
      src/app/(client-components)/(HeroSearchForm2Mobile)/LocationInput.tsx
  11. 2
      src/app/add-listing/[[...stepIndex]]/PageAddListing1.tsx
  12. 2
      src/app/custom-history/page.tsx
  13. 7
      src/app/custom-trip/page.tsx
  14. 2
      src/app/faq/table.tsx
  15. 33
      src/app/forgot-password/page.tsx
  16. 50
      src/app/login/page.tsx
  17. 5
      src/app/signup/methodes/page.tsx
  18. 73
      src/app/signup/otp-code/page.tsx
  19. 18
      src/app/signup/page.tsx
  20. 138
      src/app/tours/Card.tsx
  21. 150
      src/app/tours/SectionGridFilterCard.tsx
  22. 4
      src/app/tours/TabFilters.tsx
  23. 27
      src/app/tours/[slug]/page.tsx
  24. 14
      src/components/FooterNav.tsx
  25. 75
      src/components/SearchCard.tsx
  26. 3
      src/components/StayCard2.tsx
  27. 5
      src/components/contexts/tourDetails.tsx
  28. 5
      src/components/contexts/userContext.tsx
  29. 1
      src/data/navigation.ts
  30. 23
      src/hooks/FormValidation.ts
  31. 7
      src/images/Group.svg
  32. 3
      src/images/Vector.svg
  33. 9
      src/shared/Navigation/NavMobile.tsx
  34. 15
      yarn.lock

22
package-lock.json

@ -23,7 +23,9 @@
"eslint": "8.41.0", "eslint": "8.41.0",
"eslint-config-next": "^13.4.3", "eslint-config-next": "^13.4.3",
"framer-motion": "^10.12.16", "framer-motion": "^10.12.16",
"google-libphonenumber": "^3.2.38",
"google-map-react": "^2.2.1", "google-map-react": "^2.2.1",
"libphonenumber-js": "^1.11.9",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"next": "^13.4.3", "next": "^13.4.3",
"next-i18next": "^15.3.1", "next-i18next": "^15.3.1",
@ -42,6 +44,7 @@
"typescript": "5.0.4" "typescript": "5.0.4"
}, },
"devDependencies": { "devDependencies": {
"@types/google-libphonenumber": "^7.4.30",
"autoprefixer": "^10.4.14", "autoprefixer": "^10.4.14",
"postcss": "^8.4.23", "postcss": "^8.4.23",
"tailwindcss": "^3.3.2" "tailwindcss": "^3.3.2"
@ -570,6 +573,12 @@
"node": ">=4" "node": ">=4"
} }
}, },
"node_modules/@types/google-libphonenumber": {
"version": "7.4.30",
"resolved": "https://registry.npmjs.org/@types/google-libphonenumber/-/google-libphonenumber-7.4.30.tgz",
"integrity": "sha512-Td1X1ayRxePEm6/jPHUBs2tT6TzW1lrVB6ZX7ViPGellyzO/0xMNi+wx5nH6jEitjznq276VGIqjK5qAju0XVw==",
"dev": true
},
"node_modules/@types/google-map-react": { "node_modules/@types/google-map-react": {
"version": "2.1.7", "version": "2.1.7",
"resolved": "https://registry.npmjs.org/@types/google-map-react/-/google-map-react-2.1.7.tgz", "resolved": "https://registry.npmjs.org/@types/google-map-react/-/google-map-react-2.1.7.tgz",
@ -2574,6 +2583,14 @@
"url": "https://github.com/sponsors/sindresorhus" "url": "https://github.com/sponsors/sindresorhus"
} }
}, },
"node_modules/google-libphonenumber": {
"version": "3.2.38",
"resolved": "https://registry.npmjs.org/google-libphonenumber/-/google-libphonenumber-3.2.38.tgz",
"integrity": "sha512-t/K0dsVmA0gMMVLJgcMeB9g1Ar4ANVWfkY+AJGSdfyJ2Ay7Bu8ceLYpUlC6FZSilZgaF1qbkM9tZydGBEBHqAg==",
"engines": {
"node": ">=0.10"
}
},
"node_modules/google-map-react": { "node_modules/google-map-react": {
"version": "2.2.1", "version": "2.2.1",
"resolved": "https://registry.npmjs.org/google-map-react/-/google-map-react-2.2.1.tgz", "resolved": "https://registry.npmjs.org/google-map-react/-/google-map-react-2.2.1.tgz",
@ -3324,6 +3341,11 @@
"node": ">= 0.8.0" "node": ">= 0.8.0"
} }
}, },
"node_modules/libphonenumber-js": {
"version": "1.11.9",
"resolved": "https://registry.npmjs.org/libphonenumber-js/-/libphonenumber-js-1.11.9.tgz",
"integrity": "sha512-Zs5wf5HaWzW2/inlupe2tstl0I/Tbqo7lH20ZLr6Is58u7Dz2n+gRFGNlj9/gWxFvNfp9+YyDsiegjNhdixB9A=="
},
"node_modules/lilconfig": { "node_modules/lilconfig": {
"version": "2.1.0", "version": "2.1.0",
"resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz", "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz",

3
package.json

@ -30,7 +30,9 @@
"eslint": "8.41.0", "eslint": "8.41.0",
"eslint-config-next": "^13.4.3", "eslint-config-next": "^13.4.3",
"framer-motion": "^10.12.16", "framer-motion": "^10.12.16",
"google-libphonenumber": "^3.2.38",
"google-map-react": "^2.2.1", "google-map-react": "^2.2.1",
"libphonenumber-js": "^1.11.9",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"next": "^13.4.3", "next": "^13.4.3",
"next-i18next": "^15.3.1", "next-i18next": "^15.3.1",
@ -49,6 +51,7 @@
"typescript": "5.0.4" "typescript": "5.0.4"
}, },
"devDependencies": { "devDependencies": {
"@types/google-libphonenumber": "^7.4.30",
"autoprefixer": "^10.4.14", "autoprefixer": "^10.4.14",
"postcss": "^8.4.23", "postcss": "^8.4.23",
"tailwindcss": "^3.3.2" "tailwindcss": "^3.3.2"

2
src/app/(account-pages)/my-trips/page.tsx

@ -23,7 +23,7 @@ const MyTrips = () => {
useEffect(() => { useEffect(() => {
if (!Object.keys(user).length) { if (!Object.keys(user).length) {
router.replace("/");
router.replace("/signup");
} }
}, [user, router]); }, [user, router]);
useEffect(() => { useEffect(() => {

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

@ -15,7 +15,7 @@ const router = useRouter()
useEffect(() => { useEffect(() => {
if (!Object.keys(user).length) { if (!Object.keys(user).length) {
router.replace("/");
router.replace("/signup");
} }
}, [user, router]); }, [user, router]);

8
src/app/(client-components)/(Header)/LangDropdown.tsx

@ -6,6 +6,8 @@ import {
} from "@heroicons/react/24/outline"; } from "@heroicons/react/24/outline";
import { FC, Fragment } from "react"; import { FC, Fragment } from "react";
import { headerCurrency } from "./CurrencyDropdown"; import { headerCurrency } from "./CurrencyDropdown";
import { MdOutlineLanguage } from "react-icons/md";
export const headerLanguage = [ export const headerLanguage = [
{ {
@ -114,9 +116,9 @@ const LangDropdown: FC<LangDropdownProps> = ({
${open ? "" : "text-opacity-80"} ${open ? "" : "text-opacity-80"}
group self-center h-10 sm:h-12 px-3 py-1.5 inline-flex items-center text-sm text-gray-800 dark:text-neutral-200 font-medium hover:text-opacity-100 focus:outline-none `} group self-center h-10 sm:h-12 px-3 py-1.5 inline-flex items-center text-sm text-gray-800 dark:text-neutral-200 font-medium hover:text-opacity-100 focus:outline-none `}
> >
<GlobeAltIcon className="w-5 h-5 opacity-80" />
<span className="mx-1">/</span>
<BanknotesIcon className="w-5 h-5 opacity-80" />
<MdOutlineLanguage size={25}/>
{/* <span className="mx-1">/</span>
<BanknotesIcon className="w-5 h-5 opacity-80" /> */}
<ChevronDownIcon <ChevronDownIcon
className={`${open ? "-rotate-180" : "text-opacity-70"} className={`${open ? "-rotate-180" : "text-opacity-70"}
ml-1 h-4 w-4 group-hover:text-opacity-80 transition ease-in-out duration-150`} ml-1 h-4 w-4 group-hover:text-opacity-80 transition ease-in-out duration-150`}

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

@ -1,4 +1,4 @@
"use client"
"use client";
import React, { FC, use, useContext, useEffect, useState } from "react"; import React, { FC, use, useContext, useEffect, useState } from "react";
import Logo from "@/shared/Logo"; import Logo from "@/shared/Logo";
@ -8,17 +8,18 @@ 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 , MdOutlineCardTravel } from "react-icons/md";
import { 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";
import LangDropdown from "./LangDropdown";
export interface MainNav1Props { export interface MainNav1Props {
className?: string; className?: string;
} }
const MainNav1: FC<MainNav1Props> = ({ className = "" }) => { const MainNav1: FC<MainNav1Props> = ({ className = "" }) => {
const {user} = useUserContext()
const { user } = useUserContext();
console.log(Object.keys(user).length); console.log(Object.keys(user).length);
@ -38,31 +39,37 @@ console.log(Object.keys(user).length);
<div className="hidden md:flex flex-shrink-0 justify-end flex-1 lg:flex-none text-neutral-700 dark:text-neutral-100"> <div className="hidden md:flex flex-shrink-0 justify-end flex-1 lg:flex-none text-neutral-700 dark:text-neutral-100">
<div className="hidden xl:flex space-x-0.5 items-center"> <div className="hidden xl:flex space-x-0.5 items-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} />
</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">
<LangDropdown />
<Link
href={`${Object.keys(user).length ? "/my-trips" : "signup"}`}
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} /> <MdOutlineCardTravel size={25} />
</Link> </Link>
<SwitchDarkMode /> <SwitchDarkMode />
<SearchDropdown className="flex items-center" /> <SearchDropdown className="flex items-center" />
<div className="px-1" /> <div className="px-1" />
{Object.keys(user).length ? (<Link href={"/account"}>
<Avatar
imgUrl={user?.avatar}
sizeClass="w-10 h-10"
/>
{Object.keys(user).length ? (
<Link className="self-center" href={"/account"}>
<Avatar imgUrl={user?.avatar} sizeClass="w-10 h-10" />
</Link> </Link>
) : ( ) : (
<div >
<Link className="mr-4 text-md" href={"/login"}>LogIn</Link>
<ButtonPrimary className="self-center" href="/signup"> <ButtonPrimary className="self-center" href="/signup">
Sign up Sign up
</ButtonPrimary> </ButtonPrimary>
</div>
)} )}
</div> </div>
<div className="flex xl:hidden items-center"> <div className="flex xl:hidden items-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"> <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} />
<LangDropdown
className="flex"
panelClassName="z-10 w-screen max-w-[280px] px-4 mb-3 right-3 bottom-full sm:px-0"
/>
</div> </div>
<SwitchDarkMode /> <SwitchDarkMode />
<div className="px-0.5" /> <div className="px-0.5" />

58
src/app/(client-components)/(Header)/SearchDropdown.tsx

@ -1,8 +1,9 @@
"use client";
import { Popover, Transition } from "@headlessui/react"; import { Popover, Transition } from "@headlessui/react";
import Input from "@/shared/Input"; import Input from "@/shared/Input";
import React, { FC, Fragment } from "react";
import React, { FC, Fragment, useEffect, useState } from "react";
import { useToursContext } from "@/components/contexts/tourDetails";
import SearchCard from "@/components/SearchCard";
import axiosInstance from "@/components/api/axios";
interface Props { interface Props {
className?: string; className?: string;
@ -11,6 +12,32 @@ interface Props {
const SearchDropdown: FC<Props> = ({ className = "" }) => { const SearchDropdown: FC<Props> = ({ className = "" }) => {
const inputRef = React.createRef<HTMLInputElement>(); const inputRef = React.createRef<HTMLInputElement>();
const [value, setValue] = useState("");
const [toursDetails, setToursDetail] = useState([]);
const { getTourData, tours } = useToursContext();
useEffect(() => {
// Fetch detailed tour data (including description) for all tours
tours?.results?.forEach((item) => {
axiosInstance
.get(`/api/tours/${item.id}/`)
.then((response) => {
setToursDetail((prev) => [...prev, { ...item, ...response.data }]); // Combine tour data with fetched detail
})
.catch((error) => {
console.log(error);
});
});
}, [tours]);
// Filter tours based on title or description using the fetched tour details
const filterdTours = toursDetails?.filter((item) => {
return (
item.title.toLowerCase().includes(value.toLowerCase()) ||
item.description?.toLowerCase().includes(value.toLowerCase())
);
});
return ( return (
<React.Fragment> <React.Fragment>
<Popover className={`relative ${className}`}> <Popover className={`relative ${className}`}>
@ -29,7 +56,7 @@ const SearchDropdown: FC<Props> = ({ className = "" }) => {
<Transition <Transition
show={open} show={open}
as={Fragment}
as="div"
enter="transition ease-out duration-200" enter="transition ease-out duration-200"
enterFrom="opacity-0 translate-y-1" enterFrom="opacity-0 translate-y-1"
enterTo="opacity-100 translate-y-0" enterTo="opacity-100 translate-y-0"
@ -41,15 +68,26 @@ const SearchDropdown: FC<Props> = ({ className = "" }) => {
static static
className="absolute right-0 z-10 top-full w-screen max-w-sm" className="absolute right-0 z-10 top-full w-screen max-w-sm"
> >
<form action="" method="POST">
<Input <Input
value={value}
onChange={(e) => {
setValue(e.target.value);
}}
ref={inputRef} ref={inputRef}
rounded="rounded-full" rounded="rounded-full"
type="search"
placeholder="Type and press enter"
/>
<input type="submit" hidden value="" />
</form>
type="text"
placeholder="Type and search"
/>{value.replaceAll(" " , "") &&(
<Popover.Panel className="absolute right-0 z-10 top-full w-screen max-w-sm bg-white shadow-md rounded-3xl mt-1 ">
{filterdTours?.length ? (
filterdTours?.map((item) => (
<SearchCard key={item.id} data={item} />
))
) : (
<h3 className="p-4 text-center">No Matches Found</h3>
)}
</Popover.Panel>
)}
</Popover.Panel> </Popover.Panel>
</Transition> </Transition>
</> </>

4
src/app/(client-components)/(HeroSearchForm2Mobile)/(stay-search-form)/StaySearchForm.tsx

@ -134,9 +134,9 @@ const StaySearchForm = () => {
{/* */} {/* */}
{renderInputLocation()} {renderInputLocation()}
{/* */} {/* */}
{renderInputDates()}
{/* {renderInputDates()} */}
{/* */} {/* */}
{renderInputGuests()}
{/* {renderInputGuests()} */}
</div> </div>
</div> </div>
); );

21
src/app/(client-components)/(HeroSearchForm2Mobile)/HeroSearchForm2Mobile.tsx

@ -1,6 +1,6 @@
"use client"; "use client";
import React, { Fragment, useState } from "react";
import React, { Fragment, useEffect, useState } from "react";
import { Dialog, Tab, Transition } from "@headlessui/react"; import { Dialog, Tab, Transition } from "@headlessui/react";
import { MagnifyingGlassIcon } from "@heroicons/react/24/outline"; import { MagnifyingGlassIcon } from "@heroicons/react/24/outline";
import { XMarkIcon } from "@heroicons/react/24/solid"; import { XMarkIcon } from "@heroicons/react/24/solid";
@ -9,12 +9,15 @@ import { useTimeoutFn } from "react-use";
import StaySearchForm from "./(stay-search-form)/StaySearchForm"; import StaySearchForm from "./(stay-search-form)/StaySearchForm";
import CarsSearchForm from "./(car-search-form)/CarsSearchForm"; import CarsSearchForm from "./(car-search-form)/CarsSearchForm";
import FlightSearchForm from "./(flight-search-form)/FlightSearchForm"; import FlightSearchForm from "./(flight-search-form)/FlightSearchForm";
import { usePathname, useRouter } from "next/navigation";
const HeroSearchForm2Mobile = () => { const HeroSearchForm2Mobile = () => {
const [showModal, setShowModal] = useState(false); const [showModal, setShowModal] = useState(false);
// FOR RESET ALL DATA WHEN CLICK CLEAR BUTTON // FOR RESET ALL DATA WHEN CLICK CLEAR BUTTON
const [showDialog, setShowDialog] = useState(false); const [showDialog, setShowDialog] = useState(false);
const path = usePathname()
let [, , resetIsShowingDialog] = useTimeoutFn(() => setShowDialog(true), 1); let [, , resetIsShowingDialog] = useTimeoutFn(() => setShowDialog(true), 1);
// //
function closeModal() { function closeModal() {
@ -24,7 +27,9 @@ const HeroSearchForm2Mobile = () => {
function openModal() { function openModal() {
setShowModal(true); setShowModal(true);
} }
useEffect(()=>{
closeModal()
} , [path])
const renderButtonOpenModal = () => { const renderButtonOpenModal = () => {
return ( return (
<button <button
@ -88,7 +93,7 @@ const HeroSearchForm2Mobile = () => {
</div> </div>
<Tab.List className="pt-12 flex w-full justify-center font-semibold text-sm sm:text-base text-neutral-500 dark:text-neutral-400 space-x-6 sm:space-x-8"> <Tab.List className="pt-12 flex w-full justify-center font-semibold text-sm sm:text-base text-neutral-500 dark:text-neutral-400 space-x-6 sm:space-x-8">
{["Stay", "Experiences", "Cars", "Flights"].map(
{/* {["Stay", "Experiences", "Cars", "Flights"].map(
(item, index) => ( (item, index) => (
<Tab key={index} as={Fragment}> <Tab key={index} as={Fragment}>
{({ selected }) => ( {({ selected }) => (
@ -109,7 +114,7 @@ const HeroSearchForm2Mobile = () => {
)} )}
</Tab> </Tab>
) )
)}
)} */}
</Tab.List> </Tab.List>
<div className="flex-1 pt-3 px-1.5 sm:px-4 flex overflow-hidden"> <div className="flex-1 pt-3 px-1.5 sm:px-4 flex overflow-hidden">
<Tab.Panels className="flex-1 overflow-y-auto hiddenScrollbar py-4"> <Tab.Panels className="flex-1 overflow-y-auto hiddenScrollbar py-4">
@ -118,7 +123,7 @@ const HeroSearchForm2Mobile = () => {
<StaySearchForm /> <StaySearchForm />
</div> </div>
</Tab.Panel> </Tab.Panel>
<Tab.Panel>
{/* <Tab.Panel>
<div className="transition-opacity animate-[myblur_0.4s_ease-in-out]"> <div className="transition-opacity animate-[myblur_0.4s_ease-in-out]">
<StaySearchForm /> <StaySearchForm />
</div> </div>
@ -132,10 +137,10 @@ const HeroSearchForm2Mobile = () => {
<div className="transition-opacity animate-[myblur_0.4s_ease-in-out]"> <div className="transition-opacity animate-[myblur_0.4s_ease-in-out]">
<FlightSearchForm /> <FlightSearchForm />
</div> </div>
</Tab.Panel>
</Tab.Panel> */}
</Tab.Panels> </Tab.Panels>
</div> </div>
<div className="px-4 py-3 bg-white dark:bg-neutral-900 border-t border-neutral-200 dark:border-neutral-700 flex justify-between">
{/* <div className="px-4 py-3 bg-white dark:bg-neutral-900 border-t border-neutral-200 dark:border-neutral-700 flex justify-between">
<button <button
type="button" type="button"
className="underline font-semibold flex-shrink-0" className="underline font-semibold flex-shrink-0"
@ -151,7 +156,7 @@ const HeroSearchForm2Mobile = () => {
closeModal(); closeModal();
}} }}
/> />
</div>
</div> */}
</Tab.Group> </Tab.Group>
)} )}
</Dialog.Panel> </Dialog.Panel>

47
src/app/(client-components)/(HeroSearchForm2Mobile)/LocationInput.tsx

@ -1,6 +1,8 @@
"use client"; "use client";
import { useToursContext } from "@/components/contexts/tourDetails";
import { MapPinIcon, MagnifyingGlassIcon } from "@heroicons/react/24/outline"; import { MapPinIcon, MagnifyingGlassIcon } from "@heroicons/react/24/outline";
import { useRouter } from "next/navigation";
import React, { useState, useEffect, useRef, FC } from "react"; import React, { useState, useEffect, useRef, FC } from "react";
interface Props { interface Props {
@ -20,18 +22,21 @@ const LocationInput: FC<Props> = ({
const [value, setValue] = useState(""); const [value, setValue] = useState("");
const containerRef = useRef(null); const containerRef = useRef(null);
const inputRef = useRef(null); const inputRef = useRef(null);
const { tours } = useToursContext()
const router = useRouter()
useEffect(() => { useEffect(() => {
setValue(defaultValue); setValue(defaultValue);
}, [defaultValue]); }, [defaultValue]);
const handleSelectLocation = (item: string) => { const handleSelectLocation = (item: string) => {
// DO NOT REMOVE SETTIMEOUT FUNC
setTimeout(() => {
setValue(item);
onChange && onChange(item);
}, 0);
console.log(item);
}; };
console.log(tours.results);
const filterdTours = tours?.results?.filter((item)=>{return item.title.toLowerCase().includes(value.toLowerCase())})
const renderSearchValues = ({ const renderSearchValues = ({
heading, heading,
@ -49,12 +54,12 @@ const LocationInput: FC<Props> = ({
{items.map((item) => { {items.map((item) => {
return ( return (
<div <div
className="py-2 mb-1 flex items-center space-x-3 text-sm"
onClick={() => handleSelectLocation(item)}
key={item}
className="cursor-pointer py-2 mb-1 flex items-center space-x-3 text-sm"
onClick={() => router.push(`/tours/${item?.slug}-${item?.id}`)}
key={item.id}
> >
<MapPinIcon className="w-5 h-5 text-neutral-500 dark:text-neutral-400" /> <MapPinIcon className="w-5 h-5 text-neutral-500 dark:text-neutral-400" />
<span className="">{item}</span>
<span className="">{item.title}</span>
</div> </div>
); );
})} })}
@ -72,7 +77,7 @@ const LocationInput: FC<Props> = ({
<div className="relative mt-5"> <div className="relative mt-5">
<input <input
className={`block w-full bg-transparent border px-4 py-3 pr-12 border-neutral-900 dark:border-neutral-200 rounded-xl focus:ring-0 focus:outline-none text-base leading-none placeholder-neutral-500 dark:placeholder-neutral-300 truncate font-bold placeholder:truncate`} className={`block w-full bg-transparent border px-4 py-3 pr-12 border-neutral-900 dark:border-neutral-200 rounded-xl focus:ring-0 focus:outline-none text-base leading-none placeholder-neutral-500 dark:placeholder-neutral-300 truncate font-bold placeholder:truncate`}
placeholder={"Search destinations"}
placeholder={"Search Tours"}
value={value} value={value}
onChange={(e) => setValue(e.currentTarget.value)} onChange={(e) => setValue(e.currentTarget.value)}
ref={inputRef} ref={inputRef}
@ -84,29 +89,17 @@ const LocationInput: FC<Props> = ({
<div className="mt-7"> <div className="mt-7">
{value {value
? renderSearchValues({ ? renderSearchValues({
heading: "Locations",
items: [
"Afghanistan",
"Albania",
"Algeria",
"American Samao",
"Andorra",
],
heading: "Tours",
items: filterdTours,
}) })
: renderSearchValues({ : renderSearchValues({
heading: "Popular destinations",
items: [
"Australia",
"Canada",
"Germany",
"United Kingdom",
"United Arab Emirates",
],
heading: "All Tours",
items: tours?.results,
})} })}
</div> </div>
</div> </div>
</div> </div>
); );
}; };
// `/tours/${details?.slug}-${details?.id}`
export default LocationInput; export default LocationInput;

2
src/app/add-listing/[[...stepIndex]]/PageAddListing1.tsx

@ -41,7 +41,7 @@ const PageAddListing1: FC<PageAddListing1Props> = ({
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
if(!Object.keys(user).length){ if(!Object.keys(user).length){
router.replace("/")
router.replace("/signup")
} }
useEffect(() => { useEffect(() => {

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

@ -39,7 +39,7 @@ const PageAddListing10: FC<PageAddListing10Props> = () => {
console.log(error); console.log(error);
}); });
}else{ }else{
router.replace("/")
router.replace("/signup")
} }
}, []); }, []);

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

@ -27,6 +27,13 @@ const CommonLayout: FC<CommonLayoutProps> = () => {
const router = useRouter(); const router = useRouter();
useEffect(() => {
if (!Object.keys(user).length) {
router.replace("/signup");
}
}, [user, router]);
const [countries, setCountries] = useState<Country[]>([]); const [countries, setCountries] = useState<Country[]>([]);
const [startCity, setStartCity] = useState<string>(""); const [startCity, setStartCity] = useState<string>("");
const [startDate, setStartDate] = useState<string>(""); const [startDate, setStartDate] = useState<string>("");

2
src/app/faq/table.tsx

@ -28,7 +28,7 @@ const Table: React.FC<TableProps> = ({ faq, isActive, onClick }) => {
transition={{ duration: 0.4, ease: [0.6, 0.01, -0.05, 0.95] }} transition={{ duration: 0.4, ease: [0.6, 0.01, -0.05, 0.95] }}
style={{ overflow: "hidden" }} style={{ overflow: "hidden" }}
> >
<p className={`mt-2 text-gray-700 ${isActive ? "block" : "hidden"}`}>
<p className={`mt-2 text-gray-700 dark:text-white ${isActive ? "block" : "hidden"}`}>
{faq.answer} {faq.answer}
</p> </p>
</motion.div> </motion.div>

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

@ -9,18 +9,21 @@ import { useRouter } from "next/navigation";
import { useUserContext } from "@/components/contexts/userContext"; import { useUserContext } from "@/components/contexts/userContext";
import { ToastContainer, toast } from "react-toastify"; import { ToastContainer, toast } from "react-toastify";
import "react-toastify/dist/ReactToastify.css"; import "react-toastify/dist/ReactToastify.css";
import { PhoneNumberUtil, PhoneNumberFormat } from "google-libphonenumber"; // Import libphonenumber
export interface PageSignUpProps {} export interface PageSignUpProps {}
const PageSignUp: FC<PageSignUpProps> = () => { const PageSignUp: FC<PageSignUpProps> = () => {
const router = useRouter(); const router = useRouter();
const { setForm, setMethod } = useUserContext(); const { setForm, setMethod } = useUserContext();
const phoneUtil = PhoneNumberUtil.getInstance();
const [name, setName] = useState(''); const [name, setName] = useState('');
const [countryCode, setCountryCode] = useState('968'); 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 [loading , setLoading] = useState(false)
const [errors, setErrors] = useState<{ phoneNumber?: string; password?: string; confirmPassword?: string }>({}); const [errors, setErrors] = useState<{ phoneNumber?: string; password?: string; confirmPassword?: string }>({});
@ -30,16 +33,30 @@ const PageSignUp: FC<PageSignUpProps> = () => {
} }
}; };
const validatePhoneNumber = () => {
try {
const number = phoneUtil.parseAndKeepRawInput("+" + countryCode + phoneNumber, countryCode);
if (!phoneUtil.isValidNumber(number)) {
return "Invalid phone number.";
}
return null;
} catch (error) {
return "Invalid phone number.";
}
};
const validateForm = () => { const validateForm = () => {
const newErrors: { phoneNumber?: string; password?: string; confirmPassword?: string } = {}; const newErrors: { phoneNumber?: string; password?: string; confirmPassword?: string } = {};
if (!phoneNumber) newErrors.phoneNumber = "Phone number is required.";
const phoneError = validatePhoneNumber();
if (phoneError) newErrors.phoneNumber = phoneError;
if (!password) newErrors.password = "Password is required."; if (!password) newErrors.password = "Password is required.";
if (password !== confirmPassword) newErrors.confirmPassword = "Passwords do not match."; if (password !== confirmPassword) newErrors.confirmPassword = "Passwords do not match.";
setErrors(newErrors); setErrors(newErrors);
return Object.keys(newErrors).length === 0; // Return true if no errors return Object.keys(newErrors).length === 0; // Return true if no errors
}; };
useEffect(() => { useEffect(() => {
Object.values(errors).forEach((error) => { Object.values(errors).forEach((error) => {
toast.error(error, { toast.error(error, {
@ -54,10 +71,11 @@ const PageSignUp: FC<PageSignUpProps> = () => {
}); });
}); });
}, [errors]); }, [errors]);
const submitHandler = async () => { const submitHandler = async () => {
setLoading(true)
setErrors({}); // Clear previous errors setErrors({}); // Clear previous errors
if (!validateForm()) { if (!validateForm()) {
return; // Prevent submission if there are validation errors return; // Prevent submission if there are validation errors
} }
@ -90,7 +108,10 @@ const PageSignUp: FC<PageSignUpProps> = () => {
progress: undefined, progress: undefined,
theme: "light", theme: "light",
}); });
}finally{
setLoading(false)
} }
}; };
return ( return (
@ -100,16 +121,13 @@ const PageSignUp: FC<PageSignUpProps> = () => {
Change Password Change Password
</h2> </h2>
<div className="max-w-md mx-auto space-y-6"> <div className="max-w-md mx-auto space-y-6">
{/* FORM */}
<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"> <span className="text-neutral-800 dark:text-neutral-200">
Phone Number Phone Number
</span> </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`}> <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>
<span className="px-2 mr-[-15px] text-neutral-800 dark:text-neutral-200">+</span>
<input <input
value={countryCode} value={countryCode}
onChange={countryCodeHandler} onChange={countryCodeHandler}
@ -145,11 +163,10 @@ const PageSignUp: FC<PageSignUpProps> = () => {
className={`mt-1 ${errors.confirmPassword ? "border-red-600" : ""}`} className={`mt-1 ${errors.confirmPassword ? "border-red-600" : ""}`}
/> />
</label> </label>
<ButtonPrimary onClick={submitHandler}>Continue</ButtonPrimary>
<ButtonPrimary loading={loading} onClick={submitHandler}>Continue</ButtonPrimary>
</form> </form>
</div> </div>
</div> </div>
</div> </div>
); );
}; };

50
src/app/login/page.tsx

@ -9,16 +9,19 @@ import { useRouter } from "next/navigation";
import { useUserContext } from "@/components/contexts/userContext"; import { useUserContext } from "@/components/contexts/userContext";
import { ToastContainer, toast } from "react-toastify"; import { ToastContainer, toast } from "react-toastify";
import "react-toastify/dist/ReactToastify.css"; import "react-toastify/dist/ReactToastify.css";
import { PhoneNumberUtil } from 'google-libphonenumber';
export interface PageLoginProps {} export interface PageLoginProps {}
const PageLogin: FC<PageLoginProps> = () => { const PageLogin: FC<PageLoginProps> = () => {
const { user, setUser } = useUserContext(); const { user, setUser } = useUserContext();
const router = useRouter(); const router = useRouter();
const phoneUtil = PhoneNumberUtil.getInstance();
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>("968"); const [countryCode, setCountryCode] = useState<string>("968");
const [loading, setLoading] = useState<boolean>(false);
const [errors, setErrors] = useState<{ phoneNumber?: string; password?: string }>({}); 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
@ -41,7 +44,7 @@ const PageLogin: FC<PageLoginProps> = () => {
theme: "light", theme: "light",
}); });
}); });
} , [errors])
}, [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) {
@ -49,12 +52,26 @@ const PageLogin: FC<PageLoginProps> = () => {
} }
}; };
const validatePhoneNumber = () => {
try {
const parsedNumber = phoneUtil.parseAndKeepRawInput("+" + countryCode + phoneNumber, countryCode);
if (!phoneUtil.isValidNumber(parsedNumber)) {
return "Invalid phone number.";
}
return null;
} catch (error) {
return "Invalid phone number format.";
}
};
const validateForm = () => { const validateForm = () => {
const newErrors: { phoneNumber?: string; password?: string } = {}; const newErrors: { phoneNumber?: string; password?: string } = {};
if (!phoneNumber) {
newErrors.phoneNumber = "Phone number is required.";
const phoneError = validatePhoneNumber();
if (phoneError) {
newErrors.phoneNumber = phoneError;
} }
if (!password) { if (!password) {
newErrors.password = "Password is required."; newErrors.password = "Password is required.";
} }
@ -67,21 +84,23 @@ const PageLogin: FC<PageLoginProps> = () => {
setErrors({}); // Clear previous errors setErrors({}); // Clear previous errors
if (!validateForm()) { if (!validateForm()) {
return; // Prevent submission if there are validation errors return; // Prevent submission if there are validation errors
} }
setLoading(true);
try { try {
const response = await axiosInstance.post(`/api/account/login/`, { const response = await axiosInstance.post(`/api/account/login/`, {
phone_number: phoneNumber,
phone_number: `${countryCode}${phoneNumber}`,
password, password,
}); });
if (response.status === 201) { if (response.status === 201) {
toast.success("Your login was successful")
toast.success("Login successful!");
setUser(response.data); setUser(response.data);
router.push("/"); // Redirect to home or any other page after successful login
} else { } else {
toast.error("Something went wrong", {
toast.error("Login failed, please check your credentials.", {
position: "top-right", position: "top-right",
autoClose: 5000, autoClose: 5000,
hideProgressBar: false, hideProgressBar: false,
@ -105,7 +124,7 @@ const PageLogin: FC<PageLoginProps> = () => {
theme: "light", theme: "light",
}); });
} else { } else {
toast.error("An unknown error occurred", {
toast.error("An unknown error occurred.", {
position: "top-right", position: "top-right",
autoClose: 5000, autoClose: 5000,
hideProgressBar: false, hideProgressBar: false,
@ -116,6 +135,8 @@ const PageLogin: FC<PageLoginProps> = () => {
theme: "light", theme: "light",
}); });
} }
} finally {
setLoading(false);
} }
}; };
@ -131,10 +152,10 @@ const PageLogin: FC<PageLoginProps> = () => {
<span className="text-neutral-800 dark:text-neutral-200"> <span className="text-neutral-800 dark:text-neutral-200">
Phone Number Phone Number
</span> </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>
<div className={`flex items-center mt-1 rounded-2xl ${
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}
@ -168,7 +189,9 @@ const PageLogin: FC<PageLoginProps> = () => {
/> />
</label> </label>
<ButtonPrimary onClick={submitHandler}>Continue</ButtonPrimary>
<ButtonPrimary onClick={submitHandler} loading={loading}>
Continue
</ButtonPrimary>
</form> </form>
<span className="block text-center text-neutral-700 dark:text-neutral-300"> <span className="block text-center text-neutral-700 dark:text-neutral-300">
@ -178,7 +201,6 @@ const PageLogin: FC<PageLoginProps> = () => {
</span> </span>
</div> </div>
</div> </div>
</div> </div>
); );
}; };

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

@ -10,7 +10,7 @@ import { MdOutlineTextsms } from "react-icons/md";
function SelectMethods() { function SelectMethods() {
const router = useRouter(); const router = useRouter();
const { user, form } = useUserContext();
const { user, form ,setForm } = useUserContext();
const [selectedMethod, setSelectedMethod] = useState(""); const [selectedMethod, setSelectedMethod] = useState("");
const [error, setError] = useState(""); const [error, setError] = useState("");
@ -56,6 +56,8 @@ function SelectMethods() {
}); });
if (response.status === 202) { if (response.status === 202) {
setForm((prev) => ({ ...prev, verification_method: selectedMethod }));
setLoading(false); setLoading(false);
router.replace("signup/otp-code"); router.replace("signup/otp-code");
} }
@ -68,6 +70,7 @@ function SelectMethods() {
}); });
if (response.status === 202) { if (response.status === 202) {
setForm((prev) => ({ ...prev, verification_method: selectedMethod }));
setLoading(false); setLoading(false);
router.replace("signup/otp-code"); router.replace("signup/otp-code");
} }

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

@ -24,6 +24,8 @@ const PageSignUp: FC<PageSignUpProps> = () => {
const [error, setError] = useState<string>(""); const [error, setError] = useState<string>("");
const otpRefs = useRef<(HTMLInputElement | null)[]>([]); const otpRefs = useRef<(HTMLInputElement | null)[]>([]);
const [loading, setLoading] = useState(false);
const handleOtpChange = (value: string, index: number) => { const handleOtpChange = (value: string, index: number) => {
if (/^[0-9]?$/.test(value)) { if (/^[0-9]?$/.test(value)) {
const newOtp = [...otp]; const newOtp = [...otp];
@ -46,17 +48,66 @@ const PageSignUp: FC<PageSignUpProps> = () => {
useEffect(() => { useEffect(() => {
if (time > 0) { if (time > 0) {
const timer = setInterval(() => setTime((prevTime) => prevTime - 1), 1000);
const timer = setInterval(
() => setTime((prevTime) => prevTime - 1),
1000
);
return () => clearInterval(timer); return () => clearInterval(timer);
} }
}, [time]); }, [time]);
const handleResend = () => {
const handleResend = async () => {
if (time === 0) { if (time === 0) {
setTime(30); setTime(30);
// Logic to resend OTP can be added here
setLoading(true);
try {
const payload = {
phone_number: form.phoneNumber,
verification_method: form.verification_method,
range_phone: form.countryCode,
};
if (form.method === "register") {
payload.fullname = form.name;
payload.password = form.password;
payload.password_confirmation = form.confirmPassword;
const response = await axiosInstance.post(
`/api/account/register/`,
payload,
{
headers: { Accept: "application/json" },
}
);
if (response.status === 202) {
setLoading(false);
router.replace("signup/otp-code");
}
} else if (form.method === "reset") {
payload.password = form.password;
payload.password_confirmation = form.confirmPassword;
const response = await axiosInstance.post(
`/api/account/recover/`,
payload,
{
headers: { Accept: "application/json" },
}
);
if (response.status === 202) {
setLoading(false);
}
}
} catch (error) {
setError(error);
console.log(error);
setLoading(false);
}
} }
}; };
console.log(form);
const submitHandler = async () => { const submitHandler = async () => {
setError(""); setError("");
@ -69,11 +120,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")
toast.success("Your Sign In was successful");
} else { } else {
toast.error("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
toast.error(error.response?.data?.message || "An error occurred."); toast.error(error.response?.data?.message || "An error occurred.");
} }
}; };
@ -85,7 +137,8 @@ const PageSignUp: FC<PageSignUpProps> = () => {
Verification Code Verification Code
</h2> </h2>
<p className="text-center text-sm text-neutral-500 mb-4"> <p className="text-center text-sm text-neutral-500 mb-4">
Enter the 5-digit code that we sent to complete your account registration
Enter the 5-digit code that we sent to complete your account
registration
</p> </p>
<div className="max-w-sm mx-auto space-y-6"> <div className="max-w-sm mx-auto space-y-6">
<div className="flex justify-center space-x-2 mb-4"> <div className="flex justify-center space-x-2 mb-4">
@ -105,13 +158,17 @@ const PageSignUp: FC<PageSignUpProps> = () => {
<p className="text-center text-sm text-neutral-500 mb-4"> <p className="text-center text-sm text-neutral-500 mb-4">
Haven't got the confirmation code yet?{" "} Haven't got the confirmation code yet?{" "}
<button <button
className={`text-primary-600 hover:underline ${time > 0 ? "cursor-not-allowed opacity-50" : "cursor-pointer"}`}
className={`text-primary-600 hover:underline ${
time > 0 ? "cursor-not-allowed opacity-50" : "cursor-pointer"
}`}
onClick={handleResend} onClick={handleResend}
disabled={time > 0} disabled={time > 0}
> >
Resend Resend
</button> </button>
{time > 0 && <span className="text-xs text-neutral-400">({time} Seconds)</span>}
{time > 0 && (
<span className="text-xs text-neutral-400">({time} Seconds)</span>
)}
</p> </p>
{error && <p className="text-red-500 text-xs">{error}</p>} {error && <p className="text-red-500 text-xs">{error}</p>}
<ButtonPrimary <ButtonPrimary

18
src/app/signup/page.tsx

@ -51,7 +51,7 @@ const PageSignUp: FC<PageSignUpProps> = () => {
theme: "light", theme: "light",
}); });
}); });
} , [errors])
}, [errors]);
const submitHandler = async () => { const submitHandler = async () => {
const form = { const form = {
@ -102,16 +102,13 @@ const PageSignUp: FC<PageSignUpProps> = () => {
placeholder="Full Name" placeholder="Full Name"
className={`mt-1 ${errors.name ? "border-red-600" : "border-neutral-300"}`} className={`mt-1 ${errors.name ? "border-red-600" : "border-neutral-300"}`}
/> />
</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 ${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>
<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}
@ -153,7 +150,7 @@ const PageSignUp: FC<PageSignUpProps> = () => {
onClick={submitHandler} onClick={submitHandler}
disabled={loading} disabled={loading}
> >
{loading ? "Loading..." : "Continue"}
Continue
</ButtonPrimary> </ButtonPrimary>
</form> </form>
<span className="not-italic block text-center text-neutral-700 dark:text-neutral-300"> <span className="not-italic block text-center text-neutral-700 dark:text-neutral-300">
@ -164,7 +161,6 @@ const PageSignUp: FC<PageSignUpProps> = () => {
</span> </span>
</div> </div>
</div> </div>
</div> </div>
); );
}; };

138
src/app/tours/Card.tsx

@ -0,0 +1,138 @@
import React, { FC } from "react";
import { DEMO_STAY_LISTINGS } from "@/data/listings";
import { StayDataType } from "@/data/types";
import StartRating from "@/components/StartRating";
import BtnLikeIcon from "@/components/BtnLikeIcon";
import SaleOffBadge from "@/components/SaleOffBadge";
import Badge from "@/shared/Badge";
import Link from "next/link";
import Image from "next/image";
import calender from "../../images/Group.svg";
export interface StayCard2Props {
className?: string;
data?: StayDataType;
size?: "default" | "small";
}
const DEMO_DATA = DEMO_STAY_LISTINGS[0];
const StayCard2: FC<StayCard2Props> = ({
size = "default",
className = "",
data = DEMO_DATA,
}) => {
const {
galleryImgs,
listingCategory,
address,
title,
bedrooms,
href,
like,
saleOff,
isAds,
price,
reviewStart,
reviewCount,
id,
} = data;
// Function to format the dates
const formatDate = (dateStr: string) => {
const date = new Date(dateStr);
return date.toLocaleDateString("en-GB", {
day: "2-digit",
month: "short",
year: "numeric",
});
};
// Function to calculate the number of days between two dates
const calculateDuration = (start: string, end: string) => {
const startDate = new Date(start);
const endDate = new Date(end);
const timeDiff = endDate.getTime() - startDate.getTime();
const daysDiff = Math.ceil(timeDiff / (1000 * 3600 * 24)); // Convert ms to days
return daysDiff;
};
const renderSliderGallery = () => {
return (
<>
<Link
href={`/tours/${data?.slug}-${data?.id}`}
className="relative w-full"
>
<div className="h-40 lg:h-56 sm:h-40 overflow-hidden rounded-xl">
<Image
className="w-full h-full object-cover"
src={data?.image?.image_url?.md || "/default-image.jpg"}
alt={title || "Stay Image"}
width={500}
height={300}
/>
</div>
</Link>
{
<Badge
className="opacity-70 absolute left-3 top-3"
name={data.status}
color={data.status}
/>
}
</>
);
};
console.log(data);
const renderContent = () => {
const formattedStartDate = formatDate(data.started_at);
const formattedEndDate = formatDate(data.ended_at);
const tripDuration = calculateDuration(data.started_at, data.ended_at);
return (
<div className={size === "default" ? "mt-3 space-y-3" : "mt-2 space-y-2"}>
<div className="space-y-2">
<div className="flex items-center space-x-2">
<h2
className={`font-semibold capitalize text-neutral-900 dark:text-white ${
size === "default" ? "text-base" : "text-base"
}`}
>
<span className="line-clamp-1">{title}</span>
</h2>
</div>
<div className="flex items-center text-neutral-500 dark:text-neutral-400 text-sm space-x-1.5">
<Image alt="calendar" src={calender} />
<span className="">
{formattedStartDate} - {formattedEndDate}
</span>
</div>
<p className="flex items-center text-neutral-500 dark:text-neutral-400 text-sm space-x-1.5">
({tripDuration} Days)
</p>
</div>
<div className="w-14 border-b border-neutral-100 dark:border-neutral-800"></div>
<div className="flex justify-between items-center">
<span className="text-base font-semibold">
{price}
{` `}
</span>
{!!reviewStart && (
<StartRating reviewCount={reviewCount} point={reviewStart} />
)}
</div>
</div>
);
};
return (
<div className={`nc-StayCard2 group relative ${className}`}>
{renderSliderGallery()}
<Link href={`/tours/${data?.slug}-${data?.id}`}>{renderContent()}</Link>
</div>
);
};
export default StayCard2;

150
src/app/tours/SectionGridFilterCard.tsx

@ -5,16 +5,10 @@ import { DEMO_STAY_LISTINGS } from "@/data/listings";
import { StayDataType } from "@/data/types"; import { StayDataType } from "@/data/types";
import TabFilters from "./TabFilters"; import TabFilters from "./TabFilters";
import Heading2 from "@/shared/Heading2"; import Heading2 from "@/shared/Heading2";
import StayCard2 from "@/components/StayCard2";
import { Context, useToursContext } from "@/components/contexts/tourDetails";
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";
import StayCard2 from "./Card";
import { useToursContext } from "@/components/contexts/tourDetails";
import { useParams, useSearchParams } from "next/navigation";
import { useRouter } from "next/navigation";
export interface SectionGridFilterCardProps { export interface SectionGridFilterCardProps {
className?: string; className?: string;
@ -25,26 +19,27 @@ const SectionGridFilterCard: FC<SectionGridFilterCardProps> = ({
className = "", className = "",
data = DEMO_STAY_LISTINGS, data = DEMO_STAY_LISTINGS,
}) => { }) => {
const { countries, tours } = useToursContext()
const { countries, tours } = useToursContext();
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();
const [currentIndex, setCurrentIndex] = useState(0);
const [numberOfItems, setNumberOfItems] = useState(4); // Adjust as needed
const [direction, setDirection] = useState(0);
const searchParams = useSearchParams()
console.log(searchParams);
// Get the list of selected countries // Get the list of selected countries
const filteredCountries = Object.keys(checked).filter(
(countryName) => checked[countryName]
);
const windowWidth = useWindowSize().width;
const filteredCountries = Object.keys(checked).filter((countryName) => checked[countryName]);
useEffect(()=>{ useEffect(()=>{
const country = searchParams.get("country");
if (country) {
setChecked({ [country]: true });
const country = searchParams.get("country")
if (searchParams.has("country")){
setChecked({
[country] : true
})
} }
}, []);
} , [searchParams])
console.log(checked);
useEffect(() => { useEffect(() => {
if (!tours.results) return; if (!tours.results) return;
@ -65,108 +60,31 @@ const SectionGridFilterCard: FC<SectionGridFilterCardProps> = ({
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"
>
<Heading2 />
<div className={`nc-SectionGridFilterCard container ${className}`} data-nc-id="SectionGridFilterCard">
<div className={`mb-12 lg:mb-16 ${className}`}>
<h2 className="text-4xl font-semibold">{"All Tours"}</h2>
{/* <span className="block text-neutral-500 dark:text-neutral-400 mt-3">
233 stays
<span className="mx-2">·</span>
Aug 12 - 18
<span className="mx-2">·</span>2 Guests
</span> */}
</div>
<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>
<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 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>
)} )}
</div> </div>
</MotionConfig>
{countryTours.length === 0 && <h2>No tours Available</h2>}
</div> </div>
); );
}; };

4
src/app/tours/TabFilters.tsx

@ -652,12 +652,12 @@ const renderMoreFilterItem = (
</div> </div>
<div className=" p-4 sm:p-6 flex-shrink-0 bg-neutral-50 dark:bg-neutral-900 dark:border-t dark:border-neutral-800 flex items-center justify-between"> <div className=" p-4 sm:p-6 flex-shrink-0 bg-neutral-50 dark:bg-neutral-900 dark:border-t dark:border-neutral-800 flex items-center justify-between">
<ButtonThird
{/* <ButtonThird
onClick={closeModalMoreFilterMobile} onClick={closeModalMoreFilterMobile}
sizeClass="px-4 py-2 sm:px-5" sizeClass="px-4 py-2 sm:px-5"
> >
Clear Clear
</ButtonThird>
</ButtonThird> */}
<ButtonPrimary <ButtonPrimary
onClick={closeModalMoreFilterMobile} onClick={closeModalMoreFilterMobile}
sizeClass="px-4 py-2 sm:px-5" sizeClass="px-4 py-2 sm:px-5"

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

@ -489,12 +489,15 @@ const ListingStayDetailPage: FC<ListingStayDetailPageProps> = ({}) => {
<div className="listingSectionSidebar__wrap shadow-xl"> <div className="listingSectionSidebar__wrap shadow-xl">
{/* PRICE */} {/* PRICE */}
<div className="flex justify-between"> <div className="flex justify-between">
<span className="text-3xl font-semibold">
{details?.price}
{/* <span className="ml-1 text-base font-normal text-neutral-500 dark:text-neutral-400">
/night
</span> */}
</span>
{+details?.price === +details?.final_price ? (
<span className="text-3xl font-semibold">{details?.price}</span>
) : (
<div >
<span className="mr-2 text-3xl font-semibold">{details?.final_price}</span>
<span className="line-through">{details?.price}</span>{" "}
</div>
)}
<StartRating /> <StartRating />
</div> </div>
@ -509,13 +512,13 @@ const ListingStayDetailPage: FC<ListingStayDetailPageProps> = ({}) => {
<div className="flex flex-col space-y-4"> <div className="flex flex-col space-y-4">
<div className="flex justify-between text-neutral-6000 dark:text-neutral-300"> <div className="flex justify-between text-neutral-6000 dark:text-neutral-300">
<span> <span>
{details?.price} x {passengers} passengers
{details?.final_price} x {passengers} passengers
</span> </span>
<span> <span>
{" "} {" "}
{isNaN(details?.price * passengers)
{isNaN(details?.final_price * passengers)
? "N/A" // Or any fallback value, like "0" or a string message ? "N/A" // Or any fallback value, like "0" or a string message
: (details?.price * passengers).toString()}
: (details?.final_price * passengers).toString()}
</span> </span>
</div> </div>
<div className="flex justify-between text-neutral-6000 dark:text-neutral-300"> <div className="flex justify-between text-neutral-6000 dark:text-neutral-300">
@ -554,13 +557,15 @@ const ListingStayDetailPage: FC<ListingStayDetailPageProps> = ({}) => {
<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">
{details && <Image
{details && (
<Image
fill fill
className="object-cover rounded-md sm:rounded-xl" className="object-cover rounded-md sm:rounded-xl"
src={details?.images[0]?.image_url.lg} src={details?.images[0]?.image_url.lg}
alt="" alt=""
sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 50vw" sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 50vw"
/>}
/>
)}
{/* <div className="aspect-w-4 aspect-h-3 sm:aspect-w-6 sm:aspect-h-5"> {/* <div className="aspect-w-4 aspect-h-3 sm:aspect-w-6 sm:aspect-h-5">
<Image <Image
fill fill

14
src/components/FooterNav.tsx

@ -4,6 +4,8 @@ import {
HeartIcon, HeartIcon,
MagnifyingGlassIcon, MagnifyingGlassIcon,
UserCircleIcon, UserCircleIcon,
HomeIcon ,
BriefcaseIcon
} from "@heroicons/react/24/outline"; } from "@heroicons/react/24/outline";
import React, { useEffect, useRef } from "react"; import React, { useEffect, useRef } from "react";
import { PathName } from "@/routers/types"; import { PathName } from "@/routers/types";
@ -25,17 +27,17 @@ interface NavItem {
const NAV: NavItem[] = [ const NAV: NavItem[] = [
{ {
name: "Explore",
name: "Home",
link: "/", link: "/",
icon: MagnifyingGlassIcon,
icon: HomeIcon,
}, },
{ {
name: "Wishlists",
link: "/account-savelists",
icon: HeartIcon,
name: "My Trips",
link: "/my-trips",
icon: BriefcaseIcon,
}, },
{ {
name: "Log in",
name: "Account",
link: "/account", link: "/account",
icon: UserCircleIcon, icon: UserCircleIcon,
}, },

75
src/components/SearchCard.tsx

@ -0,0 +1,75 @@
import { useEffect, useState } from "react";
import { useToursContext } from "./contexts/tourDetails";
import Image from "next/image";
import axiosInstance from "./api/axios";
import calender from "./../images/Group.svg";
function SearchCard({ data }) {
const { details } = useToursContext();
const [tourDetail, setTourDetail] = useState();
console.log(data.image.image_url.sm);
useEffect(() => {
axiosInstance
.get(`/api/tours/${data.id}/`)
.then((response) => {
setTourDetail(response.data);
})
.catch((error) => {
console.log(error);
});
}, [data]);
const formatDate = (dateStr: string) => {
const date = new Date(dateStr);
return date.toLocaleDateString("en-GB", {
day: "2-digit",
month: "short",
year: "numeric",
});
};
// Function to calculate the number of days between two dates
const calculateDuration = (start: string, end: string) => {
const startDate = new Date(start);
const endDate = new Date(end);
const timeDiff = endDate.getTime() - startDate.getTime();
const daysDiff = Math.ceil(timeDiff / (1000 * 3600 * 24)); // Convert ms to days
return daysDiff;
};
const formattedStartDate = formatDate(data.started_at);
const formattedEndDate = formatDate(data.ended_at);
const tripDuration = calculateDuration(data.started_at, data.ended_at);
if (tourDetail) {
return (
<div className="flex hover:bg-neutral-100 p-4 cursor-pointer rounded-3xl">
<div className="h-30 inset-0 w-30 h-30 overflow-hidden rounded-xl relative">
<Image
className="max-h-16 object-cover"
width={100}
height={100}
alt={data.title}
src={data.image.image_url.sm}
/>
</div>
<div className="ml-4">
<h3 className="text-amber-800">{data?.title}</h3>
<div className="flex items-center text-neutral-500 dark:text-neutral-400 text-sm space-x-1.5">
<Image alt="calendar" src={calender} />
<span className="">
{formattedStartDate} - {formattedEndDate}
</span>
</div>
<p className="flex items-center text-neutral-500 dark:text-neutral-400 text-sm space-x-1.5">
({tripDuration} Days)
</p>
</div>
</div>
);
}
}
export default SearchCard;

3
src/components/StayCard2.tsx

@ -41,6 +41,8 @@ const StayCard2: FC<StayCard2Props> = ({
const renderSliderGallery = () => { const renderSliderGallery = () => {
return ( return (
<> <>
<Link <Link
@ -65,6 +67,7 @@ const StayCard2: FC<StayCard2Props> = ({
}; };
const renderContent = () => { const renderContent = () => {
return ( return (
<div className={size === "default" ? "mt-3 space-y-3" : "mt-2 space-y-2"}> <div className={size === "default" ? "mt-3 space-y-3" : "mt-2 space-y-2"}>
<div className="space-y-2"> <div className="space-y-2">

5
src/components/contexts/tourDetails.tsx

@ -1,4 +1,5 @@
"use client"; "use client";
import { usePathname } from "next/navigation";
import axiosInstance from "../api/axios"; import axiosInstance from "../api/axios";
import React, { createContext, useContext, useEffect, useState, ReactNode } from "react"; import React, { createContext, useContext, useEffect, useState, ReactNode } from "react";
@ -45,6 +46,7 @@ export const ContextProvider = ({ children }: ContextProviderProps) => {
const [passengers, setPassengers] = useState<number>(0); const [passengers, setPassengers] = useState<number>(0);
const [tours, setTours] = useState<Tour[]>([]); const [tours, setTours] = useState<Tour[]>([]);
const [countries, setCountries] = useState<Country[]>([]); const [countries, setCountries] = useState<Country[]>([]);
const path = usePathname()
useEffect(() => { useEffect(() => {
axiosInstance axiosInstance
@ -68,6 +70,9 @@ export const ContextProvider = ({ children }: ContextProviderProps) => {
}); });
}, []); }, []);
useEffect(()=>{
setDetails(undefined)
} , [path])
const getTourData = async (item: number) => { const getTourData = async (item: number) => {
try { try {
const response = await axiosInstance.get(`/api/tours/${item}/`); const response = await axiosInstance.get(`/api/tours/${item}/`);

5
src/components/contexts/userContext.tsx

@ -1,5 +1,6 @@
"use client"; "use client";
import { useRouter } from "next/navigation";
import React, { createContext, useContext, useState, ReactNode, useEffect } from "react"; import React, { createContext, useContext, useState, ReactNode, useEffect } from "react";
type UserContextType = { type UserContextType = {
@ -22,7 +23,7 @@ export const UserProvider: React.FC<UserProviderProps> = ({ children }) => {
const [form, setForm] = useState<Record<string, any>>({}); const [form, setForm] = useState<Record<string, any>>({});
const [user, setUser] = useState<Record<string, any>>({}); const [user, setUser] = useState<Record<string, any>>({});
const [method, setMethod] = useState<any[]>([]); const [method, setMethod] = useState<any[]>([]);
const router = useRouter()
// Load user from localStorage on initial render // Load user from localStorage on initial render
useEffect(() => { useEffect(() => {
const storedUser = localStorage.getItem("user"); const storedUser = localStorage.getItem("user");
@ -44,6 +45,8 @@ export const UserProvider: React.FC<UserProviderProps> = ({ children }) => {
const clerUser = () => { const clerUser = () => {
setUser({}); setUser({});
localStorage.removeItem("user"); localStorage.removeItem("user");
router.replace("/")
}; };
return ( return (

1
src/data/navigation.ts

@ -175,7 +175,6 @@ export const NAVIGATION_DEMO: NavItemType[] = [
href: "/tours", href: "/tours",
name: "All Tours", name: "All Tours",
type: "dropdown", type: "dropdown",
children: demoChildMenus,
}, },
{ {
id: ncNanoId(), id: ncNanoId(),

23
src/hooks/FormValidation.ts

@ -1,17 +1,10 @@
// hooks/FormValidation.ts // hooks/FormValidation.ts
import { useState } from 'react'; import { useState } from 'react';
// Define a type for the form structure
interface SignUpForm {
name: string;
countryCode: string;
phoneNumber: string;
password: string;
confirmPassword: string;
}
import { PhoneNumberUtil } from 'google-libphonenumber';
const useFormValidation = () => { const useFormValidation = () => {
const [errors, setErrors] = useState<Record<string, string>>({}); const [errors, setErrors] = useState<Record<string, string>>({});
const phoneUtil = PhoneNumberUtil.getInstance();
const validateForm = (form: SignUpForm) => { const validateForm = (form: SignUpForm) => {
let newErrors: Record<string, string> = {}; let newErrors: Record<string, string> = {};
@ -24,8 +17,14 @@ const useFormValidation = () => {
newErrors.countryCode = 'Country Code must be a number with up to 3 digits'; newErrors.countryCode = 'Country Code must be a number with up to 3 digits';
} }
if (!form.phoneNumber || !/^\d+$/.test(form.phoneNumber)) {
newErrors.phoneNumber = 'Phone Number is required and must be a number';
// Validate phone number using google-libphonenumber
try {
const parsedNumber = phoneUtil.parseAndKeepRawInput("+" + form.countryCode + form.phoneNumber, form.countryCode);
if (!phoneUtil.isValidNumber(parsedNumber)) {
newErrors.phoneNumber = 'Invalid phone number for the selected country';
}
} catch (error) {
newErrors.phoneNumber = 'Invalid phone number format';
} }
if (!form.password) { if (!form.password) {
@ -44,7 +43,7 @@ const useFormValidation = () => {
return Object.keys(newErrors).length === 0; return Object.keys(newErrors).length === 0;
}; };
return { errors, validateForm, setErrors }; // Optionally return setErrors
return { errors, validateForm, setErrors };
}; };
export default useFormValidation; export default useFormValidation;

7
src/images/Group.svg

@ -0,0 +1,7 @@
<svg width="13" height="12" viewBox="0 0 13 12" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M12.0489 4.11768V10.571C12.0489 11.0599 11.6578 11.4999 11.12 11.4999H1.92889C1.44 11.4999 1 11.1088 1 10.571V4.11768H12.0489Z" stroke="#D09460" stroke-width="0.75" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M12.0489 2.40667V4.11779H1.04889V2.40667C1.04889 1.91778 1.44 1.47778 1.97778 1.47778H11.1689C11.6089 1.52667 12.0489 1.91778 12.0489 2.40667Z" stroke="#D09460" stroke-width="0.75" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M4.42223 7.78448H3.34668C3.15112 7.78448 3.00446 7.63781 3.00446 7.44225V6.31781C3.00446 6.12225 3.15112 5.97559 3.34668 5.97559H4.47112C4.66668 5.97559 4.81335 6.12225 4.81335 6.31781V7.44225C4.81335 7.63781 4.61779 7.78448 4.42223 7.78448Z" stroke="#D09460" stroke-width="0.75" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M3.88446 2.26V0.5" stroke="#D09460" stroke-width="0.75" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M9.16443 0.5V2.26" stroke="#D09460" stroke-width="0.75" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

3
src/images/Vector.svg

@ -0,0 +1,3 @@
<svg width="13" height="9" viewBox="0 0 13 9" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M12.0489 1.11768V7.57102C12.0489 8.05991 11.6578 8.49991 11.12 8.49991H1.92889C1.44 8.49991 1 8.1088 1 7.57102V1.11768H12.0489Z" stroke="#D09460" stroke-width="0.75" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

9
src/shared/Navigation/NavMobile.tsx

@ -128,14 +128,7 @@ const NavMobile: React.FC<NavMobileProps> = ({
{data.map(_renderItem)} {data.map(_renderItem)}
</ul> </ul>
<div className="flex items-center justify-between py-6 px-5"> <div className="flex items-center justify-between py-6 px-5">
<a
className="inline-block"
href="https://themeforest.net/item/chisfis-online-booking-nextjs-template/43399526"
target="_blank"
rel="noopener noreferrer"
>
<ButtonPrimary>Get Template</ButtonPrimary>
</a>
<ButtonPrimary href="/custom-trip">Custom Tour</ButtonPrimary>
<LangDropdown <LangDropdown
className="flex" className="flex"

15
yarn.lock

@ -227,6 +227,11 @@
lodash.merge "^4.6.2" lodash.merge "^4.6.2"
postcss-selector-parser "6.0.10" postcss-selector-parser "6.0.10"
"@types/google-libphonenumber@^7.4.30":
version "7.4.30"
resolved "https://registry.npmjs.org/@types/google-libphonenumber/-/google-libphonenumber-7.4.30.tgz"
integrity sha512-Td1X1ayRxePEm6/jPHUBs2tT6TzW1lrVB6ZX7ViPGellyzO/0xMNi+wx5nH6jEitjznq276VGIqjK5qAju0XVw==
"@types/google-map-react@^2.1.7": "@types/google-map-react@^2.1.7":
version "2.1.7" version "2.1.7"
resolved "https://registry.npmjs.org/@types/google-map-react/-/google-map-react-2.1.7.tgz" resolved "https://registry.npmjs.org/@types/google-map-react/-/google-map-react-2.1.7.tgz"
@ -1439,6 +1444,11 @@ globby@^13.1.3:
merge2 "^1.4.1" merge2 "^1.4.1"
slash "^4.0.0" slash "^4.0.0"
google-libphonenumber@^3.2.38:
version "3.2.38"
resolved "https://registry.npmjs.org/google-libphonenumber/-/google-libphonenumber-3.2.38.tgz"
integrity sha512-t/K0dsVmA0gMMVLJgcMeB9g1Ar4ANVWfkY+AJGSdfyJ2Ay7Bu8ceLYpUlC6FZSilZgaF1qbkM9tZydGBEBHqAg==
google-map-react@^2.2.1: google-map-react@^2.2.1:
version "2.2.1" version "2.2.1"
resolved "https://registry.npmjs.org/google-map-react/-/google-map-react-2.2.1.tgz" resolved "https://registry.npmjs.org/google-map-react/-/google-map-react-2.2.1.tgz"
@ -1874,6 +1884,11 @@ levn@^0.4.1:
prelude-ls "^1.2.1" prelude-ls "^1.2.1"
type-check "~0.4.0" type-check "~0.4.0"
libphonenumber-js@^1.11.9:
version "1.11.9"
resolved "https://registry.npmjs.org/libphonenumber-js/-/libphonenumber-js-1.11.9.tgz"
integrity sha512-Zs5wf5HaWzW2/inlupe2tstl0I/Tbqo7lH20ZLr6Is58u7Dz2n+gRFGNlj9/gWxFvNfp9+YyDsiegjNhdixB9A==
lilconfig@^2.0.5, lilconfig@^2.1.0: lilconfig@^2.0.5, lilconfig@^2.1.0:
version "2.1.0" version "2.1.0"
resolved "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz" resolved "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz"

Loading…
Cancel
Save