Browse Source

API connected , Staticks changed

main
sina_sajjadi 2 weeks ago
parent
commit
e5490dee8b
  1. 4
      .env.local.example
  2. 10
      next.config.js
  3. 358
      package-lock.json
  4. 4
      package.json
  5. 11
      public/لوگو3 1.svg
  6. 2
      src/app/(account-pages)/(components)/Nav.tsx
  7. 32
      src/app/(account-pages)/account-billing/page.tsx
  8. 196
      src/app/(account-pages)/account/page.tsx
  9. 55
      src/app/(account-pages)/passengers-list/PassengerTable.tsx
  10. 52
      src/app/(account-pages)/passengers-list/page.tsx
  11. 22
      src/app/(client-components)/(Header)/Header.tsx
  12. 36
      src/app/(client-components)/(Header)/MainNav1.tsx
  13. 13
      src/app/(client-components)/(Header)/SiteHeader.tsx
  14. 85
      src/app/(client-components)/(HeroSearchForm)/(stay-search-form)/StayDatesRangeInput.tsx
  15. 2
      src/app/(client-components)/(HeroSearchForm)/ButtonSubmit.tsx
  16. 58
      src/app/(client-components)/(HeroSearchForm)/GuestsInput.tsx
  17. 18
      src/app/(client-components)/(HeroSearchForm)/HeroSearchForm.tsx
  18. 56
      src/app/(client-components)/(HeroSearchForm)/LocationInput.tsx
  19. 2
      src/app/(listing-detail)/SectionDateRange.tsx
  20. 7
      src/app/(listing-detail)/layout.tsx
  21. 98
      src/app/add-listing/[[...stepIndex]]/PageAddListing1.tsx
  22. 50
      src/app/add-listing/[[...stepIndex]]/layout.tsx
  23. 122
      src/app/add-listing/[[...stepIndex]]/page.tsx
  24. 227
      src/app/add-new-passenger/page.tsx
  25. 26
      src/app/api/hello/auth/[...nextauth].ts
  26. 3
      src/app/api/hello/route.ts
  27. 151
      src/app/forgot-password/page.tsx
  28. 5
      src/app/globals.css
  29. 11
      src/app/layout.tsx
  30. 168
      src/app/login/page.tsx
  31. 55
      src/app/page.tsx
  32. 150
      src/app/signup/methodes/page.tsx
  33. 132
      src/app/signup/otp-code/page.tsx
  34. 185
      src/app/signup/page.tsx
  35. 94
      src/app/tours/[slug]/GuestsInput.tsx
  36. 69
      src/app/tours/[slug]/StayDatesRangeInput.tsx
  37. 71
      src/app/tours/[slug]/constant.ts
  38. 648
      src/app/tours/[slug]/page.tsx
  39. 70
      src/app/tours/layout.tsx
  40. 9
      src/components/CardCategory3.tsx
  41. 27
      src/components/HeaderFilter.tsx
  42. 69
      src/components/SectionCustomTour.tsx
  43. 73
      src/components/SectionGridFeaturePlaces.tsx
  44. 55
      src/components/SectionSliderNewCategories.tsx
  45. 52
      src/components/StayCard2.tsx
  46. 31
      src/components/api/axios.tsx
  47. 36
      src/components/api/getImageURL.tsx
  48. 73
      src/components/contexts/tourDetails.tsx
  49. 26
      src/components/contexts/userContext.tsx
  50. 21
      src/data/jsons/__stayListing.json
  51. 368
      src/data/navigation.ts
  52. 49
      src/hooks/FormValidation.ts
  53. BIN
      src/images/hero-right-orginal.png
  54. BIN
      src/images/hero-right.png
  55. BIN
      src/images/logos/لوگو3 1.png
  56. 11
      src/images/logos/لوگو3 1.svg
  57. 36
      src/shared/Avatar.tsx
  58. 65
      src/shared/Badge.tsx
  59. 2
      src/shared/ButtonPrimary.tsx
  60. 1
      src/shared/Input.tsx
  61. 23
      src/shared/Logo.tsx
  62. 2
      src/shared/LogoSvgLight.tsx
  63. 3
      src/shared/Navigation/Navigation.tsx
  64. 0
      src/shared/PhoneNumberInput.tsx
  65. 3
      tailwind.config.js
  66. 82
      yarn.lock

4
.env.local.example

@ -1,4 +0,0 @@
NEXT_PUBLIC_CLOUDINARY_CLOUD_NAME=dwi7o19nn
CLOUDINARY_API_KEY=549144472596919
CLOUDINARY_API_SECRET=AnNiPOszr1R0YCelomUmi9IyuBM
CLOUDINARY_FOLDER=test

10
next.config.js

@ -1,10 +1,6 @@
/** @type {import('next').NextConfig} */
const nextConfig = {
reactStrictMode: false,
experimental: {
appDir: true,
typedRoutes: true,
},
images: {
remotePatterns: [
{
@ -31,6 +27,12 @@ const nextConfig = {
port: "",
pathname: "/**",
},
{
protocol: "https",
hostname: "aqila.nwhco.ir",
port: "",
pathname: "/**",
},
],
},
};

358
package-lock.json

@ -18,6 +18,7 @@
"@types/react": "18.2.7",
"@types/react-datepicker": "^4.11.2",
"@types/react-dom": "18.2.4",
"axios": "^1.7.5",
"client-only": "^0.0.1",
"eslint": "8.41.0",
"eslint-config-next": "^13.4.3",
@ -25,12 +26,13 @@
"google-map-react": "^2.2.1",
"lodash": "^4.17.21",
"next": "^13.4.3",
"next-auth": "^4.23.1",
"next-auth": "^4.24.7",
"rc-slider": "^10.1.1",
"react": "^18.2.0",
"react-datepicker": "^4.11.0",
"react-dom": "^18.2.0",
"react-hooks-global-state": "^2.1.0",
"react-icons": "^5.3.0",
"react-swipeable": "^7.0.0",
"react-use": "^17.4.0",
"react-use-keypress": "^1.3.1",
@ -303,6 +305,126 @@
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/@next/swc-darwin-arm64": {
"version": "13.4.3",
"resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-13.4.3.tgz",
"integrity": "sha512-yx18udH/ZmR4Bw4M6lIIPE3JxsAZwo04iaucEfA2GMt1unXr2iodHUX/LAKNyi6xoLP2ghi0E+Xi1f4Qb8f1LQ==",
"cpu": [
"arm64"
],
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@next/swc-darwin-x64": {
"version": "13.4.3",
"resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-13.4.3.tgz",
"integrity": "sha512-Mi8xJWh2IOjryAM1mx18vwmal9eokJ2njY4nDh04scy37F0LEGJ/diL6JL6kTXi0UfUCGbMsOItf7vpReNiD2A==",
"cpu": [
"x64"
],
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@next/swc-linux-arm64-gnu": {
"version": "13.4.3",
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-13.4.3.tgz",
"integrity": "sha512-aBvtry4bxJ1xwKZ/LVPeBGBwWVwxa4bTnNkRRw6YffJnn/f4Tv4EGDPaVeYHZGQVA56wsGbtA6nZMuWs/EIk4Q==",
"cpu": [
"arm64"
],
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@next/swc-linux-arm64-musl": {
"version": "13.4.3",
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-13.4.3.tgz",
"integrity": "sha512-krT+2G3kEsEUvZoYte3/2IscscDraYPc2B+fDJFipPktJmrv088Pei/RjrhWm5TMIy5URYjZUoDZdh5k940Dyw==",
"cpu": [
"arm64"
],
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@next/swc-linux-x64-gnu": {
"version": "13.4.3",
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-13.4.3.tgz",
"integrity": "sha512-AMdFX6EKJjC0G/CM6hJvkY8wUjCcbdj3Qg7uAQJ7PVejRWaVt0sDTMavbRfgMchx8h8KsAudUCtdFkG9hlEClw==",
"cpu": [
"x64"
],
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@next/swc-linux-x64-musl": {
"version": "13.4.3",
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-13.4.3.tgz",
"integrity": "sha512-jySgSXE48shaLtcQbiFO9ajE9mqz7pcAVLnVLvRIlUHyQYR/WyZdK8ehLs65Mz6j9cLrJM+YdmdJPyV4WDaz2g==",
"cpu": [
"x64"
],
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@next/swc-win32-arm64-msvc": {
"version": "13.4.3",
"resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-13.4.3.tgz",
"integrity": "sha512-5DxHo8uYcaADiE9pHrg8o28VMt/1kR8voDehmfs9AqS0qSClxAAl+CchjdboUvbCjdNWL1MISCvEfKY2InJ3JA==",
"cpu": [
"arm64"
],
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@next/swc-win32-ia32-msvc": {
"version": "13.4.3",
"resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-13.4.3.tgz",
"integrity": "sha512-LaqkF3d+GXRA5X6zrUjQUrXm2MN/3E2arXBtn5C7avBCNYfm9G3Xc646AmmmpN3DJZVaMYliMyCIQCMDEzk80w==",
"cpu": [
"ia32"
],
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@next/swc-win32-x64-msvc": {
"version": "13.4.3",
"resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-13.4.3.tgz",
@ -875,6 +997,11 @@
"integrity": "sha512-eBvWn1lvIApYMhzQMsu9ciLfkBY499mFZlNqG+/9WR7PVlroQw0vG30cOQQbaKz3sCEc44TAOu2ykzqXSNnwag==",
"license": "ISC"
},
"node_modules/asynckit": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
},
"node_modules/autoprefixer": {
"version": "10.4.14",
"resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.14.tgz",
@ -930,6 +1057,16 @@
"node": ">=4"
}
},
"node_modules/axios": {
"version": "1.7.5",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.7.5.tgz",
"integrity": "sha512-fZu86yCo+svH3uqJ/yTdQ0QHpQu5oL+/QE+QPSv6BZSkDAoky9vytxp7u5qk83OJFS3kEBcesWni9WTZAv3tSw==",
"dependencies": {
"follow-redirects": "^1.15.6",
"form-data": "^4.0.0",
"proxy-from-env": "^1.1.0"
}
},
"node_modules/axobject-query": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-3.1.1.tgz",
@ -1176,6 +1313,17 @@
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
"license": "MIT"
},
"node_modules/combined-stream": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
"dependencies": {
"delayed-stream": "~1.0.0"
},
"engines": {
"node": ">= 0.8"
}
},
"node_modules/commander": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz",
@ -1398,6 +1546,14 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/delayed-stream": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
"engines": {
"node": ">=0.4.0"
}
},
"node_modules/didyoumean": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz",
@ -2156,6 +2312,25 @@
"integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==",
"license": "ISC"
},
"node_modules/follow-redirects": {
"version": "1.15.6",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz",
"integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==",
"funding": [
{
"type": "individual",
"url": "https://github.com/sponsors/RubenVerborgh"
}
],
"engines": {
"node": ">=4.0"
},
"peerDependenciesMeta": {
"debug": {
"optional": true
}
}
},
"node_modules/for-each": {
"version": "0.3.3",
"resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz",
@ -2165,6 +2340,19 @@
"is-callable": "^1.1.3"
}
},
"node_modules/form-data": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
"integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
"dependencies": {
"asynckit": "^0.4.0",
"combined-stream": "^1.0.8",
"mime-types": "^2.1.12"
},
"engines": {
"node": ">= 6"
}
},
"node_modules/fraction.js": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.2.0.tgz",
@ -2992,9 +3180,9 @@
}
},
"node_modules/jose": {
"version": "4.14.6",
"resolved": "https://registry.npmjs.org/jose/-/jose-4.14.6.tgz",
"integrity": "sha512-EqJPEUlZD0/CSUMubKtMaYUOtWe91tZXTWMJZoKSbLk+KtdhNdcvppH8lA9XwVu2V4Ailvsj0GBZJ2ZwDjfesQ==",
"version": "4.15.9",
"resolved": "https://registry.npmjs.org/jose/-/jose-4.15.9.tgz",
"integrity": "sha512-1vUQX+IdDMVPj4k8kOxgUqlcK518yluMuGZwqlr44FS1ppZB/5GWh4rZG89erpOBOJjU/OBsnCVFfapsRz6nEA==",
"funding": {
"url": "https://github.com/sponsors/panva"
}
@ -3200,6 +3388,25 @@
"node": ">=8.6"
}
},
"node_modules/mime-db": {
"version": "1.52.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/mime-types": {
"version": "2.1.35",
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
"dependencies": {
"mime-db": "1.52.0"
},
"engines": {
"node": ">= 0.6"
}
},
"node_modules/mimic-fn": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz",
@ -3358,14 +3565,14 @@
}
},
"node_modules/next-auth": {
"version": "4.23.1",
"resolved": "https://registry.npmjs.org/next-auth/-/next-auth-4.23.1.tgz",
"integrity": "sha512-mL083z8KgRtlrIV6CDca2H1kduWJuK/3pTS0Fe2og15KOm4v2kkLGdSDfc2g+019aEBrJUT0pPW2Xx42ImN1WA==",
"version": "4.24.7",
"resolved": "https://registry.npmjs.org/next-auth/-/next-auth-4.24.7.tgz",
"integrity": "sha512-iChjE8ov/1K/z98gdKbn2Jw+2vLgJtVV39X+rCP5SGnVQuco7QOr19FRNGMIrD8d3LYhHWV9j9sKLzq1aDWWQQ==",
"dependencies": {
"@babel/runtime": "^7.20.13",
"@panva/hkdf": "^1.0.2",
"cookie": "^0.5.0",
"jose": "^4.11.4",
"jose": "^4.15.5",
"oauth": "^0.9.15",
"openid-client": "^5.4.0",
"preact": "^10.6.3",
@ -3373,7 +3580,7 @@
"uuid": "^8.3.2"
},
"peerDependencies": {
"next": "^12.2.5 || ^13",
"next": "^12.2.5 || ^13 || ^14",
"nodemailer": "^6.6.5",
"react": "^17.0.2 || ^18",
"react-dom": "^17.0.2 || ^18"
@ -3982,6 +4189,11 @@
"react-is": "^16.13.1"
}
},
"node_modules/proxy-from-env": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="
},
"node_modules/punycode": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz",
@ -4104,6 +4316,14 @@
"react": ">=16.8.0"
}
},
"node_modules/react-icons": {
"version": "5.3.0",
"resolved": "https://registry.npmjs.org/react-icons/-/react-icons-5.3.0.tgz",
"integrity": "sha512-DnUk8aFbTyQPSkCfF8dbX6kQjXA9DktMeJqfjrg6cK9vwQVMxmcA3BfP4QoiztVmEHtwlTgLFsPuH2NskKT6eg==",
"peerDependencies": {
"react": "*"
}
},
"node_modules/react-is": {
"version": "16.13.1",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
@ -5340,126 +5560,6 @@
"optional": true
}
}
},
"node_modules/@next/swc-darwin-arm64": {
"version": "13.4.3",
"resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-13.4.3.tgz",
"integrity": "sha512-yx18udH/ZmR4Bw4M6lIIPE3JxsAZwo04iaucEfA2GMt1unXr2iodHUX/LAKNyi6xoLP2ghi0E+Xi1f4Qb8f1LQ==",
"cpu": [
"arm64"
],
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@next/swc-darwin-x64": {
"version": "13.4.3",
"resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-13.4.3.tgz",
"integrity": "sha512-Mi8xJWh2IOjryAM1mx18vwmal9eokJ2njY4nDh04scy37F0LEGJ/diL6JL6kTXi0UfUCGbMsOItf7vpReNiD2A==",
"cpu": [
"x64"
],
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@next/swc-linux-arm64-gnu": {
"version": "13.4.3",
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-13.4.3.tgz",
"integrity": "sha512-aBvtry4bxJ1xwKZ/LVPeBGBwWVwxa4bTnNkRRw6YffJnn/f4Tv4EGDPaVeYHZGQVA56wsGbtA6nZMuWs/EIk4Q==",
"cpu": [
"arm64"
],
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@next/swc-linux-arm64-musl": {
"version": "13.4.3",
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-13.4.3.tgz",
"integrity": "sha512-krT+2G3kEsEUvZoYte3/2IscscDraYPc2B+fDJFipPktJmrv088Pei/RjrhWm5TMIy5URYjZUoDZdh5k940Dyw==",
"cpu": [
"arm64"
],
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@next/swc-linux-x64-gnu": {
"version": "13.4.3",
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-13.4.3.tgz",
"integrity": "sha512-AMdFX6EKJjC0G/CM6hJvkY8wUjCcbdj3Qg7uAQJ7PVejRWaVt0sDTMavbRfgMchx8h8KsAudUCtdFkG9hlEClw==",
"cpu": [
"x64"
],
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@next/swc-linux-x64-musl": {
"version": "13.4.3",
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-13.4.3.tgz",
"integrity": "sha512-jySgSXE48shaLtcQbiFO9ajE9mqz7pcAVLnVLvRIlUHyQYR/WyZdK8ehLs65Mz6j9cLrJM+YdmdJPyV4WDaz2g==",
"cpu": [
"x64"
],
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@next/swc-win32-arm64-msvc": {
"version": "13.4.3",
"resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-13.4.3.tgz",
"integrity": "sha512-5DxHo8uYcaADiE9pHrg8o28VMt/1kR8voDehmfs9AqS0qSClxAAl+CchjdboUvbCjdNWL1MISCvEfKY2InJ3JA==",
"cpu": [
"arm64"
],
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@next/swc-win32-ia32-msvc": {
"version": "13.4.3",
"resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-13.4.3.tgz",
"integrity": "sha512-LaqkF3d+GXRA5X6zrUjQUrXm2MN/3E2arXBtn5C7avBCNYfm9G3Xc646AmmmpN3DJZVaMYliMyCIQCMDEzk80w==",
"cpu": [
"ia32"
],
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">= 10"
}
}
}
}

4
package.json

@ -19,6 +19,7 @@
"@types/react": "18.2.7",
"@types/react-datepicker": "^4.11.2",
"@types/react-dom": "18.2.4",
"axios": "^1.7.5",
"client-only": "^0.0.1",
"eslint": "8.41.0",
"eslint-config-next": "^13.4.3",
@ -26,12 +27,13 @@
"google-map-react": "^2.2.1",
"lodash": "^4.17.21",
"next": "^13.4.3",
"next-auth": "^4.23.1",
"next-auth": "^4.24.7",
"rc-slider": "^10.1.1",
"react": "^18.2.0",
"react-datepicker": "^4.11.0",
"react-dom": "^18.2.0",
"react-hooks-global-state": "^2.1.0",
"react-icons": "^5.3.0",
"react-swipeable": "^7.0.0",
"react-use": "^17.4.0",
"react-use-keypress": "^1.3.1",

11
public/لوگو3 1.svg
File diff suppressed because it is too large
View File

2
src/app/(account-pages)/(components)/Nav.tsx

@ -12,7 +12,7 @@ export const Nav = () => {
"/account",
"/account-savelists",
"/account-password",
"/account-billing",
"/passengers-list",
];
return (

32
src/app/(account-pages)/account-billing/page.tsx

@ -1,32 +0,0 @@
import React from "react";
import ButtonPrimary from "@/shared/ButtonPrimary";
const AccountBilling = () => {
return (
<div className="space-y-6 sm:space-y-8">
{/* HEADING */}
<h2 className="text-3xl font-semibold">Payments & payouts</h2>
<div className="w-14 border-b border-neutral-200 dark:border-neutral-700"></div>
<div className="max-w-2xl">
<span className="text-xl font-semibold block">Payout methods</span>
<br />
<span className="text-neutral-700 dark:text-neutral-300 block">
{` When you receive a payment for a reservation, we call that payment
to you a "payout." Our secure payment system supports several
payout methods, which can be set up below. Go to FAQ.`}
<br />
<br />
To get paid, you need to set up a payout method Airbnb releases
payouts about 24 hours after a guests scheduled check-in time. The
time it takes for the funds to appear in your account depends on your
payout method. Learn more
</span>
<div className="pt-10">
<ButtonPrimary>Add payout mothod</ButtonPrimary>
</div>
</div>
</div>
);
};
export default AccountBilling;

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

@ -1,23 +1,131 @@
import React, { FC } from "react";
"use client";
import React, { useContext, useState } from "react";
import Label from "@/components/Label";
import Avatar from "@/shared/Avatar";
import ButtonPrimary from "@/shared/ButtonPrimary";
import Input from "@/shared/Input";
import Select from "@/shared/Select";
import Textarea from "@/shared/Textarea";
import ButtonSecondary from "@/shared/ButtonSecondary";
import axiosInstance from "@/components/api/axios";
import { useRouter } from "next/navigation";
import { user as UserContext } from "@/components/contexts/userContext";
import getImageURL from "@/components/api/getImageURL";
export interface AccountPageProps {}
const AccountPage = () => {
const router = useRouter();
const User = JSON.parse(localStorage.getItem("user"))
let user = JSON.parse(localStorage.getItem("user"));
if (!user) {
return router.replace("/");
}
const { setStatus } = useContext(UserContext);
const [name, setName] = useState(user.fullname || "");
const [email, setEmail] = useState(user.email || "");
const [number, setNumber] = useState(user.phone_number || "");
const [password, setPassword] = useState("");
const [image, setImage] = useState<File | null>(null);
const [imageURL, setImageURL] = useState<string | null>(null);
const [error, setError] = useState("");
const deleteHandler = async () => {
setStatus(false);
setError("");
try {
const response = await axiosInstance.delete(
`/api/account/profile/delete/`,
{
headers: {
Authorization: `token ${user.token}`,
},
}
);
if (response.status === 204) {
localStorage.removeItem("user");
router.replace("/");
} else {
setError("Something went wrong");
}
} catch (error) {
setError(error.message);
}
};
const signOutHandler = () => {
localStorage.removeItem("user");
setStatus(false);
router.replace("/");
};
const changeHandler = async () => {
setError("");
const formData = new FormData();
formData.append("fullname", name);
formData.append("email", email);
formData.append("password", password);
if (imageURL) {
formData.append("avatar", imageURL);
}
try {
const response = await axiosInstance.put(
`/api/account/profile/update/`,
formData,
{
headers: {
Authorization: `token ${user.token}`,
"Content-Type": "multipart/form-data",
},
}
);
if (response.status === 200) {
console.log(response);
user.avatar = response.data.avatar;
user.email = response.data.email;
user.fullname = response.data.fullname;
user.phone_number = response.data.phone_number;
localStorage.setItem("user", JSON.stringify(user));
} else {
setError("Something went wrong");
}
} catch (error) {
setError(error.message);
}
};
const handleFileChange = async (e: React.ChangeEvent<HTMLInputElement>) => {
const file = e.target.files?.[0];
if (file) {
const uploadedFile = await getImageURL(file);
setImageURL(uploadedFile.url);
};
setImage(file);
}
return (
<div className="space-y-6 sm:space-y-8">
{/* HEADING */}
<h2 className="text-3xl font-semibold">Account infomation</h2>
<h2 className="text-3xl font-semibold">Account information</h2>
<div className="w-14 border-b border-neutral-200 dark:border-neutral-700"></div>
<div className="flex flex-col md:flex-row">
<div className="flex-shrink-0 flex items-start">
<div className="relative rounded-full overflow-hidden flex">
<Avatar sizeClass="w-32 h-32" />
<Avatar
imgUrl={
imageURL || (image ? URL.createObjectURL(image) : User.avatar)
}
sizeClass="w-32 h-32"
/>
<div className="absolute inset-0 bg-black bg-opacity-60 flex flex-col items-center justify-center text-neutral-50 cursor-pointer">
<svg
width="30"
@ -40,55 +148,61 @@ const AccountPage = () => {
<input
type="file"
className="absolute inset-0 opacity-0 cursor-pointer"
onChange={handleFileChange}
accept="image/*"
/>
</div>
</div>
<div className="flex-grow mt-10 md:mt-0 md:pl-16 max-w-3xl space-y-6">
<div>
<Label>Name</Label>
<Input className="mt-1.5" defaultValue="Eden Tuan" />
</div>
{/* ---- */}
<div>
<Label>Gender</Label>
<Select className="mt-1.5">
<option value="Male">Male</option>
<option value="Female">Female</option>
<option value="Other">Other</option>
</Select>
</div>
{/* ---- */}
<div>
<Label>Username</Label>
<Input className="mt-1.5" defaultValue="@eden_tuan" />
<Input
value={name}
onChange={(e) => setName(e.target.value)}
className="mt-1.5"
/>
</div>
{/* ---- */}
<div>
<Label>Email</Label>
<Input className="mt-1.5" defaultValue="example@email.com" />
<Input
value={email}
onChange={(e) => setEmail(e.target.value)}
className="mt-1.5"
/>
</div>
{/* ---- */}
<div className="max-w-lg">
<Label>Date of birth</Label>
<Input className="mt-1.5" type="date" defaultValue="1990-07-22" />
</div>
{/* ---- */}
<div>
<Label>Addess</Label>
<Input className="mt-1.5" defaultValue="New york, USA" />
</div>
{/* ---- */}
<div>
<Label>Phone number</Label>
<Input className="mt-1.5" defaultValue="003 888 232" />
<Label>Phone</Label>
<Input
value={number}
onChange={(e) => setNumber(e.target.value)}
className="[appearance:textfield] [&::-webkit-outer-spin-button]:appearance-none [&::-webkit-inner-spin-button]:appearance-none mt-1.5"
type="text"
/>
</div>
{/* ---- */}
<div>
<Label>About you</Label>
<Textarea className="mt-1.5" defaultValue="..." />
<Label>Password</Label>
<Input
value={password}
onChange={(e) => setPassword(e.target.value)}
type="password"
className="mt-1.5"
/>
</div>
<div className="pt-2">
<ButtonPrimary>Update info</ButtonPrimary>
{error && <p className=" text-red-500 text-xs">{error}</p>}
<div className=" flex flex-col pt-2 sm:flex-row ">
<ButtonPrimary className="mt-8" onClick={changeHandler}>
Update info
</ButtonPrimary>
<ButtonPrimary className="mt-8 sm:ml-8 " onClick={signOutHandler}>
{" "}
Sign out
</ButtonPrimary>
<ButtonSecondary
onClick={deleteHandler}
className="opacity-60 mt-8 hover:bg-red-500 hover:text-white hover:opacity-100 sm:ml-8"
>
Delete account
</ButtonSecondary>
</div>
</div>
</div>

55
src/app/(account-pages)/passengers-list/PassengerTable.tsx

@ -0,0 +1,55 @@
import axiosInstance from "@/components/api/axios";
import React, { useState } from "react";
import { IoMdTrash } from "react-icons/io";
import { MdEdit } from "react-icons/md";
const PassengerTable = ({data}) => {
const user = JSON.parse(localStorage.getItem("user"))
const [show , setShow] =useState(true)
const deletHandler = async ()=>{
try{
const response = await axiosInstance.delete(`/api/account/passengers/${data.id}/` ,{
headers :{
Authorization : `token ${user.token}`
}
})
console.log(response);
setShow(false)
}catch (error){
console.log(error);
}
}
return (
<div className= {`${!show && "hidden" } sm:w-[500px] flex items-center justify-between p-4 bg-white rounded-xl shadow-sm border border-neutral-200 dark:bg-neutral-800`}>
{/* Passenger Information */}
<div className="flex flex-col">
<p className="text-sm text-neutral-500 dark:text-neutral-400">
Passenger information
</p>
<h3 className="text-lg font-semibold text-neutral-900 dark:text-white">
{data.fullname}
</h3>
</div>
{/* Action Icons */}
<div className="flex space-x-6 text-neutral-500">
<button className="hover:text-red-500 transition-colors">
<button type="submit">
<IoMdTrash onClick={deletHandler} className="text-2xl" /> {/* Increased size using text-2xl */}
</button>
</button>
<button className="hover:text-blue-500 transition-colors">
<MdEdit className="text-2xl" /> {/* Increased size using text-2xl */}
</button>
</div>
</div>
);
};
export default PassengerTable;

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

@ -0,0 +1,52 @@
"use client"
import React, { useEffect, useState } from "react";
import PassengerTable from "./PassengerTable";
import { IoPersonAddOutline } from "react-icons/io5";
import axiosInstance from "@/components/api/axios";
import Link from "next/link";
const PassengersList = () => {
const user = JSON.parse(localStorage.getItem("user"))
const [passengers , setPassenger ] = useState([])
useEffect(()=>{
axiosInstance.get("/api/account/passengers/" ,{
headers :{
Authorization : `token ${user.token}`
}
})
.then((response)=>{
setPassenger(response.data.results);
console.log(response);
}).catch((error)=>{
console.error(error);
})
console.log(passengers);
} , [])
return (
<div className="flex flex-col items-start space-y-6 sm:space-y-8">
{/* Add New Passenger Section */}
<Link href={"/add-new-passenger"} className="flex items-center space-x-2 text-orange-500 cursor-pointer hover:text-orange-600">
<IoPersonAddOutline className="text-xl" /> {/* Adjust icon size */}
<p className="text-sm font-medium">Add new passenger</p>
</Link>
{/* Passenger Table */}
{passengers.map((item)=>(
<PassengerTable key={item.id} data={item} />
))}
</div>
);
};
export default PassengersList;

22
src/app/(client-components)/(Header)/Header.tsx

@ -8,22 +8,22 @@ export interface HeaderProps {
}
const Header: FC<HeaderProps> = ({ navType = "MainNav1", className = "" }) => {
const renderNav = () => {
switch (navType) {
case "MainNav1":
return <MainNav1 />;
case "MainNav2":
return <MainNav2 />;
default:
return <MainNav1 />;
}
};
// const renderNav = () => {
// switch (navType) {
// case "MainNav1":
// return <MainNav1 />;
// case "MainNav2":
// return <MainNav2 />;
// default:
// return <MainNav1 />;
// }
// };
return (
<div
className={`nc-Header sticky top-0 w-full left-0 right-0 z-40 nc-header-bg ${className}`}
>
{renderNav()}
<MainNav1 />
</div>
);
};

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

@ -1,4 +1,4 @@
import React, { FC } from "react";
import React, { FC, useContext, useEffect, useState } from "react";
import Logo from "@/shared/Logo";
import Navigation from "@/shared/Navigation/Navigation";
import SearchDropdown from "./SearchDropdown";
@ -6,13 +6,27 @@ import ButtonPrimary from "@/shared/ButtonPrimary";
import MenuBar from "@/shared/MenuBar";
import SwitchDarkMode from "@/shared/SwitchDarkMode";
import HeroSearchForm2MobileFactory from "../(HeroSearchForm2Mobile)/HeroSearchForm2MobileFactory";
import LangDropdown from "./LangDropdown";
import { MdOutlineLanguage } from "react-icons/md";
import Avatar from "@/shared/Avatar";
import Link from "next/link";
import { user } from "@/components/contexts/userContext";
export interface MainNav1Props {
className?: string;
}
const MainNav1: FC<MainNav1Props> = ({ className = "" }) => {
const { status, setStatus } = useContext(user);
const User = JSON.parse(localStorage.getItem("user"))
User? setStatus(true) : setStatus(false)
console.log(status);
console.log(User.avatar);
return (
<div className={`nc-MainNav1 relative z-10 ${className}`}>
<div className="px-4 lg:container h-20 relative flex justify-between">
@ -28,16 +42,30 @@ const MainNav1: FC<MainNav1Props> = ({ className = "" }) => {
</div>
<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">
<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>
<SwitchDarkMode />
<SearchDropdown className="flex items-center" />
<div className="px-1" />
<ButtonPrimary className="self-center" href="/login">
{status ? (<Link href={"/account"}>
<Avatar
imgUrl={User.avatar}
sizeClass="w-10 h-10"
/>
</Link>
) : (
<ButtonPrimary className="self-center" href="/signup">
Sign up
</ButtonPrimary>
)}
</div>
<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">
<MdOutlineLanguage size={25} />
</div>
<SwitchDarkMode />
<div className="px-0.5" />
<MenuBar />

13
src/app/(client-components)/(Header)/SiteHeader.tsx

@ -34,7 +34,7 @@ const PAGES_HIDE_HEADER_BORDER: PathName[] = [
];
const SiteHeader = () => {
const anchorRef = useRef<HTMLDivElement>(null);
{/* const anchorRef = useRef<HTMLDivElement>(null);
let [headers] = useState<SiteHeaders[]>(["Header 1", "Header 2", "Header 3"]);
@ -91,7 +91,7 @@ const SiteHeader = () => {
}`}
onClick={() => setHeaderSelected(header)}
>
{header}
{headers[0]}
</div>
);
})}
@ -187,13 +187,14 @@ const SiteHeader = () => {
default:
return <Header className={headerClassName} navType="MainNav1" />;
}
};
};*/}
return (
<>
{renderControlSelections()}
{renderHeader()}
<div ref={anchorRef} className="h-1 absolute invisible"></div>
<Header className="shadow-sm dark:border-b dark:border-neutral-700" navType="MainNav1" />
{/* {renderControlSelections()}
{renderHeader()}*/}
{/* <div ref={anchorRef} className="h-1 absolute invisible"></div>*/}
</>
);
};

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

@ -1,12 +1,13 @@
"use client";
import React, { Fragment, useState, FC } from "react";
import React, { Fragment, useState, FC, useContext } from "react";
import { Popover, Transition } from "@headlessui/react";
import { CalendarIcon } from "@heroicons/react/24/outline";
import DatePickerCustomHeaderTwoMonth from "@/components/DatePickerCustomHeaderTwoMonth";
import DatePickerCustomDay from "@/components/DatePickerCustomDay";
import DatePicker from "react-datepicker";
import ClearDataButton from "../ClearDataButton";
import { Context } from "@/components/contexts/tourDetails";
export interface StayDatesRangeInputProps {
className?: string;
@ -17,11 +18,9 @@ const StayDatesRangeInput: FC<StayDatesRangeInputProps> = ({
className = "[ lg:nc-flex-2 ]",
fieldClassName = "[ nc-hero-field-padding ]",
}) => {
const [startDate, setStartDate] = useState<Date | null>(
new Date("2023/02/06")
);
const [endDate, setEndDate] = useState<Date | null>(new Date("2023/02/23"));
//
const { details} = useContext(Context);
const onChangeDate = (dates: [Date | null, Date | null]) => {
const [start, end] = dates;
@ -29,54 +28,58 @@ const StayDatesRangeInput: FC<StayDatesRangeInputProps> = ({
setEndDate(end);
};
const renderInput = () => {
// 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 (
<Popover className={`StayDatesRangeInput z-10 relative flex ${className}`}>
{({ open }) => (
<>
<div
className={`flex-1 z-10 flex relative ${fieldClassName} items-center space-x-3 focus:outline-none`}
>
<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?.toLocaleDateString("en-US", {
month: "short",
day: "2-digit",
}) || "Add dates"}
{endDate
? " - " +
endDate?.toLocaleDateString("en-US", {
month: "short",
day: "2-digit",
})
: ""}
{details?.started_at.replaceAll("-", "/") || "Tour period"}
{details?.ended_at && " - " + details?.ended_at.replaceAll("-", "/")}
</span>
<span className="block mt-1 text-sm text-neutral-400 leading-none font-light">
{"Check in - Check out"}
{"Start - End"}
</span>
</div>
</>
);
};
return (
<Popover className={`StayDatesRangeInput z-10 relative flex ${className}`}>
{({ open }) => (
<>
<Popover.Button
className={`flex-1 z-10 flex relative ${fieldClassName} items-center space-x-3 focus:outline-none ${
open ? "nc-hero-field-focused" : ""
}`}
>
{renderInput()}
{startDate && open && (
<ClearDataButton onClick={() => onChangeDate([null, null])} />
)}
</Popover.Button>
</div>
{open && (
{/* {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
{/* <Transition
as={Fragment}
enter="transition ease-out duration-200"
enterFrom="opacity-0 translate-y-1"
@ -105,7 +108,7 @@ const StayDatesRangeInput: FC<StayDatesRangeInputProps> = ({
/>
</div>
</Popover.Panel>
</Transition>
</Transition> */}
</>
)}
</Popover>

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

@ -11,7 +11,7 @@ const ButtonSubmit: FC<Props> = ({ href = "/listing-stay-map" }) => {
<Link
href={href}
type="button"
className="h-14 md:h-16 w-full md:w-16 rounded-full bg-primary-6000 hover:bg-primary-700 flex items-center justify-center text-neutral-50 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"
>
<span className="mr-3 md:hidden">Search</span>
<svg

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

@ -1,6 +1,7 @@
"use client";
import React, { Fragment, useEffect, useState } from "react";
"use client"
import React, { Fragment, useContext, useEffect, useState } from "react";
import { Popover, Transition } from "@headlessui/react";
import NcInputNumber from "@/components/NcInputNumber";
import { FC } from "react";
@ -9,6 +10,7 @@ import ButtonSubmit from "./ButtonSubmit";
import { PathName } from "@/routers/types";
import { UserPlusIcon } from "@heroicons/react/24/outline";
import { GuestsObject } from "../type";
import { Context } from "@/components/contexts/tourDetails";
export interface GuestsInputProps {
fieldClassName?: string;
@ -24,31 +26,23 @@ const GuestsInput: FC<GuestsInputProps> = ({
hasButtonSubmit = true,
}) => {
const [guestAdultsInputValue, setGuestAdultsInputValue] = useState(2);
const [guestChildrenInputValue, setGuestChildrenInputValue] = useState(1);
const [guestInfantsInputValue, setGuestInfantsInputValue] = useState(1);
const { passengers, details , setPassengers } = useContext(Context);
const handleChangeData = (value: number, type: keyof GuestsObject) => {
let newValue = {
guestAdults: guestAdultsInputValue,
guestChildren: guestChildrenInputValue,
guestInfants: guestInfantsInputValue,
};
if (type === "guestAdults") {
setGuestAdultsInputValue(value);
newValue.guestAdults = value;
}
if (type === "guestChildren") {
setGuestChildrenInputValue(value);
newValue.guestChildren = value;
}
if (type === "guestInfants") {
setGuestInfantsInputValue(value);
newValue.guestInfants = value;
}
setPassengers(value)
};
const totalGuests =
guestChildrenInputValue + guestAdultsInputValue + guestInfantsInputValue;
useEffect(()=>{
setPassengers(guestAdultsInputValue)
} , [])
console.log(passengers);
return (
<Popover className={`flex relative ${className}`}>
@ -67,19 +61,18 @@ const GuestsInput: FC<GuestsInputProps> = ({
</div>
<div className="flex-grow">
<span className="block xl:text-lg font-semibold">
{totalGuests || ""} Guests
{guestAdultsInputValue || ""} Passengers
</span>
<span className="block mt-1 text-sm text-neutral-400 leading-none font-light">
{totalGuests ? "Guests" : "Add guests"}
{guestAdultsInputValue ? "Passengers" : "Add passengers"}
</span>
</div>
{!!totalGuests && open && (
{!!guestAdultsInputValue && open && (
<ClearDataButton
onClick={() => {
setGuestAdultsInputValue(0);
setGuestChildrenInputValue(0);
setGuestInfantsInputValue(0);
setPassengers(0)
}}
/>
)}
@ -88,7 +81,7 @@ const GuestsInput: FC<GuestsInputProps> = ({
{/* BUTTON SUBMIT OF FORM */}
{hasButtonSubmit && (
<div className="pr-2 xl:pr-4">
<ButtonSubmit href={buttonSubmitHref} />
<ButtonSubmit href={`/tours/${details?.slug}-${details?.id}`} />
</div>
)}
</div>
@ -115,23 +108,6 @@ const GuestsInput: FC<GuestsInputProps> = ({
label="Adults"
desc="Ages 13 or above"
/>
<NcInputNumber
className="w-full mt-6"
defaultValue={guestChildrenInputValue}
onChange={(value) => handleChangeData(value, "guestChildren")}
max={4}
label="Children"
desc="Ages 2–12"
/>
<NcInputNumber
className="w-full mt-6"
defaultValue={guestInfantsInputValue}
onChange={(value) => handleChangeData(value, "guestInfants")}
max={4}
label="Infants"
desc="Ages 0–2"
/>
</Popover.Panel>
</Transition>
</>

18
src/app/(client-components)/(HeroSearchForm)/HeroSearchForm.tsx

@ -14,15 +14,15 @@ export interface HeroSearchFormProps {
currentPage?: "Stays" | "Experiences" | "Cars" | "Flights";
}
const HeroSearchForm: FC<HeroSearchFormProps> = ({
const HeroSearchForm: FC<HeroSearchFormProps> = ({/*
className = "",
currentTab = "Stays",
currentTab = "Flights",
currentPage,
}) => {
*/}) => {{/*
const tabs: SearchTab[] = ["Stays", "Experiences", "Cars", "Flights"];
const [tabActive, setTabActive] = useState<SearchTab>(currentTab);
const renderTab = () => {
{const renderTab = () => {
return (
<ul className="ml-2 sm:ml-6 md:ml-12 flex space-x-5 sm:space-x-8 lg:space-x-11 overflow-x-auto hiddenScrollbar">
{tabs.map((tab) => {
@ -46,7 +46,7 @@ const HeroSearchForm: FC<HeroSearchFormProps> = ({
})}
</ul>
);
};
};}
const renderForm = () => {
switch (tabActive) {
@ -62,14 +62,14 @@ const HeroSearchForm: FC<HeroSearchFormProps> = ({
default:
return null;
}
};
};*/}
return (
<div
className={`nc-HeroSearchForm w-full max-w-6xl py-5 lg:py-0 ${className}`}
className={`nc-HeroSearchForm w-full max-w-6xl py-5 lg:py-0`}
>
{renderTab()}
{renderForm()}
{/*renderTab()*/}
<StaySearchForm />
</div>
);
};

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

@ -1,8 +1,11 @@
"use client";
import { ClockIcon, MapPinIcon } from "@heroicons/react/24/outline";
import React, { useState, useRef, useEffect, FC } from "react";
import React, { useState, useRef, useEffect, useContext, FC } from "react";
import ClearDataButton from "./ClearDataButton";
import axiosInstance from "@/components/api/axios";
import { Context } from "@/components/contexts/tourDetails";
import { log } from "console";
export interface LocationInputProps {
placeHolder?: string;
@ -14,14 +17,15 @@ export interface LocationInputProps {
const LocationInput: FC<LocationInputProps> = ({
autoFocus = false,
placeHolder = "Location",
desc = "Where are you going?",
placeHolder = "Tours",
desc = "Select your tour",
className = "nc-flex-1.5",
divHideVerticalLineClass = "left-10 -right-0.5",
}) => {
const containerRef = useRef<HTMLDivElement>(null);
const inputRef = useRef<HTMLInputElement>(null);
const { getTourData, setPassengers, setDetails, tours } = useContext(Context);
const [value, setValue] = useState("");
const [showPopover, setShowPopover] = useState(autoFocus);
@ -56,34 +60,30 @@ const LocationInput: FC<LocationInputProps> = ({
setShowPopover(false);
};
const handleSelectLocation = (item: string) => {
setValue(item);
const handleSelectLocation = async (item: string) => {
setValue(item.title);
setShowPopover(false);
getTourData(item.id);
};
const renderRecentSearches = () => {
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">
Recent searches
Tours
</h3>
<div className="mt-2">
{[
"Hamptons, Suffolk County, NY",
"Las Vegas, NV, United States",
"Ueno, Taito, Tokyo",
"Ikebukuro, Toshima, Tokyo",
].map((item) => (
{tours.results.map((item) => (
<span
onClick={() => handleSelectLocation(item)}
key={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 text-neutral-400">
<ClockIcon className="h-4 sm:h-6 w-4 sm:w-6" />
</span>
<span className=" block font-medium text-neutral-700 dark:text-neutral-200">
{item}
{item.title}
</span>
</span>
))}
@ -95,25 +95,27 @@ const LocationInput: FC<LocationInputProps> = ({
const renderSearchValue = () => {
return (
<>
{[
"Ha Noi, Viet Nam",
"San Diego, CA",
"Humboldt Park, Chicago, IL",
"Bangor, Northern Ireland",
].map((item) => (
{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}
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 text-neutral-400">
<ClockIcon className="h-4 w-4 sm:h-6 sm:w-6" />
</span>
<span className="block font-medium text-neutral-700 dark:text-neutral-200">
{item}
{item.title}
</span>
</span>
))}
))
) : (
<h3>No match found</h3>
)}
</>
);
};
@ -147,6 +149,8 @@ const LocationInput: FC<LocationInputProps> = ({
<ClearDataButton
onClick={() => {
setValue("");
setDetails();
setPassengers();
}}
/>
)}

2
src/app/(listing-detail)/SectionDateRange.tsx

@ -49,7 +49,7 @@ const SectionDateRange = () => {
);
};
return renderSectionCheckIndate();
// return renderSectionCheckIndate();
};
export default SectionDateRange;

7
src/app/(listing-detail)/layout.tsx

@ -5,12 +5,14 @@ import ListingImageGallery from "@/components/listing-image-gallery/ListingImage
import SectionSliderNewCategories from "@/components/SectionSliderNewCategories";
import SectionSubscribe2 from "@/components/SectionSubscribe2";
import { usePathname, useRouter, useSearchParams } from "next/navigation";
import React, { ReactNode } from "react";
import React, { ReactNode, useEffect, useState } from "react";
import MobileFooterSticky from "./(components)/MobileFooterSticky";
import { imageGallery as listingStayImageGallery } from "./listing-stay-detail/constant";
import { imageGallery as listingCarImageGallery } from "./listing-car-detail/constant";
import { imageGallery as listingExperienceImageGallery } from "./listing-experiences-detail/constant";
import { Route } from "next";
import axiosInstance from "@/components/api/axios";
import axios from "axios";
const DetailtLayout = ({ children }: { children: ReactNode }) => {
const router = useRouter();
@ -25,6 +27,8 @@ const DetailtLayout = ({ children }: { children: ReactNode }) => {
};
const getImageGalleryListing = () => {
if (thisPathname?.includes("/listing-stay-detail")) {
return listingStayImageGallery;
}
@ -38,6 +42,7 @@ const DetailtLayout = ({ children }: { children: ReactNode }) => {
return [];
};
return (
<div className="ListingDetailPage">
<ListingImageGallery

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

@ -2,10 +2,28 @@ import React, { FC } from "react";
import Input from "@/shared/Input";
import Select from "@/shared/Select";
import FormItem from "../FormItem";
import { IoPersonAddOutline } from "react-icons/io5";
import getImageURL from "@/components/api/getImageURL";
export interface PageAddListing1Props {}
const PageAddListing1: FC<PageAddListing1Props> = () => {
const PageAddListing1: FC<PageAddListing1Props> = ({
Passenger,
setNewPassenger,
}) => {
const { fullName, date, number, passport } = Passenger;
const handleFileChange = async (e: React.ChangeEvent<HTMLInputElement>) => {
const file = e.target.files?.[0];
if (file) {
const uploadedFile = await getImageURL(file);
setNewPassenger((prev) => ({ ...prev, image: uploadedFile.url }));
}
};
return (
<>
<h2 className="text-2xl font-semibold">Choosing listing categories</h2>
@ -13,35 +31,59 @@ const PageAddListing1: FC<PageAddListing1Props> = () => {
{/* FORM */}
<div className="space-y-8">
{/* ITEM */}
<FormItem
label="Choose a property type"
desc="Hotel: Professional hospitality businesses that usually have a unique style or theme defining their brand and decor"
>
<Select>
<option value="Hotel">Hotel</option>
<option value="Cottage">Cottage</option>
<option value="Villa">Villa</option>
<option value="Cabin">Cabin</option>
<option value="Farm stay">Farm stay</option>
<option value="Houseboat">Houseboat</option>
<option value="Lighthouse">Lighthouse</option>
</Select>
<div className="flex items-center space-x-2 text-orange-500 cursor-pointer hover:text-orange-600">
<IoPersonAddOutline className="text-xl" /> {/* Adjust icon size */}
<p className="text-sm font-medium">Add From passengers List</p>
</div>
<FormItem label="Full Name" desc="">
<Input
onChange={(e) =>
setNewPassenger((prev) => ({ ...prev, fullName: e.target.value }))
}
value={fullName}
placeholder="Full Name"
/>
</FormItem>
<FormItem label="Passport Number" desc="">
<Input
type="number"
className="[appearance:textfield] [&::-webkit-outer-spin-button]:appearance-none [&::-webkit-inner-spin-button]:appearance-none"
onChange={(e) =>
setNewPassenger((prev) => ({ ...prev, passport: e.target.value }))
}
value={passport}
placeholder="Passport Number"
/>
</FormItem>
<FormItem label="Date of Birth" desc="">
<Input
type="date"
value={date}
onChange={(e) =>
setNewPassenger((prev) => ({ ...prev, date: e.target.value }))
}
placeholder="Date of Birth"
/>
</FormItem>
<FormItem
label="Place name"
desc="A catchy name usually includes: House name + Room name + Featured property + Tourist destination"
>
<Input placeholder="Places name" />
<FormItem label="Phone Number" desc="">
<Input
type="number"
className="[appearance:textfield] [&::-webkit-outer-spin-button]:appearance-none [&::-webkit-inner-spin-button]:appearance-none"
value={number}
onChange={(e) =>
setNewPassenger((prev) => ({ ...prev, number: e.target.value }))
}
placeholder="Phone Number"
/>
</FormItem>
<FormItem
label="Rental form"
desc="Entire place: Guests have the whole place to themselves—there's a private entrance and no shared spaces. A bedroom, bathroom, and kitchen are usually included."
>
<Select>
<option value="Hotel">Entire place</option>
<option value="Private room">Private room</option>
<option value="Share room">Share room</option>
</Select>
<FormItem label="Upload Passport image Here" desc="">
<Input
type="file"
onChange={handleFileChange}
placeholder="Passport"
/>
</FormItem>
</div>
</>

50
src/app/add-listing/[[...stepIndex]]/layout.tsx

@ -1,50 +0,0 @@
import React from "react";
import { FC } from "react";
import ButtonPrimary from "@/shared/ButtonPrimary";
import ButtonSecondary from "@/shared/ButtonSecondary";
import { Route } from "@/routers/types";
export interface CommonLayoutProps {
children: React.ReactNode;
params: {
stepIndex: string;
};
}
const CommonLayout: FC<CommonLayoutProps> = ({ children, params }) => {
const index = Number(params.stepIndex) || 1;
const nextHref = (
index < 10 ? `/add-listing/${index + 1}` : `/add-listing/${1}`
) as Route;
const backtHref = (
index > 1 ? `/add-listing/${index - 1}` : `/add-listing/${1}`
) as Route;
const nextBtnText = index > 9 ? "Publish listing" : "Continue";
return (
<div
className={`nc-PageAddListing1 px-4 max-w-3xl mx-auto pb-24 pt-14 sm:py-24 lg:pb-32`}
>
<div className="space-y-11">
<div>
<span className="text-4xl font-semibold">{index}</span>{" "}
<span className="text-lg text-neutral-500 dark:text-neutral-400">
/ 10
</span>
</div>
{/* --------------------- */}
<div className="listingSection__wrap ">{children}</div>
{/* --------------------- */}
<div className="flex justify-end space-x-5">
<ButtonSecondary href={backtHref}>Go back</ButtonSecondary>
<ButtonPrimary href={nextHref}>
{nextBtnText || "Continue"}
</ButtonPrimary>
</div>
</div>
</div>
);
};
export default CommonLayout;

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

@ -1,61 +1,71 @@
import React from "react";
"use client"
import React, { useState } from "react";
import { FC } from "react";
import ButtonPrimary from "@/shared/ButtonPrimary";
import ButtonSecondary from "@/shared/ButtonSecondary";
import { Route } from "@/routers/types";
import PageAddListing1 from "./PageAddListing1";
import PageAddListing10 from "./PageAddListing10";
import PageAddListing2 from "./PageAddListing2";
import PageAddListing3 from "./PageAddListing3";
import PageAddListing4 from "./PageAddListing4";
import PageAddListing5 from "./PageAddListing5";
import PageAddListing6 from "./PageAddListing6";
import PageAddListing7 from "./PageAddListing7";
import PageAddListing8 from "./PageAddListing8";
import PageAddListing9 from "./PageAddListing9";
const Page = ({
params,
searchParams,
}: {
params: { stepIndex: string };
searchParams?: { [key: string]: string | string[] | undefined };
}) => {
let ContentComponent = PageAddListing1;
switch (Number(params.stepIndex)) {
case 1:
ContentComponent = PageAddListing1;
break;
case 2:
ContentComponent = PageAddListing2;
break;
case 3:
ContentComponent = PageAddListing3;
break;
case 4:
ContentComponent = PageAddListing4;
break;
case 5:
ContentComponent = PageAddListing5;
break;
case 6:
ContentComponent = PageAddListing6;
break;
case 7:
ContentComponent = PageAddListing7;
break;
case 8:
ContentComponent = PageAddListing8;
break;
case 9:
ContentComponent = PageAddListing9;
break;
case 10:
ContentComponent = PageAddListing10;
break;
default:
ContentComponent = PageAddListing1;
break;
import { setHttpClientAndAgentOptions } from "next/dist/server/config";
export interface CommonLayoutProps {
children: React.ReactNode;
params: {
stepIndex: string;
};
}
const CommonLayout: FC<CommonLayoutProps> = ({ params }) => {
const [passengers , setPassengers] =useState([])
const [newPassenger , setNewPassenger] =useState({
fullName :"",
date : "" ,
number : "",
passport : "",
image : ""
})
const nextHandler =()=>{
setPassengers((prev) => [...prev, newPassenger]);
}
console.log(newPassenger);
console.log(passengers);
const index = Number(params.stepIndex) || 1;
const nextHref = (
index < 10 ? `/add-listing/${index + 1}` : `/add-listing/${1}`
) as Route;
const backtHref = (
index > 1 ? `/add-listing/${index - 1}` : `/add-listing/${1}`
) as Route;
const nextBtnText = index > 9 ? "Publish listing" : "Continue";
return (
<div
className={`nc-PageAddListing1 px-4 max-w-3xl mx-auto pb-24 pt-14 sm:py-24 lg:pb-32`}
>
<div className="space-y-11">
<div>
<span className="text-4xl font-semibold">{index}</span>{" "}
<span className="text-lg text-neutral-500 dark:text-neutral-400">
/ 10
</span>
</div>
{/* --------------------- */}
<div className="listingSection__wrap "><PageAddListing1 Passenger={newPassenger} setNewPassenger ={setNewPassenger} /></div>
return <ContentComponent />;
{/* --------------------- */}
<div className="flex justify-end space-x-5">
{index > 1 && <ButtonSecondary href={backtHref}>Go back</ButtonSecondary>}
<ButtonPrimary onClick={nextHandler} href={nextHref}>
{nextBtnText || "Continue"}
</ButtonPrimary>
</div>
</div>
</div>
);
};
export default Page;
export default CommonLayout;

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

@ -0,0 +1,227 @@
"use client";
import React, { useState } from "react";
import { FC } from "react";
import ButtonPrimary from "@/shared/ButtonPrimary";
import Input from "@/shared/Input";
import FormItem from "../add-listing/FormItem";
import getImageURL from "@/components/api/getImageURL";
import axiosInstance from "@/components/api/axios";
import { useRouter } from "next/navigation";
export interface CommonLayoutProps {
params: {
stepIndex: string;
};
}
const CommonLayout: FC<CommonLayoutProps> = () => {
const user = JSON.parse(localStorage.getItem("user"));
const router = useRouter();
const [passenger, setPassenger] = useState({
name: "",
passport: "",
number: "",
date: "",
image: "",
});
const [errors, setErrors] = useState({
name: "",
passport: "",
number: "",
date: "",
image: "",
});
const handleFileChange = async (e: React.ChangeEvent<HTMLInputElement>) => {
const file = e.target.files[0];
if (file) {
const image = await getImageURL(file);
setPassenger((prev) => ({ ...prev, image: image.url }));
setErrors((prev) => ({ ...prev, image: "" })); // Clear image error
}
};
const validateForm = () => {
let formIsValid = true;
let errors = {
name: "",
passport: "",
number: "",
date: "",
image: "",
};
if (!passenger.name) {
formIsValid = false;
errors.name = "Full Name is required.";
}
if (!passenger.passport) {
formIsValid = false;
errors.passport = "Passport Number is required.";
} else if (!/^\d+$/.test(passenger.passport)) {
formIsValid = false;
errors.passport = "Passport Number must be numeric.";
}
if (!passenger.date) {
formIsValid = false;
errors.date = "Date of Birth is required.";
}
if (!passenger.number) {
formIsValid = false;
errors.number = "Phone Number is required.";
} else if (!/^\d+$/.test(passenger.number)) {
formIsValid = false;
errors.number = "Phone Number must be numeric.";
}
if (!passenger.image) {
formIsValid = false;
errors.image = "Passport image is required.";
}
setErrors(errors);
return formIsValid;
};
const handleSavePassenger = async (passenger) => {
if (!validateForm()) return; // Validate before saving
try {
const response = await axiosInstance.post(
`/api/account/passengers/`,
{
fullname: passenger.name,
passport_number: passenger.passport,
birthdate: passenger.date,
phone_number: passenger.number,
passport_image: passenger.image,
},
{
headers: {
Authorization: `token ${user.token}`,
},
}
);
console.log(response.data);
router.push("/passengers-list");
} catch (error) {
console.error("Error saving passenger:", error);
}
};
return (
<div
className={`nc-PageAddListing1 px-4 max-w-3xl mx-auto pb-24 pt-14 sm:py-24 lg:pb-32`}
>
<div className="space-y-11">
{/* --------------------- */}
<form>
<div className="listingSection__wrap ">
<>
<h2 className="text-2xl font-semibold">Passengers Information</h2>
<div className="w-14 border-b border-neutral-200 dark:border-neutral-700"></div>
{/* FORM */}
<div className="space-y-8">
{/* ITEM */}
<FormItem label="Full Name" desc="">
<Input
required
value={passenger.name}
onChange={(e) => {
setPassenger((prev) => ({
...prev,
name: e.target.value,
}));
setErrors((prev) => ({ ...prev, name: "" })); // Clear error on input change
}}
placeholder="Full Name"
/>
{errors.name && <p className="text-red-500 text-xs">{errors.name}</p>}
</FormItem>
<FormItem label="Passport Number" desc="">
<Input
required
value={passenger.passport}
onChange={(e) => {
setPassenger((prev) => ({
...prev,
passport: e.target.value,
}));
setErrors((prev) => ({ ...prev, passport: "" })); // Clear error on input change
}}
type="number"
className="[appearance:textfield] [&::-webkit-outer-spin-button]:appearance-none [&::-webkit-inner-spin-button]:appearance-none"
placeholder="Passport Number"
/>
{errors.passport && <p className="text-red-500 text-xs">{errors.passport}</p>}
</FormItem>
<FormItem label="Date of Birth" desc="">
<Input
required
value={passenger.date}
onChange={(e) => {
setPassenger((prev) => ({
...prev,
date: e.target.value,
}));
setErrors((prev) => ({ ...prev, date: "" })); // Clear error on input change
}}
type="date"
placeholder="Date of Birth"
/>
{errors.date && <p className="text-red-500 text-xs">{errors.date}</p>}
</FormItem>
<FormItem label="Phone Number" desc="">
<Input
required
value={passenger.number}
onChange={(e) => {
setPassenger((prev) => ({
...prev,
number: e.target.value,
}));
setErrors((prev) => ({ ...prev, number: "" })); // Clear error on input change
}}
type="number"
className="[appearance:textfield] [&::-webkit-outer-spin-button]:appearance-none [&::-webkit-inner-spin-button]:appearance-none"
placeholder="Phone Number"
/>
{errors.number && <p className="text-red-500 text-xs">{errors.number}</p>}
</FormItem>
<FormItem label="Upload Passport Image Here" desc="">
<Input
required
onChange={handleFileChange}
type="file"
placeholder="Passport"
/>
{errors.image && <p className="text-red-500 text-xs">{errors.image}</p>}
</FormItem>
</div>
</>
</div>
{/* --------------------- */}
<div className="flex justify-end space-x-5">
<ButtonPrimary onClick={(e) => { e.preventDefault(); handleSavePassenger(passenger); }}>
Continue
</ButtonPrimary>
</div>
</form>
</div>
</div>
);
};
export default CommonLayout;

26
src/app/api/hello/auth/[...nextauth].ts

@ -1,26 +0,0 @@
import NextAuth from "next-auth";
import GithubProvider from "next-auth/providers/github";
import GoogleProvider from "next-auth/providers/google";
if (!process.env.GOOGLE_ID || !process.env.GOOGLE_SECRET || !process.env.GITHUB_ID || !process.env.GITHUB_SECRET) {
throw new Error("The environment variables GOOGLE_ID, GOOGLE_SECRET, GITHUB_ID, and GITHUB_SECRET must be set.");
}
export const authOptions = {
// Configure one or more authentication providers
providers: [
GithubProvider({
clientId: process.env.GITHUB_ID,
clientSecret: process.env.GITHUB_SECRET,
}),
GoogleProvider({
clientId: process.env.GOOGLE_ID,
clientSecret: process.env.GOOGLE_SECRET,
}),
// ...add more providers here
],
pages: {
signIn: "/login",
},
};
export default NextAuth(authOptions);

3
src/app/api/hello/route.ts

@ -1,3 +0,0 @@
export async function GET(request: Request) {
return new Response('Hello, Next.js!')
}

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

@ -0,0 +1,151 @@
"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 Input from "@/shared/Input";
import ButtonPrimary from "@/shared/ButtonPrimary";
import Image from "next/image";
import Link from "next/link";
import axiosInstance from "@/components/api/axios";
import { user } from "@/components/contexts/userContext";
import useFormValidation from "@/hooks/FormValidation";
import { useRouter } from "next/navigation";
export interface PageSignUpProps {}
const PageSignUp: FC<PageSignUpProps> = ({}) => {
const router = useRouter()
const { setForm , setMethod } = useContext(user);
const [name, setName] = useState('');
const [countryCode, setCountryCode] = useState('');
const [phoneNumber, setPhoneNumber] = useState('');
const [password, setPassword] = useState('');
const [confirmPassword, setConfirmPassword] = useState('');
const { errors, validateForm } = useFormValidation();
const countryCodeHandler = (e) => {
if (e.target.value.length <= 3) {
setCountryCode(e.target.value);
}
};
const submitHandler = async (
name,
countryCode,
phoneNumber,
password,
confirmPassword
) => {
const form = {
name,
countryCode,
phoneNumber,
password,
confirmPassword,
};
if (validateForm(form)) {
setForm(form);
console.log('Form is valid, submitting:', form);
await axiosInstance
.get(`/api/account/verfication/?range_phone=${countryCode}&phone_number=${phoneNumber}`)
.then((response) => {
setMethod(response.data.verification_method)
console.log(response.data.verification_method);
})
.catch((error) => {
console.error("Error fetching data:", error);
});
router.push("/signup/methodes")
} else {
console.log('Form has errors:', errors);
}
};
return (
<div className={`nc-PageSignUp`}>
<div className="container mb-24 lg:mb-32">
<h2 className="my-20 flex items-center text-3xl leading-[115%] md:text-5xl md:leading-[115%] font-semibold text-neutral-900 dark:text-neutral-100 justify-center">
Change Password
</h2>
<div className="max-w-md mx-auto space-y-6">
{/* FORM */}
<form className="grid grid-cols-1 gap-6" action="#" method="post">
<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>
<input
value={countryCode}
onChange={countryCodeHandler}
type="text"
placeholder="98"
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"
/>
<span className="px-2 text-neutral-500">|</span>
<input
value={phoneNumber}
onChange={(e) => setPhoneNumber(e.target.value)}
type="text"
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"
/>
</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 className="block">
<span className="flex justify-between items-center text-neutral-800 dark:text-neutral-200">
Password
</span>
<Input
value={password}
onChange={(e) => setPassword(e.target.value)}
type="text"
className="secure-input unselectable-input mt-1"
onCopy={(e)=>{
e.preventDefault()
}}
/>
{errors.password && <p className="text-xs text-red-600">{errors.password}</p>}
</label>
<label className="block">
<span className="flex justify-between items-center text-neutral-800 dark:text-neutral-200">
Confirm Password
</span>
<Input
value={confirmPassword}
onChange={(e) => setConfirmPassword(e.target.value)}
placeholder="Password"
type="text"
className="secure-input unselectable-input mt-1"
onCopy={(e)=>{
e.preventDefault()
}}
/>
{errors.confirmPassword && <p className="text-xs text-red-600">{errors.confirmPassword}</p>}
</label>
<ButtonPrimary
onClick={(e) => {
e.preventDefault();
submitHandler(name, countryCode, phoneNumber, password, confirmPassword);
}}
>
Continue
</ButtonPrimary>
</form>
</div>
</div>
</div>
);
};
export default PageSignUp;

5
src/app/globals.css

@ -1,3 +1,8 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
.secure-input {
font-family: 'PasswordDots';
font-size: 16px;
-webkit-text-security: disc;
}

11
src/app/layout.tsx

@ -1,12 +1,17 @@
"use client";
import { Poppins } from "next/font/google";
import SiteHeader from "./(client-components)/(Header)/SiteHeader";
import ClientCommons from "./ClientCommons";
import { SessionProvider } from "next-auth/react";
import { ContextProvider } from "@/components/contexts/tourDetails";
import "./globals.css";
import "@/fonts/line-awesome-1.3.0/css/line-awesome.css";
import "@/styles/index.scss";
import "rc-slider/assets/index.css";
import Footer from "@/components/Footer";
import FooterNav from "@/components/FooterNav";
import { UserProvider } from "@/components/contexts/userContext";
const poppins = Poppins({
subsets: ["latin"],
@ -24,11 +29,17 @@ export default function RootLayout({
return (
<html lang="en" className={poppins.className}>
<body className="bg-white text-base dark:bg-neutral-900 text-neutral-900 dark:text-neutral-200">
<SessionProvider>
<ContextProvider>
<UserProvider>
<ClientCommons />
<SiteHeader />
{children}
<FooterNav />
<Footer />
</UserProvider>
</ContextProvider>
</SessionProvider>
</body>
</html>
);

168
src/app/login/page.tsx

@ -1,93 +1,141 @@
import React, { FC } from "react";
import facebookSvg from "@/images/Facebook.svg";
import twitterSvg from "@/images/Twitter.svg";
"use client";
import React, { FC, useContext, useState } from "react";
import googleSvg from "@/images/Google.svg";
import Input from "@/shared/Input";
import ButtonPrimary from "@/shared/ButtonPrimary";
import Image from "next/image";
import Link from "next/link";
import axiosInstance from "@/components/api/axios";
import { useRouter } from "next/navigation";
import { user as UserContext } from "@/components/contexts/userContext";
export interface PageLoginProps {}
const loginSocials = [
{
name: "Continue with Facebook",
href: "#",
icon: facebookSvg,
},
{
name: "Continue with Twitter",
href: "#",
icon: twitterSvg,
},
{
name: "Continue with Google",
href: "#",
icon: googleSvg,
},
];
const PageLogin: FC<PageLoginProps> = ({}) => {
const router = useRouter();
let user = JSON.parse(localStorage.getItem("user"));
if (user) {
return router.replace("/")
}
const { setStatus } = useContext(UserContext);
const [phoneNumber, setPhoneNumber] = useState("");
const [password, setPassword] = useState("");
const [countryCode, setCountryCode] = useState();
const [error, setError] = useState();
const countryCodeHandler = (e) => {
if (e.target.value.length <= 3) {
setCountryCode(e.target.value);
}
};
const submitHandler = async (phoneNumber, password) => {
setError("");
await axiosInstance
.post(`/api/account/login/`, {
phone_number: phoneNumber,
password,
})
.then((response) => {
console.log(response);
if (response.status === 201) {
localStorage.setItem("user", JSON.stringify(response.data));
setStatus(true)
console.log(response.data);
} else {
setError("something went wrong");
}
})
.catch((error) => {
setError(error.message)
});
};
return (
<div className={`nc-PageLogin`}>
<div className="nc-PageLogin">
<div className="container mb-24 lg:mb-32">
<h2 className="my-20 flex items-center text-3xl leading-[115%] md:text-5xl md:leading-[115%] font-semibold text-neutral-900 dark:text-neutral-100 justify-center">
Login
</h2>
<div className="max-w-md mx-auto space-y-6">
<div className="grid gap-3">
{loginSocials.map((item, index) => (
<a
key={index}
href={item.href}
className="flex w-full rounded-lg bg-primary-50 dark:bg-neutral-800 px-4 py-3 transform transition-transform sm:px-6 hover:translate-y-[-2px]"
>
<Image
className="flex-shrink-0"
src={item.icon}
alt={item.name}
/>
<h3 className="flex-grow text-center text-sm font-medium text-neutral-700 dark:text-neutral-300 sm:text-sm">
{item.name}
</h3>
</a>
))}
</div>
{/* OR */}
<div className="relative text-center">
<span className="relative z-10 inline-block px-4 font-medium text-sm bg-white dark:text-neutral-400 dark:bg-neutral-900">
OR
</span>
<div className="absolute left-0 w-full top-1/2 transform -translate-y-1/2 border border-neutral-100 dark:border-neutral-800"></div>
</div>
{/* FORM */}
{/* Email/Password Form */}
<form className="grid grid-cols-1 gap-6" action="#" method="post">
<label className="block">
<span className="text-neutral-800 dark:text-neutral-200">
Email address
Phone Number
</span>
<Input
type="email"
placeholder="example@example.com"
className="mt-1"
<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">
{/* Plus Sign as Static Text */}
<span className="px-2 mr-[-15px] text-neutral-800 dark:text-neutral-200">
+
</span>
{/* Country Code Input */}
<input
value={countryCode}
onChange={(e) => {
countryCodeHandler(e);
}}
type="number"
placeholder="98"
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"
/>
{/* Divider Line */}
<span className="px-2 text-neutral-500">|</span>
{/* Phone Number Input */}
<input
value={phoneNumber}
onChange={(e) => {
setPhoneNumber(e.target.value);
}}
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"
/>
</div>
</label>
<label className="block">
<span className="flex justify-between items-center text-neutral-800 dark:text-neutral-200">
Password
<Link href="/login" className="text-sm underline font-medium">
<Link
href="/forgot-password"
className="text-sm underline font-medium"
>
Forgot password?
</Link>
</span>
<Input type="password" className="mt-1" />
<Input
type="password"
className="mt-1"
value={password}
onChange={(e) => {
setPassword(e.target.value);
}}
/>
</label>
<ButtonPrimary type="submit">Continue</ButtonPrimary>
{error && <p className="text-red-500 text-xs">{error}</p>}
<ButtonPrimary
onClick={(e) => {
e.preventDefault();
submitHandler(phoneNumber, password);
}}
>
Continue
</ButtonPrimary>
</form>
{/* ==== */}
{/* Sign Up Link */}
<span className="block text-center text-neutral-700 dark:text-neutral-300">
New user? {` `}
<Link href="/signup" className="font-semibold underline">
<Link href="/signup" className="text-bronze font-semibold">
Create an account
</Link>
</span>

55
src/app/page.tsx
File diff suppressed because it is too large
View File

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

@ -0,0 +1,150 @@
"use client";
import axiosInstance from "@/components/api/axios";
import { user } from "@/components/contexts/userContext";
import ButtonPrimary from "@/shared/ButtonPrimary";
import { useRouter } from "next/navigation";
import { useContext, useState } from "react";
import { FaWhatsapp } from "react-icons/fa";
import { MdOutlineTextsms } from "react-icons/md";
function SelectMethods() {
const router = useRouter();
let user = JSON.parse(localStorage.getItem("user"));
if (user) {
return router.replace("/");
}
const { method, form } = useContext(user);
const [selectedMethod, setSelectedMethod] = useState("");
const [error, setError] = useState("");
const [loading, setLoading] = useState("");
const enabled = {
watsapp: method.join().includes("watsapp"),
sms: method.join().includes("sms"),
};
const handleMethodChange = (e) => {
if (!enabled.watsapp && !enabled.sms) {
setSelectedMethod("");
} else if (!enabled.watsapp) {
setSelectedMethod("sms");
} else if (!enabled.sms) {
setSelectedMethod("whatsapp");
} else {
setSelectedMethod(e.target.value);
}
};
console.log(method);
const handleSubmit = async () => {
setLoading(true);
try {
const response = await axiosInstance.post(
`/api/account/register/`,
{
fullname: form.name,
phone_number: form.phoneNumber,
verification_method: selectedMethod,
range_phone: form.countryCode,
password: form.password,
password_confirmation: form.confirmPassword,
},
{
headers: {
Accept: "application/json",
},
}
);
console.log(response);
response.status === 202 &&
(console.log("sucsses"),
setLoading(false),
router.replace("signup/otp-code"));
} catch (error) {
setError(error);
console.log(error);
setLoading(false);
}
};
return (
<div className="w-[550px] container mb-24 lg:mb-32 p-4 space-y-4">
<h2 className="my-20 flex items-center text-3xl leading-[115%] md:text-5xl md:leading-[115%] font-semibold text-neutral-900 dark:text-neutral-100 justify-center">
Verification Method
</h2>
<form className="grid grid-cols-1 gap-6">
<div
className={`${
!enabled.watsapp ? "opacity-40" : ""
} h-15 flex items-center justify-between p-3 border rounded-xl shadow-sm bg-white dark:bg-neutral-800`}
>
<div className="flex items-center">
<FaWhatsapp className="text-xl mr-3" />
<label
htmlFor="whatsapp"
className={`${
!enabled.watsapp ? "cursor-not-allowed" : "cursor-pointer"
} text-neutral-800 dark:text-neutral-200 font-medium`}
>
Send via WhatsApp
</label>
</div>
<input
type="radio"
id="whatsapp"
name="method"
value="whatsapp"
checked={selectedMethod === "whatsapp"}
onChange={(e) => handleMethodChange(e)}
disabled={!enabled.watsapp}
className="cursor-pointer form-radio accent-black text-black focus:ring-primary-500 focus:ring-2"
/>
</div>
<div
className={`${
!enabled.sms ? "opacity-40" : ""
} h-15 flex items-center justify-between p-3 border rounded-xl shadow-sm bg-white dark:bg-neutral-800`}
>
<div className="flex items-center">
<MdOutlineTextsms className="text-xl mr-3" />
<label
htmlFor="sms"
className={`${
!enabled.sms ? "cursor-not-allowed" : "cursor-pointer"
} text-neutral-800 dark:text-neutral-200 font-medium`}
>
Send via SMS
</label>
</div>
<input
type="radio"
id="sms"
name="method"
value="sms"
checked={selectedMethod === "sms"}
onChange={(e) => handleMethodChange(e)}
disabled={!enabled.sms}
className="cursor-pointer form-radio accent-black text-black focus:ring-primary-500 focus:ring-2"
/>
</div>
{/* Continue Button */}
{error && <p className="text-xs text-red-500">{error.message}</p>}
<ButtonPrimary
loading={loading}
onClick={(e) => {
e.preventDefault();
handleSubmit();
}}
>
Continue
</ButtonPrimary>
</form>
</div>
);
}
export default SelectMethods;

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

@ -0,0 +1,132 @@
"use client";
import React, { FC, useContext, useState, useEffect, useRef } from "react";
import ButtonPrimary from "@/shared/ButtonPrimary";
import axiosInstance from "@/components/api/axios";
import { user as userContext } from "@/components/contexts/userContext";
import useFormValidation from "@/hooks/FormValidation";
import { useRouter } from "next/navigation";
export interface PageSignUpProps {}
const PageSignUp: FC<PageSignUpProps> = () => {
const router = useRouter();
const user = JSON.parse(localStorage.getItem("user"));
const { form, setMethod, setStatus } = useContext(userContext);
const [otp, setOtp] = useState(["", "", "", "", ""]);
const [time, setTime] = useState(30);
const [error, setError] = useState("");
const otpRefs = useRef<(HTMLInputElement | null)[]>([]);
useEffect(() => {
if (user) {
router.replace("/");
}
}, [user, router]);
const handleOtpChange = (value: string, index: number) => {
if (/^[0-9]?$/.test(value)) {
const newOtp = [...otp];
newOtp[index] = value;
setOtp(newOtp);
if (value && index < otpRefs.current.length - 1) {
otpRefs.current[index + 1]?.focus();
}
}
};
const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>, index: number) => {
if (e.key === "Backspace" && !otp[index] && index > 0) {
otpRefs.current[index - 1]?.focus();
}
};
useEffect(() => {
if (time > 0) {
const timer = setInterval(() => setTime((prevTime) => prevTime - 1), 1000);
return () => clearInterval(timer);
}
}, [time]);
const handleResend = () => {
if (time === 0) {
setTime(30);
// Add logic to resend OTP here if needed
}
};
const submitHandler = async () => {
setError("");
try {
const response = await axiosInstance.post("/api/account/verify/", {
method: "register",
phone_number: form.phoneNumber,
code: otp.join(""),
});
if (response.status === 201) {
localStorage.setItem("user", JSON.stringify(response.data));
setStatus(true);
router.replace("/");
} else {
setError("Something went wrong. Please try again.");
}
} catch (error) {
setError(error.response?.data?.message || "An error occurred.");
}
};
return (
<div className={`nc-PageSignUp`}>
<div className="container mb-24 lg:mb-32">
<h2 className="my-10 text-center text-2xl font-semibold text-neutral-900 dark:text-neutral-100">
Verification Code
</h2>
<p className="text-center text-sm text-neutral-500 mb-4">
Enter the 5-digit code that we sent to complete your account registration
</p>
<div className="max-w-sm mx-auto space-y-6">
<div className="flex justify-center space-x-2 mb-4">
{otp.map((value, index) => (
<input
key={index}
ref={(el) => (otpRefs.current[index] = el)}
type="text"
maxLength={1}
value={value}
onChange={(e) => handleOtpChange(e.target.value, index)}
onKeyDown={(e) => handleKeyDown(e, index)}
className="w-12 h-12 border rounded-lg text-center text-lg font-semibold border-neutral-200 dark:border-neutral-700 focus:outline-none focus:ring-2 focus:ring-primary-500"
/>
))}
</div>
<p className="text-center text-sm text-neutral-500 mb-4">
Haven't got the confirmation code yet?{" "}
<button
className={`text-primary-600 hover:underline ${time > 0 ? "cursor-not-allowed opacity-50" : "cursor-pointer"}`}
onClick={handleResend}
disabled={time > 0}
>
Resend
</button>
{time > 0 && <span className="text-xs text-neutral-400">({time} Seconds)</span>}
</p>
{error && <p className="text-red-500 text-xs">{error}</p>}
<ButtonPrimary
className="w-full h-12 text-white font-semibold rounded-full"
onClick={(e) => {
e.preventDefault();
submitHandler();
}}
>
Confirm
</ButtonPrimary>
</div>
</div>
</div>
);
};
export default PageSignUp;

185
src/app/signup/page.tsx

@ -1,33 +1,70 @@
import React, { FC } from "react";
import facebookSvg from "@/images/Facebook.svg";
import twitterSvg from "@/images/Twitter.svg";
import googleSvg from "@/images/Google.svg";
"use client";
import React, { FC, useContext, useState } from "react";
import Input from "@/shared/Input";
import ButtonPrimary from "@/shared/ButtonPrimary";
import Image from "next/image";
import Link from "next/link";
import axiosInstance from "@/components/api/axios";
import { user as UserContext } from "@/components/contexts/userContext";
import useFormValidation from "@/hooks/FormValidation";
import { useRouter } from "next/navigation";
export interface PageSignUpProps {}
const loginSocials = [
{
name: "Continue with Facebook",
href: "#",
icon: facebookSvg,
},
{
name: "Continue with Twitter",
href: "#",
icon: twitterSvg,
},
{
name: "Continue with Google",
href: "#",
icon: googleSvg,
},
];
const PageSignUp: FC<PageSignUpProps> = () => {
const router = useRouter();
const storedUser = JSON.parse(localStorage.getItem("user"));
if (storedUser) {
router.replace("/");
return null; // Avoid rendering the component if the user is already logged in
}
const { setForm, setMethod } = useContext(UserContext);
const [name, setName] = useState("");
const [countryCode, setCountryCode] = useState("");
const [phoneNumber, setPhoneNumber] = useState("");
const [password, setPassword] = useState("");
const [confirmPassword, setConfirmPassword] = useState("");
const [loading, setLoading] = useState(false);
const { errors, validateForm } = useFormValidation();
const countryCodeHandler = (e) => {
if (e.target.value.length <= 3) {
setCountryCode(e.target.value);
}
};
const submitHandler = async () => {
const form = {
name,
countryCode,
phoneNumber,
password,
confirmPassword,
};
if (validateForm(form)) {
setLoading(true);
setForm(form);
try {
const response = await axiosInstance.get(
`/api/account/verification/?range_phone=${countryCode}&phone_number=${phoneNumber}`
);
setMethod(response.data.verification_method);
router.push("/signup/methods");
} catch (error) {
console.error("Error fetching data:", error);
} finally {
setLoading(false);
}
} else {
console.log("Form has errors:", errors);
}
};
const PageSignUp: FC<PageSignUpProps> = ({}) => {
return (
<div className={`nc-PageSignUp`}>
<div className="container mb-24 lg:mb-32">
@ -35,56 +72,82 @@ const PageSignUp: FC<PageSignUpProps> = ({}) => {
Signup
</h2>
<div className="max-w-md mx-auto space-y-6">
<div className="grid gap-3">
{loginSocials.map((item, index) => (
<a
key={index}
href={item.href}
className="nc-will-change-transform flex w-full rounded-lg bg-primary-50 dark:bg-neutral-800 px-4 py-3 transform transition-transform sm:px-6 hover:translate-y-[-2px]"
>
<Image
className="flex-shrink-0"
src={item.icon}
alt={item.name}
<form className="grid grid-cols-1 gap-6" onSubmit={(e) => e.preventDefault()}>
<label className="block">
<span className="text-neutral-800 dark:text-neutral-200">Full Name</span>
<Input
value={name}
onChange={(e) => setName(e.target.value)}
type="text"
placeholder="Full Name"
className="mt-1"
/>
{errors.name && <p className="text-xs text-red-600">{errors.name}</p>}
</label>
<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>
<input
value={countryCode}
onChange={countryCodeHandler}
type="text"
placeholder="98"
maxLength={3}
className="w-[50px] p-2 mr-[-10px] text-center border-none outline-none bg-transparent text-neutral-800 dark:text-neutral-200"
/>
<span className="px-2 text-neutral-500">|</span>
<input
value={phoneNumber}
onChange={(e) => setPhoneNumber(e.target.value)}
type="text"
placeholder="26363687"
className="flex-1 p-2 border-none outline-none bg-transparent text-neutral-800 dark:text-neutral-200"
/>
<h3 className="flex-grow text-center text-sm font-medium text-neutral-700 dark:text-neutral-300 sm:text-sm">
{item.name}
</h3>
</a>
))}
</div>
{/* OR */}
<div className="relative text-center">
<span className="relative z-10 inline-block px-4 font-medium text-sm bg-white dark:text-neutral-400 dark:bg-neutral-900">
OR
</span>
<div className="absolute left-0 w-full top-1/2 transform -translate-y-1/2 border border-neutral-100 dark:border-neutral-800"></div>
</div>
{/* FORM */}
<form className="grid grid-cols-1 gap-6" action="#" method="post">
{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 className="block">
<span className="text-neutral-800 dark:text-neutral-200">
Email address
<span className="flex justify-between items-center text-neutral-800 dark:text-neutral-200">
Password
</span>
<Input
type="email"
placeholder="example@example.com"
className="mt-1"
value={password}
onChange={(e) => setPassword(e.target.value)}
type="password" // Changed to password type for security
className="secure-input mt-1"
onCopy={(e) => e.preventDefault()}
/>
{errors.password && <p className="text-xs text-red-600">{errors.password}</p>}
</label>
<label className="block">
<span className="flex justify-between items-center text-neutral-800 dark:text-neutral-200">
Password
Confirm Password
</span>
<Input type="password" className="mt-1" />
<Input
value={confirmPassword}
onChange={(e) => setConfirmPassword(e.target.value)}
placeholder="Password"
type="password" // Changed to password type for security
className="secure-input mt-1"
onCopy={(e) => e.preventDefault()}
/>
{errors.confirmPassword && (
<p className="text-xs text-red-600">{errors.confirmPassword}</p>
)}
</label>
<ButtonPrimary type="submit">Continue</ButtonPrimary>
<ButtonPrimary
loading={loading}
onClick={submitHandler}
disabled={loading}
>
{loading ? "Loading..." : "Continue"}
</ButtonPrimary>
</form>
{/* ==== */}
<span className="block text-center text-neutral-700 dark:text-neutral-300">
Already have an account? {` `}
<Link href="/login" className="font-semibold underline">
<span className="not-italic block text-center text-neutral-700 dark:text-neutral-300">
Already have an account?{" "}
<Link href="/login" className="text-primary-600 font-semibold underline">
Sign in
</Link>
</span>

94
src/app/tours/[slug]/GuestsInput.tsx

@ -0,0 +1,94 @@
"use client";
import React, { Fragment, FC, useState, useContext } from "react";
import { Popover, Transition } from "@headlessui/react";
import NcInputNumber from "@/components/NcInputNumber";
import { UserPlusIcon } from "@heroicons/react/24/outline";
import ClearDataButton from "@/app/(client-components)/(HeroSearchForm)/ClearDataButton";
import { GuestsObject } from "@/app/(client-components)/type";
import { Context } from "@/components/contexts/tourDetails";
export interface GuestsInputProps {
className?: string;
}
const GuestsInput: FC<GuestsInputProps> = ({ className = "flex-1" }) => {
const { setPassengers, passengers } = useContext(Context);
const [guestAdultsInputValue, setGuestAdultsInputValue] =
useState(passengers);
const handleChangeData = (value: number) => {
let newValue = {
guestAdults: guestAdultsInputValue,
};
setGuestAdultsInputValue(value);
newValue.guestAdults = value;
setPassengers(value);
};
const totalGuests = guestAdultsInputValue;
return (
<Popover className={`flex relative ${className}`}>
{({ open }) => (
<>
<div
className={`flex-1 flex items-center focus:outline-none rounded-b-3xl ${
open ? "shadow-lg" : ""
}`}
>
<Popover.Button
className={`relative z-10 flex-1 flex text-left items-center p-3 space-x-3 focus:outline-none`}
>
<div className="text-neutral-300 dark:text-neutral-400">
<UserPlusIcon className="w-5 h-5 lg:w-7 lg:h-7" />
</div>
<div className="flex-grow">
<span className="block xl:text-lg font-semibold">
{totalGuests || ""} Guests
</span>
<span className="block mt-1 text-sm text-neutral-400 leading-none font-light">
{totalGuests ? "Guests" : "Add guests"}
</span>
</div>
{!!totalGuests && open && (
<ClearDataButton
onClick={() => {
setGuestAdultsInputValue(0);
setPassengers(0);
}}
/>
)}
</Popover.Button>
</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 right-0 z-10 w-full sm:min-w-[340px] max-w-sm bg-white dark:bg-neutral-800 top-full mt-3 py-5 sm:py-6 px-4 sm:px-8 rounded-3xl shadow-xl ring-1 ring-black ring-opacity-5 ">
<NcInputNumber
className="w-full"
defaultValue={guestAdultsInputValue}
onChange={(value) => handleChangeData(value)}
max={10}
min={1}
label="Passsengers"
desc="Ages 13 or above"
/>
</Popover.Panel>
</Transition>
</>
)}
</Popover>
);
};
export default GuestsInput;

69
src/app/tours/[slug]/StayDatesRangeInput.tsx

@ -0,0 +1,69 @@
"use client";
import React, { Fragment, useState, FC } from "react";
import { Popover, Transition } from "@headlessui/react";
import { CalendarIcon } from "@heroicons/react/24/outline";
import DatePickerCustomHeaderTwoMonth from "@/components/DatePickerCustomHeaderTwoMonth";
import DatePickerCustomDay from "@/components/DatePickerCustomDay";
import DatePicker from "react-datepicker";
import ClearDataButton from "@/app/(client-components)/(HeroSearchForm)/ClearDataButton";
export interface StayDatesRangeInputProps {
className?: string;
}
const StayDatesRangeInput: FC<StayDatesRangeInputProps> = ({
className = "flex-1",
details
}) => {
const [startDate, setStartDate] = useState<Date | null>(
new Date("2023/02/06")
);
const [endDate, setEndDate] = useState<Date | null>(new Date("2023/02/23"));
//
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">
{details?.started_at.replaceAll("-", "/") || "Tour period"}
{details?.ended_at &&
" - " + details?.ended_at.replaceAll("-", "/")}
</span>
<span className="block mt-1 text-sm text-neutral-400 leading-none font-light">
{"Starts - End"}
</span>
</div>
</>
);
};
return (
<Popover className={`StayDatesRangeInput z-10 relative flex ${className}`}>
{({ open }) => (
<>
<div
className={`flex-1 flex relative p-3 items-center space-x-3 focus:outline-none `}
>
{renderInput()}
</div>
</>
)}
</Popover>
);
};
export default StayDatesRangeInput;

71
src/app/tours/[slug]/constant.ts

@ -0,0 +1,71 @@
import { ListingGalleryImage } from "@/components/listing-image-gallery/utils/types";
export const PHOTOS: string[] = [
"https://images.pexels.com/photos/6129967/pexels-photo-6129967.jpeg?auto=compress&cs=tinysrgb&dpr=3&h=750&w=1260",
"https://images.pexels.com/photos/7163619/pexels-photo-7163619.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=750&w=1260",
"https://images.pexels.com/photos/6527036/pexels-photo-6527036.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=750&w=1260",
"https://images.pexels.com/photos/6969831/pexels-photo-6969831.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=750&w=1260",
"https://images.pexels.com/photos/6438752/pexels-photo-6438752.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=750&w=1260",
"https://images.pexels.com/photos/1320686/pexels-photo-1320686.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=750&w=1260",
"https://images.pexels.com/photos/261394/pexels-photo-261394.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=750&w=1260",
"https://images.pexels.com/photos/2861361/pexels-photo-2861361.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=750&w=1260",
"https://images.pexels.com/photos/2677398/pexels-photo-2677398.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=750&w=1260",
"https://images.pexels.com/photos/1365425/pexels-photo-1365425.jpeg?auto=compress&cs=tinysrgb&w=1600",
"https://images.pexels.com/photos/914128/pexels-photo-914128.jpeg?auto=compress&cs=tinysrgb&w=1600",
"https://images.pexels.com/photos/840667/pexels-photo-840667.jpeg?auto=compress&cs=tinysrgb&w=1600",
"https://images.pexels.com/photos/732632/pexels-photo-732632.jpeg?auto=compress&cs=tinysrgb&w=1600",
"https://images.pexels.com/photos/450062/pexels-photo-450062.jpeg?auto=compress&cs=tinysrgb&w=1600",
"https://images.pexels.com/photos/917510/pexels-photo-917510.jpeg?auto=compress&cs=tinysrgb&w=1600",
"https://images.pexels.com/photos/1194233/pexels-photo-1194233.jpeg?auto=compress&cs=tinysrgb&w=1600",
"https://images.pexels.com/photos/236973/pexels-photo-236973.jpeg?auto=compress&cs=tinysrgb&w=1600",
"https://images.pexels.com/photos/1392099/pexels-photo-1392099.jpeg?auto=compress&cs=tinysrgb&w=1600",
"https://images.pexels.com/photos/547116/pexels-photo-547116.jpeg?auto=compress&cs=tinysrgb&w=1600",
"https://images.pexels.com/photos/1002272/pexels-photo-1002272.jpeg?auto=compress&cs=tinysrgb&w=1600",
"https://images.pexels.com/photos/917511/pexels-photo-917511.jpeg?auto=compress&cs=tinysrgb&w=1600",
"https://images.pexels.com/photos/771079/pexels-photo-771079.jpeg?auto=compress&cs=tinysrgb&w=1600",
"https://images.pexels.com/photos/13461077/pexels-photo-13461077.jpeg?auto=compress&cs=tinysrgb&w=1600&lazy=load",
"https://images.pexels.com/photos/9074921/pexels-photo-9074921.jpeg?auto=compress&cs=tinysrgb&w=1600&lazy=load",
"https://images.pexels.com/photos/9336042/pexels-photo-9336042.jpeg?auto=compress&cs=tinysrgb&w=1600&lazy=load",
"https://images.pexels.com/photos/5418318/pexels-photo-5418318.jpeg?auto=compress&cs=tinysrgb&w=1600&lazy=load",
"https://images.pexels.com/photos/4815278/pexels-photo-4815278.jpeg?auto=compress&cs=tinysrgb&w=1600&lazy=load",
"https://images.pexels.com/photos/1365425/pexels-photo-1365425.jpeg?auto=compress&cs=tinysrgb&w=1600",
];
export const Amenities_demos = [
{ name: "la-key", icon: "la-key" },
{ name: "la-luggage-cart", icon: "la-luggage-cart" },
{ name: "la-shower", icon: "la-shower" },
{ name: "la-smoking", icon: "la-smoking" },
{ name: "la-snowflake", icon: "la-snowflake" },
{ name: "la-spa", icon: "la-spa" },
{ name: "la-suitcase", icon: "la-suitcase" },
{ name: "la-suitcase-rolling", icon: "la-suitcase-rolling" },
{ name: "la-swimmer", icon: "la-swimmer" },
{ name: "la-swimming-pool", icon: "la-swimming-pool" },
{ name: "la-tv", icon: "la-tv" },
{ name: "la-umbrella-beach", icon: "la-umbrella-beach" },
{ name: "la-utensils", icon: "la-utensils" },
{ name: "la-wheelchair", icon: "la-wheelchair" },
{ name: "la-wifi", icon: "la-wifi" },
{ name: "la-baby-carriage", icon: "la-baby-carriage" },
{ name: "la-bath", icon: "la-bath" },
{ name: "la-bed", icon: "la-bed" },
{ name: "la-briefcase", icon: "la-briefcase" },
{ name: "la-car", icon: "la-car" },
{ name: "la-cocktail", icon: "la-cocktail" },
{ name: "la-coffee", icon: "la-coffee" },
{ name: "la-concierge-bell", icon: "la-concierge-bell" },
{ name: "la-dice", icon: "la-dice" },
{ name: "la-dumbbell", icon: "la-dumbbell" },
{ name: "la-hot-tub", icon: "la-hot-tub" },
{ name: "la-infinity", icon: "la-infinity" },
];
export const imageGallery: ListingGalleryImage[] = [...PHOTOS].map(
(item, index): ListingGalleryImage => {
return {
id: index,
url: item,
};
}
);

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

@ -0,0 +1,648 @@
"use client";
import React, { FC, Fragment, useContext, useEffect, useState } from "react";
import { Dialog, Transition } from "@headlessui/react";
import { ArrowRightIcon, Squares2X2Icon } from "@heroicons/react/24/outline";
import CommentListing from "@/components/CommentListing";
import FiveStartIconForRate from "@/components/FiveStartIconForRate";
import StartRating from "@/components/StartRating";
import Avatar from "@/shared/Avatar";
import Badge from "@/shared/Badge";
import ButtonCircle from "@/shared/ButtonCircle";
import ButtonPrimary from "@/shared/ButtonPrimary";
import ButtonSecondary from "@/shared/ButtonSecondary";
import ButtonClose from "@/shared/ButtonClose";
import Input from "@/shared/Input";
import LikeSaveBtns from "@/components/LikeSaveBtns";
import Image from "next/image";
import { useParams, usePathname, useRouter } from "next/navigation";
import { Amenities_demos, PHOTOS } from "./constant";
import StayDatesRangeInput from "./StayDatesRangeInput";
import GuestsInput from "./GuestsInput";
import { Route } from "next";
import { Context } from "@/components/contexts/tourDetails";
export interface ListingStayDetailPageProps {}
const ListingStayDetailPage: FC<ListingStayDetailPageProps> = ({}) => {
//
const { getTourData, details, passengers } = useContext(Context);
const r = /-?(\d+)$/;
const id: number = useParams().slug.match(r)[1];
useEffect(() => {
getTourData(id);
}, []);
console.log(details);
let [isOpenModalAmenities, setIsOpenModalAmenities] = useState(false);
const thisPathname = usePathname();
const router = useRouter();
function closeModalAmenities() {
setIsOpenModalAmenities(false);
}
function openModalAmenities() {
setIsOpenModalAmenities(true);
}
const handleOpenModalImageGallery = () => {
router.push(`${thisPathname}/?modal=PHOTO_TOUR_SCROLLABLE` as Route);
};
const renderSection1 = () => {
return (
<div className="listingSection__wrap !space-y-6">
{/* 1 */}
<div className="flex justify-between items-center">
<Badge name="Wooden house" />
<LikeSaveBtns />
</div>
{/* 2 */}
<h2 className="text-2xl sm:text-3xl lg:text-4xl font-semibold">
{details?.title}
</h2>
{/* 3 */}
<div className="flex items-center space-x-4">
<StartRating />
<span>·</span>
<span>
<i className="las la-map-marker-alt"></i>
<span className="ml-1"> Tokyo, Jappan</span>
</span>
</div>
{/* 4 */}
<div className="flex items-center">
<Avatar hasChecked sizeClass="h-10 w-10" radius="rounded-full" />
<span className="ml-2.5 text-neutral-500 dark:text-neutral-400">
Hosted by{" "}
<span className="text-neutral-900 dark:text-neutral-200 font-medium">
Kevin Francis
</span>
</span>
</div>
{/* 5 */}
<div className="w-full border-b border-neutral-100 dark:border-neutral-700" />
{/* 6 */}
<div className="flex items-center justify-between xl:justify-start space-x-8 xl:space-x-12 text-sm text-neutral-700 dark:text-neutral-300">
<div className="flex items-center space-x-3 ">
<i className=" las la-user text-2xl "></i>
<span className="">
6 <span className="hidden sm:inline-block">guests</span>
</span>
</div>
<div className="flex items-center space-x-3">
<i className=" las la-bed text-2xl"></i>
<span className=" ">
6 <span className="hidden sm:inline-block">beds</span>
</span>
</div>
<div className="flex items-center space-x-3">
<i className=" las la-bath text-2xl"></i>
<span className=" ">
3 <span className="hidden sm:inline-block">baths</span>
</span>
</div>
<div className="flex items-center space-x-3">
<i className=" las la-door-open text-2xl"></i>
<span className=" ">
2 <span className="hidden sm:inline-block">bedrooms</span>
</span>
</div>
</div>
</div>
);
};
const renderSection2 = () => {
return (
<div className="listingSection__wrap">
<h2 className="text-2xl font-semibold">Stay information</h2>
<div className="w-14 border-b border-neutral-200 dark:border-neutral-700"></div>
<div className="text-neutral-6000 dark:text-neutral-300">
<span>{details?.description}</span>
<br />
<br />
<span>
There is a private bathroom with bidet in all units, along with a
hairdryer and free toiletries.
</span>
<br /> <br />
<span>
The Symphony 9 Tam Coc offers a terrace. Both a bicycle rental
service and a car rental service are available at the accommodation,
while cycling can be enjoyed nearby.
</span>
</div>
</div>
);
};
const renderSection3 = () => {
return (
<div className="listingSection__wrap">
<div>
<h2 className="text-2xl font-semibold">Amenities </h2>
<span className="block mt-2 text-neutral-500 dark:text-neutral-400">
{` About the property's amenities and services`}
</span>
</div>
<div className="w-14 border-b border-neutral-200 dark:border-neutral-700"></div>
{/* 6 */}
<div className="grid grid-cols-1 xl:grid-cols-3 gap-6 text-sm text-neutral-700 dark:text-neutral-300 ">
{details && details.tour_features?.map((item) => (
<div key={item.id} className="flex items-center space-x-3">
<span className=" ">{item.title}</span>
</div>
))}
</div>
{console.log(details)}
<div className="w-14 border-b border-neutral-200"></div>
<div>
<ButtonSecondary onClick={openModalAmenities}>
View more 20 amenities
</ButtonSecondary>
</div>
{renderMotalAmenities()}
</div>
);
};
const renderMotalAmenities = () => {
return (
<Transition appear show={isOpenModalAmenities} as={Fragment}>
<Dialog
as="div"
className="fixed inset-0 z-50 overflow-y-auto"
onClose={closeModalAmenities}
>
<div className="min-h-screen px-4 text-center">
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="ease-in duration-200"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<Dialog.Overlay className="fixed inset-0 bg-black bg-opacity-40" />
</Transition.Child>
{/* This element is to trick the browser into centering the modal contents. */}
<span
className="inline-block h-screen align-middle"
aria-hidden="true"
>
&#8203;
</span>
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0 scale-95"
enterTo="opacity-100 scale-100"
leave="ease-in duration-200"
leaveFrom="opacity-100 scale-100"
leaveTo="opacity-0 scale-95"
>
<div className="inline-block py-8 h-screen w-full max-w-4xl">
<div className="inline-flex pb-2 flex-col w-full text-left align-middle transition-all transform overflow-hidden rounded-2xl bg-white dark:bg-neutral-900 dark:border dark:border-neutral-700 dark:text-neutral-100 shadow-xl h-full">
<div className="relative flex-shrink-0 px-6 py-4 border-b border-neutral-200 dark:border-neutral-800 text-center">
<h3
className="text-lg font-medium leading-6 text-gray-900"
id="headlessui-dialog-title-70"
>
Amenities
</h3>
<span className="absolute left-3 top-3">
<ButtonClose onClick={closeModalAmenities} />
</span>
</div>
<div className="px-8 overflow-auto text-neutral-700 dark:text-neutral-300 divide-y divide-neutral-200">
{Amenities_demos.filter((_, i) => i < 1212).map((item) => (
<div
key={item.name}
className="flex items-center py-2.5 sm:py-4 lg:py-5 space-x-5 lg:space-x-8"
>
<i
className={`text-4xl text-neutral-6000 las ${item.icon}`}
></i>
<span>{item.name}</span>
</div>
))}
</div>
</div>
</div>
</Transition.Child>
</div>
</Dialog>
</Transition>
);
};
const renderSection4 = () => {
return (
<div className="listingSection__wrap">
{/* HEADING */}
<div>
<h2 className="text-2xl font-semibold">Room Rates </h2>
<span className="block mt-2 text-neutral-500 dark:text-neutral-400">
Prices may increase on weekends or holidays
</span>
</div>
<div className="w-14 border-b border-neutral-200 dark:border-neutral-700"></div>
{/* CONTENT */}
<div className="flow-root">
<div className="text-sm sm:text-base text-neutral-6000 dark:text-neutral-300 -mb-4">
<div className="p-4 bg-neutral-100 dark:bg-neutral-800 flex justify-between items-center space-x-4 rounded-lg">
<span>Monday - Thursday</span>
<span>$199</span>
</div>
<div className="p-4 flex justify-between items-center space-x-4 rounded-lg">
<span>Monday - Thursday</span>
<span>$199</span>
</div>
<div className="p-4 bg-neutral-100 dark:bg-neutral-800 flex justify-between items-center space-x-4 rounded-lg">
<span>Friday - Sunday</span>
<span>$219</span>
</div>
<div className="p-4 flex justify-between items-center space-x-4 rounded-lg">
<span>Rent by month</span>
<span>-8.34 %</span>
</div>
<div className="p-4 bg-neutral-100 dark:bg-neutral-800 flex justify-between items-center space-x-4 rounded-lg">
<span>Minimum number of nights</span>
<span>1 night</span>
</div>
<div className="p-4 flex justify-between items-center space-x-4 rounded-lg">
<span>Max number of nights</span>
<span>90 nights</span>
</div>
</div>
</div>
</div>
);
};
const renderSection5 = () => {
return (
<div className="listingSection__wrap">
{/* HEADING */}
<h2 className="text-2xl font-semibold">Host Information</h2>
<div className="w-14 border-b border-neutral-200 dark:border-neutral-700"></div>
{/* host */}
<div className="flex items-center space-x-4">
<Avatar
hasChecked
hasCheckedClass="w-4 h-4 -top-0.5 right-0.5"
sizeClass="h-14 w-14"
radius="rounded-full"
/>
<div>
<a className="block text-xl font-medium" href="##">
Kevin Francis
</a>
<div className="mt-1.5 flex items-center text-sm text-neutral-500 dark:text-neutral-400">
<StartRating />
<span className="mx-2">·</span>
<span> 12 places</span>
</div>
</div>
</div>
{/* desc */}
<span className="block text-neutral-6000 dark:text-neutral-300">
{details?.description}
</span>
{/* info */}
<div className="block text-neutral-500 dark:text-neutral-400 space-y-2.5">
<div className="flex items-center space-x-3">
<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="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"
/>
</svg>
<span>Joined in March 2016</span>
</div>
<div className="flex items-center space-x-3">
<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="M7 8h10M7 12h4m1 8l-4-4H5a2 2 0 01-2-2V6a2 2 0 012-2h14a2 2 0 012 2v8a2 2 0 01-2 2h-3l-4 4z"
/>
</svg>
<span>Response rate - 100%</span>
</div>
<div className="flex items-center space-x-3">
<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="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"
/>
</svg>
<span>Fast response - within a few hours</span>
</div>
</div>
{/* == */}
<div className="w-14 border-b border-neutral-200 dark:border-neutral-700"></div>
<div>
<ButtonSecondary href="/author">See host profile</ButtonSecondary>
</div>
</div>
);
};
const renderSection6 = () => {
return (
<div className="listingSection__wrap">
{/* HEADING */}
<h2 className="text-2xl font-semibold">Reviews (23 reviews)</h2>
<div className="w-14 border-b border-neutral-200 dark:border-neutral-700"></div>
{/* Content */}
<div className="space-y-5">
<FiveStartIconForRate iconClass="w-6 h-6" className="space-x-0.5" />
<div className="relative">
<Input
fontClass=""
sizeClass="h-16 px-4 py-3"
rounded="rounded-3xl"
placeholder="Share your thoughts ..."
/>
<ButtonCircle
className="absolute right-2 top-1/2 transform -translate-y-1/2"
size=" w-12 h-12 "
>
<ArrowRightIcon className="w-5 h-5" />
</ButtonCircle>
</div>
</div>
{/* comment */}
<div className="divide-y divide-neutral-100 dark:divide-neutral-800">
<CommentListing className="py-8" />
<CommentListing className="py-8" />
<CommentListing className="py-8" />
<CommentListing className="py-8" />
<div className="pt-8">
<ButtonSecondary>View more 20 reviews</ButtonSecondary>
</div>
</div>
</div>
);
};
const renderSection7 = () => {
return (
<div className="listingSection__wrap">
{/* HEADING */}
<div>
<h2 className="text-2xl font-semibold">Location</h2>
<span className="block mt-2 text-neutral-500 dark:text-neutral-400">
San Diego, CA, United States of America (SAN-San Diego Intl.)
</span>
</div>
<div className="w-14 border-b border-neutral-200 dark:border-neutral-700" />
{/* MAP */}
<div className="aspect-w-5 aspect-h-5 sm:aspect-h-3 ring-1 ring-black/10 rounded-xl z-0">
<div className="rounded-xl overflow-hidden z-0">
<iframe
width="100%"
height="100%"
loading="lazy"
allowFullScreen
referrerPolicy="no-referrer-when-downgrade"
src="https://www.google.com/maps/embed/v1/place?key=AIzaSyAGVJfZMAKYfZ71nzL_v5i3LjTTWnCYwTY&q=Iran+Mashhad+Imam Reza Holy Shrine"
></iframe>
</div>
</div>
</div>
);
};
const renderSection8 = () => {
return (
<div className="listingSection__wrap">
{/* HEADING */}
<h2 className="text-2xl font-semibold">Things to know</h2>
<div className="w-14 border-b border-neutral-200 dark:border-neutral-700" />
{/* CONTENT */}
<div>
<h4 className="text-lg font-semibold">Cancellation policy</h4>
<span className="block mt-3 text-neutral-500 dark:text-neutral-400">
Refund 50% of the booking value when customers cancel the room
within 48 hours after successful booking and 14 days before the
check-in time. <br />
Then, cancel the room 14 days before the check-in time, get a 50%
refund of the total amount paid (minus the service fee).
</span>
</div>
<div className="w-14 border-b border-neutral-200 dark:border-neutral-700" />
{/* CONTENT */}
<div>
<h4 className="text-lg font-semibold">Check-in time</h4>
<div className="mt-3 text-neutral-500 dark:text-neutral-400 max-w-md text-sm sm:text-base">
<div className="flex space-x-10 justify-between p-3 bg-neutral-100 dark:bg-neutral-800 rounded-lg">
<span>Check-in</span>
<span>08:00 am - 12:00 am</span>
</div>
<div className="flex space-x-10 justify-between p-3">
<span>Check-out</span>
<span>02:00 pm - 04:00 pm</span>
</div>
</div>
</div>
<div className="w-14 border-b border-neutral-200 dark:border-neutral-700" />
{/* CONTENT */}
<div>
<h4 className="text-lg font-semibold">Special Note</h4>
<div className="prose sm:prose">
<ul className="mt-3 text-neutral-500 dark:text-neutral-400 space-y-2">
{details && details.travel_tips.map((item)=>(
<>
<h4>
{item.title}
</h4>
<p>
{item.description}
</p>
</>
))
}
</ul>
</div>
</div>
</div>
);
};
const renderSidebar = () => {
return (
<div className="listingSectionSidebar__wrap shadow-xl">
{/* PRICE */}
<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>
<StartRating />
</div>
{/* FORM */}
<form className="flex flex-col border border-neutral-200 dark:border-neutral-700 rounded-3xl ">
<StayDatesRangeInput details={details} className="flex-1 z-[11]" />
<div className="w-full border-b border-neutral-200 dark:border-neutral-700"></div>
<GuestsInput className="flex-1" />
</form>
{/* SUM */}
<div className="flex flex-col space-y-4">
<div className="flex justify-between text-neutral-6000 dark:text-neutral-300">
<span>
{details?.price} x {passengers} passengers
</span>
<span>{details?.price * passengers}</span>
</div>
<div className="flex justify-between text-neutral-6000 dark:text-neutral-300">
<span>Service charge</span>
<span>$0</span>
</div>
<div className="border-b border-neutral-200 dark:border-neutral-700"></div>
<div className="flex justify-between font-semibold">
<span>Total</span>
<span>{details?.price * passengers}</span>
</div>
</div>
{/* SUBMIT */}
<ButtonPrimary href={"/checkout"}>Reserve</ButtonPrimary>
</div>
);
};
return (
<div className="nc-ListingStayDetailPage">
{/* HEADER */}
<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="col-span-2 row-span-3 sm:row-span-2 relative rounded-md sm:rounded-xl overflow-hidden cursor-pointer"
onClick={handleOpenModalImageGallery}
>
<Image
fill
className="object-cover rounded-md sm:rounded-xl"
src={details?.images[0]?.image_url.lg}
alt=""
sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 50vw"
/>
<div className="absolute inset-0 bg-neutral-900 bg-opacity-20 opacity-0 hover:opacity-100 transition-opacity"></div>
</div>
{details?.images.filter((_, i) => i >= 1 && i < 5).map((item, index) => (
<div
key={index}
className={`relative rounded-md sm:rounded-xl overflow-hidden ${
index >= 3 ? "hidden sm:block" : ""
}`}
>
<div className="aspect-w-4 aspect-h-3 sm:aspect-w-6 sm:aspect-h-5">
<Image
fill
className="object-cover rounded-md sm:rounded-xl "
src={item.image_url.lg}
alt=""
sizes="400px"
/>
{console.log(details)}
</div>
{/* OVERLAY */}
<div
className="absolute inset-0 bg-neutral-900 bg-opacity-20 opacity-0 hover:opacity-100 transition-opacity cursor-pointer"
onClick={handleOpenModalImageGallery}
/>
</div>
))}
{/* <button
className="absolute hidden md:flex md:items-center md:justify-center left-3 bottom-3 px-4 py-2 rounded-xl bg-neutral-100 text-neutral-500 hover:bg-neutral-200 z-10"
onClick={handleOpenModalImageGallery}
>
<Squares2X2Icon className="w-5 h-5" />
<span className="ml-2 text-neutral-800 text-sm font-medium">
Show all photos
</span>
</button> */}
</div>
</header>
{/* MAIN */}
<main className=" relative z-10 mt-11 flex flex-col lg:flex-row ">
{/* CONTENT */}
<div className="w-full lg:w-3/5 xl:w-2/3 space-y-8 lg:space-y-10 lg:pr-10">
{renderSection1()}
{renderSection2()}
{renderSection3()}
{renderSection4()}
{renderSection5()}
{/* {renderSection6()} */}
{renderSection7()}
{renderSection8()}
</div>
{/* SIDEBAR */}
<div className="hidden lg:block flex-grow mt-14 lg:mt-0">
<div className="sticky top-28">{renderSidebar()}</div>
</div>
</main>
</div>
);
};
export default ListingStayDetailPage;

70
src/app/tours/layout.tsx

@ -0,0 +1,70 @@
"use client";
import BackgroundSection from "@/components/BackgroundSection";
import ListingImageGallery from "@/components/listing-image-gallery/ListingImageGallery";
import SectionSliderNewCategories from "@/components/SectionSliderNewCategories";
import SectionSubscribe2 from "@/components/SectionSubscribe2";
import { usePathname, useRouter, useSearchParams } from "next/navigation";
import React, { ReactNode } from "react";
import MobileFooterSticky from "../(listing-detail)/(components)/MobileFooterSticky";
import { imageGallery as listingStayImageGallery } from "./[slug]/constant";
import { Route } from "next";
const DetailtLayout = ({ children }: { children: ReactNode }) => {
const router = useRouter();
const thisPathname = usePathname();
const searchParams = useSearchParams();
const modal = searchParams?.get("modal");
const handleCloseModalImageGallery = () => {
let params = new URLSearchParams(document.location.search);
params.delete("modal");
router.push(`${thisPathname}/?${params.toString()}` as Route);
};
const getImageGalleryListing = () => {
if (thisPathname?.includes("/listing-stay-detail")) {
return listingStayImageGallery;
}
if (thisPathname?.includes("/listing-car-detail")) {
return listingCarImageGallery;
}
if (thisPathname?.includes("/listing-experiences-detail")) {
return listingExperienceImageGallery;
}
return [];
};
return (
<div className="ListingDetailPage">
<ListingImageGallery
isShowModal={modal === "PHOTO_TOUR_SCROLLABLE"}
onClose={handleCloseModalImageGallery}
images={getImageGalleryListing()}
/>
<div className="container ListingDetailPage__content">{children}</div>
{/* OTHER SECTION */}
<div className="container py-24 lg:py-32">
<div className="relative py-16">
<BackgroundSection />
<SectionSliderNewCategories
heading="Explore by types of stays"
subHeading="Explore houses based on 10 types of stays"
categoryCardType="card5"
itemPerRow={5}
sliderStyle="style2"
/>
</div>
{/* <SectionSubscribe2 className="pt-24 lg:pt-32" /> */}
</div>
{/* STICKY FOOTER MOBILE */}
<MobileFooterSticky />
</div>
);
};
export default DetailtLayout;

9
src/components/CardCategory3.tsx
File diff suppressed because it is too large
View File

27
src/components/HeaderFilter.tsx

@ -9,10 +9,10 @@ import { ArrowRightIcon } from "@heroicons/react/24/outline";
export interface HeaderFilterProps {
tabActive: string;
tabs: string[];
tabs: any[]; // Adjust based on your data type
heading: ReactNode;
subHeading?: ReactNode;
onClickTab?: (item: string) => void;
onClickTab: (item: string) => void;
}
const HeaderFilter: FC<HeaderFilterProps> = ({
@ -26,10 +26,11 @@ const HeaderFilter: FC<HeaderFilterProps> = ({
useEffect(() => {
setTabActiveState(tabActive);
onClickTab(tabActive);
}, [tabActive]);
const handleClickTab = (item: string) => {
onClickTab(item);
onClickTab(item); // Trigger the onClickTab function passed from parent
setTabActiveState(item);
};
@ -41,24 +42,30 @@ const HeaderFilter: FC<HeaderFilterProps> = ({
className="sm:space-x-2"
containerClassName="relative flex w-full overflow-x-auto text-sm md:text-base hiddenScrollbar"
>
{tabs.map((item, index) => (
<NavItem
key={index}
isActive={tabActiveState === item}
onClick={() => handleClickTab(item)}
isActive={tabActiveState === "All"}
onClick={() => handleClickTab("All")} // Pass the country name
>
{item}
{"All"}
</NavItem>
{tabs?.map((item) => (
<NavItem
key={item.id}
isActive={tabActiveState === item.name}
onClick={() => handleClickTab(item.name)} // Pass the country name
>
{item.name}
</NavItem>
))}
</Nav>
<span className="hidden sm:block flex-shrink-0">
{/* <span className="hidden sm:block flex-shrink-0">
<ButtonSecondary href="/listing-stay" className="!leading-none">
<div className="flex items-center justify-center">
<span>View all</span>
<ArrowRightIcon className="w-5 h-5 ml-3" />
</div>
</ButtonSecondary>
</span>
</span> */}
</div>
</div>
);

69
src/components/SectionCustomTour.tsx

@ -0,0 +1,69 @@
import React, { FC } from "react";
import rightImgPng from "@/images/our-features.png";
import Image, { StaticImageData } from "next/image";
import Badge from "@/shared/Badge";
import ButtonPrimary from "@/shared/ButtonPrimary";
export interface SectionCustomTourProps {
className?: string;
rightImg?: StaticImageData;
type?: "type1" | "type2";
}
console.log("fsafs");
const SectionCustomTour: FC<SectionCustomTourProps> = ({
className = "lg:py-14",
rightImg = rightImgPng,
type = "type1",
}) => {
return (
<div
className={`nc-SectionCustomTour relative flex flex-col items-center ${
type === "type1" ? "lg:flex-row" : "lg:flex-row-reverse"
} ${className}`}
data-nc-id="SectionCustomTour"
>
<div className="flex-grow">
<Image src={rightImg} alt="" />
</div>
<div
className={`max-w-2xl flex-shrink-0 mt-10 lg:mt-0 lg:w-2/5 ${
type === "type1" ? "lg:pl-16" : "lg:pr-16"
}`}
>
<h2 className="font-semibold text-4xl mt-5">Custoum Tour </h2>
<ul className="space-y-10 mt-16">
<li className="space-y-4">
<span className="block text-xl font-semibold">
Cost-effective advertising
</span>
<span className="block mt-5 text-neutral-500 dark:text-neutral-400">
With a free listing, you can advertise your rental with no upfront
costs
</span>
</li>
<li className="space-y-4">
<span className="block mt-5 text-neutral-500 dark:text-neutral-400">
Millions of people are searching for unique places to stay around
the world
</span>
</li>
<li className="space-y-4">
<span className="block mt-5 text-neutral-500 dark:text-neutral-400">
A Holiday Lettings listing gives you a secure and easy way to take
bookings and payments online
</span>
</li>
<li className="space-y-4">
<ButtonPrimary>Custoum Tour</ButtonPrimary>
</li>
</ul>
</div>
</div>
);
};
export default SectionCustomTour;

73
src/components/SectionGridFeaturePlaces.tsx

@ -1,10 +1,14 @@
import React, { FC, ReactNode } from "react";
"use client";
import React, { FC, ReactNode, useContext, useEffect, useState } from "react";
import { DEMO_STAY_LISTINGS } from "@/data/listings";
import { StayDataType } from "@/data/types";
import ButtonPrimary from "@/shared/ButtonPrimary";
import HeaderFilter from "./HeaderFilter";
import StayCard from "./StayCard";
import StayCard2 from "./StayCard2";
import axiosInstance from "./api/axios";
import { Context } from "./contexts/tourDetails";
// OTHER DEMO WILL PASS PROPS
const DEMO_DATA: StayDataType[] = DEMO_STAY_LISTINGS.filter((_, i) => i < 8);
@ -23,44 +27,73 @@ export interface SectionGridFeaturePlacesProps {
const SectionGridFeaturePlaces: FC<SectionGridFeaturePlacesProps> = ({
stayListings = DEMO_DATA,
gridClass = "",
heading = "Featured places to stay",
heading = "List of Tours",
subHeading = "Popular places to stay that Chisfis recommends for you",
headingIsCenter,
tabs = ["New York", "Tokyo", "Paris", "London"],
cardType = "card2",
}) => {
const renderCard = (stay: StayDataType) => {
let CardName = StayCard;
switch (cardType) {
case "card1":
CardName = StayCard;
break;
case "card2":
CardName = StayCard2;
break;
const { countries, tours } = useContext(Context);
const [countryTours, setCountryTours] = useState();
default:
CardName = StayCard;
}
console.log(tours);
useEffect(()=>{
handleChange("All")
} , [tours , countries])
const handleChange = async (item) => {
console.log(item , countries , tours);
if (item ==="All"){
setCountryTours(tours.results);
return <CardName key={stay.id} data={stay} />;
} else{
const selected = countries.find((country) => country.name === item);
const selectedTours = tours.results?.filter(
(tour) => tour.destination_country === selected?.code
);
setCountryTours(selectedTours);
console.log(selected);
console.log(selectedTours);}
};
console.log(countries);
// 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} />;
// };
return (
<div className="nc-SectionGridFeaturePlaces relative">
<HeaderFilter
tabActive={"New York"}
tabActive={"All"}
subHeading={subHeading}
tabs={tabs}
tabs={countries}
heading={heading}
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}`}
>
{stayListings.map((stay) => renderCard(stay))}
{countryTours?.length ? (
countryTours?.map((stay) => <StayCard2 key={stay.id} data={stay} />)
) : (
<h2>No tours Available</h2>
)}
</div>
<div className="flex mt-16 justify-center items-center">
<ButtonPrimary loading>Show me more</ButtonPrimary>
<ButtonPrimary>Show me more</ButtonPrimary>
</div>
</div>
);

55
src/components/SectionSliderNewCategories.tsx

@ -1,6 +1,6 @@
"use client";
import React, { FC, useEffect, useState } from "react";
import React, { FC, useContext, useEffect, useState } from "react";
import { TaxonomyType } from "@/data/types";
import CardCategory3 from "@/components/CardCategory3";
import CardCategory4 from "@/components/CardCategory4";
@ -12,6 +12,8 @@ import PrevBtn from "./PrevBtn";
import NextBtn from "./NextBtn";
import { variants } from "@/utils/animationVariants";
import { useWindowSize } from "react-use";
import axiosInstance from "./api/axios";
import { Context } from "./contexts/tourDetails";
export interface SectionSliderNewCategoriesProps {
className?: string;
@ -100,7 +102,7 @@ const DEMO_CATS: TaxonomyType[] = [
];
const SectionSliderNewCategories: FC<SectionSliderNewCategoriesProps> = ({
heading = "Suggestions for discovery",
heading = "Countries ",
subHeading = "Popular places to recommends for you",
className = "",
itemClassName = "",
@ -112,6 +114,9 @@ const SectionSliderNewCategories: FC<SectionSliderNewCategoriesProps> = ({
const [currentIndex, setCurrentIndex] = useState(0);
const [direction, setDirection] = useState(0);
const [numberOfItems, setNumberOfitem] = useState(0);
const { countries} = useContext(Context);
console.log(countries)
const windowWidth = useWindowSize().width;
useEffect(() => {
@ -131,6 +136,20 @@ const SectionSliderNewCategories: FC<SectionSliderNewCategoriesProps> = ({
setNumberOfitem(itemPerRow);
}, [itemPerRow, windowWidth]);
// useEffect(() => {
// axiosInstance
// .get("/api/tours/")
// .then((response) => {
// setTours(response.data);
// })
// .catch((error) => {
// console.error("Error fetching data:", error);
// });
// }, []);
// console.log(tours);
function changeItemId(newVal: number) {
if (newVal > currentIndex) {
setDirection(1);
@ -142,7 +161,7 @@ const SectionSliderNewCategories: FC<SectionSliderNewCategoriesProps> = ({
const handlers = useSwipeable({
onSwipedLeft: () => {
if (currentIndex < categories?.length - 1) {
if (currentIndex < countries?.length - 1) {
changeItemId(currentIndex + 1);
}
},
@ -154,18 +173,18 @@ const SectionSliderNewCategories: FC<SectionSliderNewCategoriesProps> = ({
trackMouse: true,
});
const renderCard = (item: TaxonomyType) => {
switch (categoryCardType) {
case "card3":
return <CardCategory3 taxonomy={item} />;
case "card4":
return <CardCategory4 taxonomy={item} />;
case "card5":
return <CardCategory5 taxonomy={item} />;
default:
return <CardCategory3 taxonomy={item} />;
}
};
// const renderCard = (item: TaxonomyType) => {
// switch (categoryCardType) {
// case "card3":
// return <CardCategory3 taxonomy={item} />;
// case "card4":
// return <CardCategory4 taxonomy={item} />;
// case "card5":
// return <CardCategory5 taxonomy={item} />;
// default:
// return <CardCategory3 taxonomy={item} />;
// }
// };
if (!numberOfItems) return null;
@ -187,7 +206,7 @@ const SectionSliderNewCategories: FC<SectionSliderNewCategoriesProps> = ({
className="relative whitespace-nowrap -mx-2 xl:-mx-4"
>
<AnimatePresence initial={false} custom={direction}>
{categories.map((item, indx) => (
{countries?.map((item, indx) => (
<motion.li
className={`relative inline-block px-2 xl:px-4 ${itemClassName}`}
custom={direction}
@ -203,7 +222,7 @@ const SectionSliderNewCategories: FC<SectionSliderNewCategoriesProps> = ({
width: `calc(1/${numberOfItems} * 100%)`,
}}
>
{renderCard(item)}
<CardCategory3 taxonomy={item} />
</motion.li>
))}
</AnimatePresence>
@ -218,7 +237,7 @@ const SectionSliderNewCategories: FC<SectionSliderNewCategoriesProps> = ({
/>
) : null}
{categories.length > currentIndex + numberOfItems ? (
{countries?.length > currentIndex + numberOfItems ? (
<NextBtn
style={{ transform: "translate3d(0, 0, 0)" }}
onClick={() => changeItemId(currentIndex + 1)}

52
src/components/StayCard2.tsx

@ -1,5 +1,6 @@
"use client";
import React, { FC } from "react";
import GallerySlider from "@/components/GallerySlider";
import { DEMO_STAY_LISTINGS } from "@/data/listings";
import { StayDataType } from "@/data/types";
import StartRating from "@/components/StartRating";
@ -7,6 +8,7 @@ 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";
export interface StayCard2Props {
className?: string;
@ -38,30 +40,39 @@ const StayCard2: FC<StayCard2Props> = ({
} = data;
const renderSliderGallery = () => {
// console.log(data);
return (
<div className="relative w-full">
<GallerySlider
uniqueID={`StayCard2_${id}`}
ratioClass="aspect-w-12 aspect-h-11"
galleryImgs={galleryImgs}
imageClass="rounded-lg"
href={href}
<>
<Link
href={`/tours/${data?.slug}-${data?.id}`}
className="relative w-full"
>
<div className="h-[220px] 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}
/>
<BtnLikeIcon isLiked={like} className="absolute right-3 top-3 z-[1]" />
{saleOff && <SaleOffBadge className="absolute left-3 top-3" />}
</div>
</Link>
<BtnLikeIcon isLiked={like} className="absolute right-3 top-3 z-[1]" />
{<Badge className=" opacity-70 absolute left-3 top-3" name={data.status} color={data.status} />}
</>
);
};
console.log(data);
const renderContent = () => {
return (
<div className={size === "default" ? "mt-3 space-y-3" : "mt-2 space-y-2"}>
<div className="space-y-2">
<span className="text-sm text-neutral-500 dark:text-neutral-400">
{listingCategory.name} · {bedrooms} beds
</span>
<div className="flex items-center space-x-2">
{isAds && <Badge name="ADS" color="green" />}
{/* {<Badge name="ADS" color="green" />} */}
<h2
className={`font-semibold capitalize text-neutral-900 dark:text-white ${
size === "default" ? "text-base" : "text-base"
@ -71,7 +82,7 @@ const StayCard2: FC<StayCard2Props> = ({
</h2>
</div>
<div className="flex items-center text-neutral-500 dark:text-neutral-400 text-sm space-x-1.5">
{size === "default" && (
{/* {size === "default" && (
<svg
className="h-4 w-4"
fill="none"
@ -91,8 +102,8 @@ const StayCard2: FC<StayCard2Props> = ({
d="M15 11a3 3 0 11-6 0 3 3 0 016 0z"
/>
</svg>
)}
<span className="">{address}</span>
)} */}
{/* <span className="">{address}</span> */}
</div>
</div>
<div className="w-14 border-b border-neutral-100 dark:border-neutral-800"></div>
@ -100,11 +111,6 @@ const StayCard2: FC<StayCard2Props> = ({
<span className="text-base font-semibold">
{price}
{` `}
{size === "default" && (
<span className="text-sm text-neutral-500 dark:text-neutral-400 font-normal">
/night
</span>
)}
</span>
{!!reviewStart && (
<StartRating reviewCount={reviewCount} point={reviewStart} />
@ -117,7 +123,7 @@ const StayCard2: FC<StayCard2Props> = ({
return (
<div className={`nc-StayCard2 group relative ${className}`}>
{renderSliderGallery()}
<Link href={href}>{renderContent()}</Link>
<Link href={`/tours/${data?.slug}-${data?.id}`}>{renderContent()}</Link>
</div>
);
};

31
src/components/api/axios.tsx

@ -0,0 +1,31 @@
import axios from "axios";
const axiosInstance= axios.create({
baseURL : "https://aqila.nwhco.ir/"
})
axiosInstance.interceptors.request.use(
(config) => {
// Log the request headers
console.log('Request Headers:', config);
return config;
},
(error) => {
// Handle the request error
console.error('Request Error:', error);
return Promise.reject(error);
}
);
// You can also add a response interceptor if needed
axiosInstance.interceptors.response.use(
(response) => response,
(error) => {
// Handle response errors
console.error('Response Error:', error);
return Promise.reject(error);
}
);
export default axiosInstance;

36
src/components/api/getImageURL.tsx

@ -0,0 +1,36 @@
import axiosInstance from "./axios";
const getImageURL = async (file) => {
let image ;
if (file) {
const modifiedFileName = file.name.replaceAll(" ", "");
const modifiedFile = new File([file], modifiedFileName, {
type: file.type,
lastModified: file.lastModified,
});
const formData = new FormData();
formData.append("file", modifiedFile);
try {
const response = await axiosInstance.post(
`/api/upload-tmp-media/`,
formData,
{
headers: {
"Content-Type": "multipart/form-data",
},
}
);
if (response.status === 200) {
image = response.data;
} else {
console.log("Something went wrong during file upload");
}
} catch (error) {
console.log(error.message);
}
}
return image
};
export default getImageURL;

73
src/components/contexts/tourDetails.tsx

@ -0,0 +1,73 @@
"use client";
import axiosInstance from "../api/axios";
import React, { createContext, useEffect, useState } from "react";
export const Context = createContext();
export const ContextProvider = ({ children }) => {
const [details, setDetails] = useState();
const [passengers, setPassengers] = useState(0);
const [tours, setTours] = useState([]);
const [countries, setCountries] = useState([]);
useEffect(() => {
axiosInstance
.get("/api/cityguide/countries/")
.then((response) => {
setCountries(response.data.results);
})
.catch((error) => {
console.error("Error fetching data:", error);
});
}, []);
useEffect(() => {
axiosInstance
.get("/api/tours/")
.then((response) => {
setTours(response.data);
})
.catch((error) => {
console.error("Error fetching data:", error);
});
}, []);
console.log(tours);
const getTourData = async (item) => {
await axiosInstance
.get(`/api/tours/${item}/`)
.then((response) => {
setDetails(response.data);
})
.catch((error) => {
console.error("Error fetching data:", error);
});
// console.log(details);
// await axiosInstance
// .get(`/api/tours/${item}/passengers/`)
// .then((response) => {
// setPassengers(response.data);
// })
// .catch((error) => {
// console.error("Error fetching data:", error);
// });
// console.log(passengers);
};
return (
<Context.Provider
value={{
details,
passengers,
getTourData,
setPassengers,
setDetails,
tours,
countries,
}}
>
{children}
</Context.Provider>
);
};

26
src/components/contexts/userContext.tsx

@ -0,0 +1,26 @@
"use client";
import axiosInstance from "../api/axios";
import React, { createContext, useEffect, useState } from "react";
export const user = createContext();
export const UserProvider = ({ children }) => {
const [status, setStatus] = useState(false);
const [form, setForm] = useState({});
const [method, setMethod] = useState([]);
return (
<user.Provider
value={{
status,
setStatus,
form,
setForm,
method,
setMethod,
}}
>
{children}
</user.Provider>
);
};

21
src/data/jsons/__stayListing.json
File diff suppressed because it is too large
View File

368
src/data/navigation.ts

@ -167,7 +167,7 @@ export const NAVIGATION_DEMO: NavItemType[] = [
{
id: ncNanoId(),
href: "/",
name: "Home",
name: "All Tours",
type: "dropdown",
children: demoChildMenus,
isNew: true,
@ -175,200 +175,200 @@ export const NAVIGATION_DEMO: NavItemType[] = [
{
id: ncNanoId(),
href: "/",
name: "Five columns",
name: "Blogs",
type: "megaMenu",
megaMenu: megaMenuDemo,
},
{
id: ncNanoId(),
href: "/listing-stay",
name: "Listing Page",
type: "dropdown",
children: [
{
id: ncNanoId(),
href: "/listing-stay",
name: "Stay listings",
type: "dropdown",
children: [
{ id: ncNanoId(), href: "/listing-stay", name: "Stay page" },
{
id: ncNanoId(),
href: "/listing-stay-map",
name: "Stay page (map)",
},
{ id: ncNanoId(), href: "/listing-stay-detail", name: "Stay Detail" },
],
},
// {
// id: ncNanoId(),
// href: "/listing-stay",
// name: "Listing Page",
// type: "dropdown",
// children: [
// {
// id: ncNanoId(),
// href: "/listing-stay",
// name: "Stay listings",
// type: "dropdown",
// children: [
// { id: ncNanoId(), href: "/listing-stay", name: "Stay page" },
// {
// id: ncNanoId(),
// href: "/listing-stay-map",
// name: "Stay page (map)",
// },
// { id: ncNanoId(), href: "/listing-stay-detail", name: "Stay Detail" },
// ],
// },
//
{
id: ncNanoId(),
href: "/listing-experiences",
name: "Experiences listings",
type: "dropdown",
children: [
{
id: ncNanoId(),
href: "/listing-experiences",
name: "Experiences page",
},
{
id: ncNanoId(),
href: "/listing-experiences-map",
name: "Experiences page (map)",
},
{
id: ncNanoId(),
href: "/listing-experiences-detail",
name: "Experiences Detail",
},
],
},
// //
// {
// id: ncNanoId(),
// href: "/listing-experiences",
// name: "Experiences listings",
// type: "dropdown",
// children: [
// {
// id: ncNanoId(),
// href: "/listing-experiences",
// name: "Experiences page",
// },
// {
// id: ncNanoId(),
// href: "/listing-experiences-map",
// name: "Experiences page (map)",
// },
// {
// id: ncNanoId(),
// href: "/listing-experiences-detail",
// name: "Experiences Detail",
// },
// ],
// },
//
{
id: ncNanoId(),
href: "/listing-car",
name: "Cars listings",
type: "dropdown",
children: [
{ id: ncNanoId(), href: "/listing-car", name: "Cars page" },
{ id: ncNanoId(), href: "/listing-car-map", name: "Cars page (map)" },
{ id: ncNanoId(), href: "/listing-car-detail", name: "Car Detail" },
],
},
// //
// {
// id: ncNanoId(),
// href: "/listing-car",
// name: "Cars listings",
// type: "dropdown",
// children: [
// { id: ncNanoId(), href: "/listing-car", name: "Cars page" },
// { id: ncNanoId(), href: "/listing-car-map", name: "Cars page (map)" },
// { id: ncNanoId(), href: "/listing-car-detail", name: "Car Detail" },
// ],
// },
//
{
id: ncNanoId(),
href: "/listing-real-estate",
name: "Real Estate Listings",
type: "dropdown",
children: [
{
id: ncNanoId(),
href: "/listing-real-estate",
name: "Real Estate Listings",
},
{
id: ncNanoId(),
href: "/listing-real-estate-map",
name: "Real Estate Maps",
},
],
},
//
{
id: ncNanoId(),
href: "/listing-flights",
name: "Flights listings",
},
],
},
{
id: ncNanoId(),
href: "/author",
name: "Templates",
type: "dropdown",
children: templatesChildrenMenus,
},
// //
// {
// id: ncNanoId(),
// href: "/listing-real-estate",
// name: "Real Estate Listings",
// type: "dropdown",
// children: [
// {
// id: ncNanoId(),
// href: "/listing-real-estate",
// name: "Real Estate Listings",
// },
// {
// id: ncNanoId(),
// href: "/listing-real-estate-map",
// name: "Real Estate Maps",
// },
// ],
// },
// //
// {
// id: ncNanoId(),
// href: "/listing-flights",
// name: "Flights listings",
// },
// ],
// },
// {
// id: ncNanoId(),
// href: "/author",
// name: "Templates",
// type: "dropdown",
// children: templatesChildrenMenus,
// },
{
id: ncNanoId(),
href: "/blog",
name: "Other pages",
type: "dropdown",
children: otherPageChildMenus,
},
];
// {
// id: ncNanoId(),
// href: "/blog",
// name: "Other pages",
// type: "dropdown",
// children: otherPageChildMenus,
// },
// ];
export const NAVIGATION_DEMO_2: NavItemType[] = [
{
id: ncNanoId(),
href: "/",
name: "Home",
type: "dropdown",
children: demoChildMenus,
isNew: true,
},
// export const NAVIGATION_DEMO_2: NavItemType[] = [
// {
// id: ncNanoId(),
// href: "/",
// name: "Home",
// type: "dropdown",
// children: demoChildMenus,
// isNew: true,
// },
//
{
id: ncNanoId(),
href: "/listing-stay",
name: "Listing pages",
children: [
{ id: ncNanoId(), href: "/listing-stay", name: "Stay listings" },
{
id: ncNanoId(),
href: "/listing-stay-map",
name: "Stay listings (map)",
},
{ id: ncNanoId(), href: "/listing-stay-detail", name: "Stay detail" },
// //
// {
// id: ncNanoId(),
// href: "/listing-stay",
// name: "Listing pages",
// children: [
// { id: ncNanoId(), href: "/listing-stay", name: "Stay listings" },
// {
// id: ncNanoId(),
// href: "/listing-stay-map",
// name: "Stay listings (map)",
// },
// { id: ncNanoId(), href: "/listing-stay-detail", name: "Stay detail" },
//
{
id: ncNanoId(),
href: "/listing-experiences",
name: "Experiences listings",
},
{
id: ncNanoId(),
href: "/listing-experiences-map",
name: "Experiences (map)",
},
{
id: ncNanoId(),
href: "/listing-experiences-detail",
name: "Experiences detail",
},
],
},
{
id: ncNanoId(),
href: "/listing-car",
name: "Listing pages",
children: [
{ id: ncNanoId(), href: "/listing-car", name: "Cars listings" },
{ id: ncNanoId(), href: "/listing-car-map", name: "Cars listings (map)" },
{ id: ncNanoId(), href: "/listing-car-detail", name: "Car detail" },
// //
// {
// id: ncNanoId(),
// href: "/listing-experiences",
// name: "Experiences listings",
// },
// {
// id: ncNanoId(),
// href: "/listing-experiences-map",
// name: "Experiences (map)",
// },
// {
// id: ncNanoId(),
// href: "/listing-experiences-detail",
// name: "Experiences detail",
// },
// ],
// },
// {
// id: ncNanoId(),
// href: "/listing-car",
// name: "Listing pages",
// children: [
// { id: ncNanoId(), href: "/listing-car", name: "Cars listings" },
// { id: ncNanoId(), href: "/listing-car-map", name: "Cars listings (map)" },
// { id: ncNanoId(), href: "/listing-car-detail", name: "Car detail" },
//
{
id: ncNanoId(),
href: "/listing-real-estate",
name: "Real estate listings",
},
{
id: ncNanoId(),
href: "/listing-real-estate-map",
name: "Real estate (map)",
},
//
{
id: ncNanoId(),
href: "/listing-flights",
name: "Flights listings",
},
],
},
// //
// {
// id: ncNanoId(),
// href: "/listing-real-estate",
// name: "Real estate listings",
// },
// {
// id: ncNanoId(),
// href: "/listing-real-estate-map",
// name: "Real estate (map)",
// },
// //
// {
// id: ncNanoId(),
// href: "/listing-flights",
// name: "Flights listings",
// },
// ],
// },
//
{
id: ncNanoId(),
href: "/author",
name: "Templates",
type: "dropdown",
children: templatesChildrenMenus,
},
// //
// {
// id: ncNanoId(),
// href: "/author",
// name: "Templates",
// type: "dropdown",
// children: templatesChildrenMenus,
// },
//
{
id: ncNanoId(),
href: "/blog",
name: "Other pages",
type: "dropdown",
children: otherPageChildMenus,
},
// //
// {
// id: ncNanoId(),
// href: "/blog",
// name: "Other pages",
// type: "dropdown",
// children: otherPageChildMenus,
// },
];

49
src/hooks/FormValidation.ts

@ -0,0 +1,49 @@
// hooks/FormValidation.js
import { useState } from 'react';
// Validation function for the signup form
const useFormValidation = () => {
const [errors, setErrors] = useState({});
// Validate form fields
const validateForm = (form) => {
let newErrors = {};
// Full Name validation
if (!form.name) {
newErrors.name = 'Full Name is required';
}
// Country Code validation: must be a number and up to 3 digits
if (!form.countryCode || !/^\d{1,3}$/.test(form.countryCode)) {
newErrors.countryCode = 'Country Code must be a number with up to 3 digits';
}
// Phone Number validation: must be a number and not empty
if (!form.phoneNumber || !/^\d+$/.test(form.phoneNumber)) {
newErrors.phoneNumber = 'Phone Number is required and must be a number';
}
// Password validation
if (!form.password) {
newErrors.password = 'Password is required';
} else if (form.password.length < 6) {
newErrors.password = 'Password must be at least 6 characters';
}
// Confirm Password validation
if (!form.confirmPassword) {
newErrors.confirmPassword = 'Confirm Password is required';
} else if (form.password !== form.confirmPassword) {
newErrors.confirmPassword = 'Passwords do not match';
}
setErrors(newErrors);
// Return true if no errors
return Object.keys(newErrors).length === 0;
};
return { errors, validateForm };
};
export default useFormValidation;

BIN
src/images/hero-right-orginal.png

After

Width: 1001  |  Height: 1031  |  Size: 1.3 MiB

BIN
src/images/hero-right.png

Before

Width: 1001  |  Height: 1031  |  Size: 1.3 MiB

After

Width: 1001  |  Height: 1029  |  Size: 1.9 MiB

BIN
src/images/logos/لوگو3 1.png

After

Width: 113  |  Height: 151  |  Size: 15 KiB

11
src/images/logos/لوگو3 1.svg
File diff suppressed because it is too large
View File

36
src/shared/Avatar.tsx

@ -1,5 +1,6 @@
"use client"
import { avatarColors } from "@/contains/contants";
import React, { FC } from "react";
import React, { FC, useEffect, useState } from "react";
import avatar1 from "@/images/avatars/Image-1.png";
import Image, { StaticImageData } from "next/image";
@ -7,7 +8,7 @@ export interface AvatarProps {
containerClassName?: string;
sizeClass?: string;
radius?: string;
imgUrl?: string | StaticImageData;
imgUrl?: string | StaticImageData | File | null;
userName?: string;
hasChecked?: boolean;
hasCheckedClass?: string;
@ -22,28 +23,43 @@ const Avatar: FC<AvatarProps> = ({
hasChecked,
hasCheckedClass = "w-4 h-4 -top-0.5 -right-0.5",
}) => {
const url = imgUrl || "";
const name = userName || "John Doe";
const [resolvedImgUrl, setResolvedImgUrl] = useState<string | StaticImageData>(avatar1);
useEffect(() => {
// Resolve URL if imgUrl is a File
if (imgUrl instanceof File) {
const objectUrl = URL.createObjectURL(imgUrl);
setResolvedImgUrl(objectUrl);
// Cleanup on component unmount or when imgUrl changes
return () => URL.revokeObjectURL(objectUrl);
} else {
setResolvedImgUrl(imgUrl || avatar1); // Set the URL directly or fallback to default
}
}, [imgUrl]);
const _setBgColor = (name: string) => {
const backgroundIndex = Math.floor(
name.charCodeAt(0) % avatarColors.length
);
const backgroundIndex = Math.floor(name.charCodeAt(0) % avatarColors.length);
return avatarColors[backgroundIndex];
};
return (
<div
className={`wil-avatar relative flex-shrink-0 inline-flex items-center justify-center text-neutral-100 uppercase font-semibold shadow-inner ${radius} ${sizeClass} ${containerClassName}`}
style={{ backgroundColor: url ? undefined : _setBgColor(name) }}
style={{ backgroundColor: resolvedImgUrl ? undefined : _setBgColor(name) }}
>
{url && (
{resolvedImgUrl ? (
<Image
className={`absolute inset-0 w-full h-full object-cover ${radius}`}
src={url}
src={resolvedImgUrl}
alt={name}
fill
style={{ objectFit: "cover" }}
/>
)}
) : (
<span className="wil-avatar__name">{name[0]}</span>
)}
{hasChecked && (
<span

65
src/shared/Badge.tsx

@ -13,63 +13,56 @@ export interface BadgeProps {
const Badge: FC<BadgeProps> = ({
className = "relative",
name,
color = "blue",
color ,
href,
}) => {
const getColorClass = (hasHover = true) => {
switch (color) {
case "pink":
return `text-pink-800 bg-pink-100 ${
hasHover ? "hover:bg-pink-800" : ""
}`;
case "red":
case "SOLD_OUT":
return `text-red-800 bg-red-100 ${hasHover ? "hover:bg-red-800" : ""}`;
case "gray":
return `text-gray-800 bg-gray-100 ${
hasHover ? "hover:bg-gray-800" : ""
}`;
case "green":
case "AVAILABLE":
return `text-green-800 bg-green-100 ${
hasHover ? "hover:bg-green-800" : ""
}`;
case "purple":
return `text-purple-800 bg-purple-100 ${
hasHover ? "hover:bg-purple-800" : ""
}`;
case "indigo":
return `text-indigo-800 bg-indigo-100 ${
hasHover ? "hover:bg-indigo-800" : ""
}`;
case "yellow":
return `text-yellow-800 bg-yellow-100 ${
case "BOOKED":
return `text-bronze bg-yellow-100 ${
hasHover ? "hover:bg-yellow-800" : ""
}`;
case "blue":
return `text-blue-800 bg-blue-100 ${
hasHover ? "hover:bg-blue-800" : ""
}`;
default:
return `text-pink-800 bg-pink-100 ${
hasHover ? "hover:bg-pink-800" : ""
}`;
}
};
const getStatus = (hasHover = true) => {
switch (color) {
case "SOLD_OUT":
return `Sold Out`;
case "AVAILABLE":
return `Available`;
case "BOOKED":
return `Booked`;
default:
return `None`;
}
};
const CLASSES =
"nc-Badge inline-flex px-2.5 py-1 rounded-full font-medium text-xs " +
className;
return !!href ? (
<Link
href={href || ""}
className={`transition-colors hover:text-white duration-300 ${CLASSES} ${getColorClass()}`}
>
{name}
</Link>
) : (
return (
<span className={`${CLASSES} ${getColorClass(false)} ${className}`}>
{name}
</span>
);
{getStatus()}
</span>)
};
export default Badge;

2
src/shared/ButtonPrimary.tsx

@ -9,7 +9,7 @@ const ButtonPrimary: React.FC<ButtonPrimaryProps> = ({
}) => {
return (
<Button
className={`ttnc-ButtonPrimary disabled:bg-opacity-70 bg-primary-6000 hover:bg-primary-700 text-neutral-50 ${className}`}
className={`ttnc-ButtonPrimary disabled:bg-opacity-70 bg-bronze hover:bg-bronze-secondary text-neutral-50 hover:text-bronze ${className}`}
{...args}
/>
);

1
src/shared/Input.tsx

@ -20,6 +20,7 @@ const Input = React.forwardRef<HTMLInputElement, InputProps>(
},
ref
) => {
return (
<input
ref={ref}

23
src/shared/Logo.tsx

@ -1,10 +1,10 @@
import React from "react";
import logoImg from "@/images/logo.png";
import logoLightImg from "@/images/logo-light.png";
import logoImg from "@/images/logos/لوگو3 1.png";
import logoLightImg from "@/images/logos/لوگو3 1.svg";
import LogoSvgLight from "./LogoSvgLight";
import LogoSvg from "./LogoSvg";
import Link from "next/link";
import { StaticImageData } from "next/image";
import Image, { StaticImageData } from "next/image";
export interface LogoProps {
img?: StaticImageData;
@ -17,18 +17,19 @@ const Logo: React.FC<LogoProps> = ({
imgLight = logoLightImg,
className = "w-24",
}) => {
return (
<Link
href="/"
className={`ttnc-logo inline-block text-primary-6000 focus:outline-none focus:ring-0 ${className}`}
>
<LogoSvgLight />
<LogoSvg />
{/* <LogoSvgLight /> */}
{/* THIS USE FOR MY CLIENT */}
{/* PLEASE UN COMMENT BELLOW CODE AND USE IT */}
{/* {img ? (
<img
{img ? (
<Image
width={40}
height={40}
className={`block max-h-12 ${imgLight ? "dark:hidden" : ""}`}
src={img}
alt="Logo"
@ -37,12 +38,14 @@ const Logo: React.FC<LogoProps> = ({
"Logo Here"
)}
{imgLight && (
<img
<Image
width={40}
height={40}
className="hidden max-h-12 dark:block"
src={imgLight}
alt="Logo-Light"
/>
)} */}
)}
</Link>
);
};

2
src/shared/LogoSvgLight.tsx

@ -1,5 +1,7 @@
import React from "react";
import Logo from "../images/logos/لوگو3 1.svg";
const LogoSvgLight = () => {
return (
<svg

3
src/shared/Navigation/Navigation.tsx

@ -1,6 +1,8 @@
import React from "react";
import NavigationItem from "./NavigationItem";
import { NAVIGATION_DEMO } from "@/data/navigation";
import ButtonPrimary from "../ButtonPrimary";
import ButtonSecondary from "../ButtonSecondary";
function Navigation() {
return (
@ -8,6 +10,7 @@ function Navigation() {
{NAVIGATION_DEMO.map((item) => (
<NavigationItem key={item.id} menuItem={item} />
))}
<ButtonSecondary className="m-5">Custoum Tour</ButtonSecondary>
</ul>
);
}

0
src/shared/PhoneNumberInput.tsx

3
tailwind.config.js

@ -33,6 +33,9 @@ module.exports = {
extend: {
colors: {
'bronze': '#D09460',
'bronze-secondary': '#F9F0E9',
primary: {
50: customColors("--c-primary-50"),
100: customColors("--c-primary-100"),

82
yarn.lock

@ -461,6 +461,11 @@ ast-types-flow@^0.0.7:
resolved "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.7.tgz"
integrity sha512-eBvWn1lvIApYMhzQMsu9ciLfkBY499mFZlNqG+/9WR7PVlroQw0vG30cOQQbaKz3sCEc44TAOu2ykzqXSNnwag==
asynckit@^0.4.0:
version "0.4.0"
resolved "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz"
integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==
autoprefixer@^10.4.14:
version "10.4.14"
resolved "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.14.tgz"
@ -483,6 +488,15 @@ axe-core@^4.6.2:
resolved "https://registry.npmjs.org/axe-core/-/axe-core-4.7.1.tgz"
integrity sha512-sCXXUhA+cljomZ3ZAwb8i1p3oOlkABzPy08ZDAoGcYuvtBPlQ1Ytde129ArXyHWDhfeewq7rlx9F+cUx2SSlkg==
axios@^1.7.5:
version "1.7.5"
resolved "https://registry.npmjs.org/axios/-/axios-1.7.5.tgz"
integrity sha512-fZu86yCo+svH3uqJ/yTdQ0QHpQu5oL+/QE+QPSv6BZSkDAoky9vytxp7u5qk83OJFS3kEBcesWni9WTZAv3tSw==
dependencies:
follow-redirects "^1.15.6"
form-data "^4.0.0"
proxy-from-env "^1.1.0"
axobject-query@^3.1.1:
version "3.1.1"
resolved "https://registry.npmjs.org/axobject-query/-/axobject-query-3.1.1.tgz"
@ -619,6 +633,13 @@ color-name@~1.1.4:
resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz"
integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==
combined-stream@^1.0.8:
version "1.0.8"
resolved "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz"
integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==
dependencies:
delayed-stream "~1.0.0"
commander@^4.0.0:
version "4.1.1"
resolved "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz"
@ -761,6 +782,11 @@ define-properties@^1.1.3, define-properties@^1.1.4, define-properties@^1.2.0:
has-property-descriptors "^1.0.0"
object-keys "^1.1.1"
delayed-stream@~1.0.0:
version "1.0.0"
resolved "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz"
integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==
didyoumean@^1.2.2:
version "1.2.2"
resolved "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz"
@ -1230,6 +1256,11 @@ flatted@^3.1.0:
resolved "https://registry.npmjs.org/flatted/-/flatted-3.2.7.tgz"
integrity sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==
follow-redirects@^1.15.6:
version "1.15.6"
resolved "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz"
integrity sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==
for-each@^0.3.3:
version "0.3.3"
resolved "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz"
@ -1237,6 +1268,15 @@ for-each@^0.3.3:
dependencies:
is-callable "^1.1.3"
form-data@^4.0.0:
version "4.0.0"
resolved "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz"
integrity sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==
dependencies:
asynckit "^0.4.0"
combined-stream "^1.0.8"
mime-types "^2.1.12"
fraction.js@^4.2.0:
version "4.2.0"
resolved "https://registry.npmjs.org/fraction.js/-/fraction.js-4.2.0.tgz"
@ -1738,10 +1778,10 @@ jiti@^1.18.2:
resolved "https://registry.npmjs.org/jiti/-/jiti-1.18.2.tgz"
integrity sha512-QAdOptna2NYiSSpv0O/BwoHBSmz4YhpzJHyi+fnMRTXFjp7B8i/YG5Z8IfusxB1ufjcD2Sre1F3R+nX3fvy7gg==
jose@^4.11.4, jose@^4.14.4:
version "4.14.6"
resolved "https://registry.npmjs.org/jose/-/jose-4.14.6.tgz"
integrity sha512-EqJPEUlZD0/CSUMubKtMaYUOtWe91tZXTWMJZoKSbLk+KtdhNdcvppH8lA9XwVu2V4Ailvsj0GBZJ2ZwDjfesQ==
jose@^4.14.4, jose@^4.15.5:
version "4.15.9"
resolved "https://registry.npmjs.org/jose/-/jose-4.15.9.tgz"
integrity sha512-1vUQX+IdDMVPj4k8kOxgUqlcK518yluMuGZwqlr44FS1ppZB/5GWh4rZG89erpOBOJjU/OBsnCVFfapsRz6nEA==
js-cookie@^2.2.1:
version "2.2.1"
@ -1879,6 +1919,18 @@ micromatch@^4.0.4, micromatch@^4.0.5:
braces "^3.0.2"
picomatch "^2.3.1"
mime-db@1.52.0:
version "1.52.0"
resolved "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz"
integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==
mime-types@^2.1.12:
version "2.1.35"
resolved "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz"
integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==
dependencies:
mime-db "1.52.0"
mimic-fn@^2.1.0:
version "2.1.0"
resolved "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz"
@ -1949,22 +2001,22 @@ natural-compare@^1.4.0:
resolved "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz"
integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==
next-auth@^4.23.1:
version "4.23.1"
resolved "https://registry.npmjs.org/next-auth/-/next-auth-4.23.1.tgz"
integrity sha512-mL083z8KgRtlrIV6CDca2H1kduWJuK/3pTS0Fe2og15KOm4v2kkLGdSDfc2g+019aEBrJUT0pPW2Xx42ImN1WA==
next-auth@^4.24.7:
version "4.24.7"
resolved "https://registry.npmjs.org/next-auth/-/next-auth-4.24.7.tgz"
integrity sha512-iChjE8ov/1K/z98gdKbn2Jw+2vLgJtVV39X+rCP5SGnVQuco7QOr19FRNGMIrD8d3LYhHWV9j9sKLzq1aDWWQQ==
dependencies:
"@babel/runtime" "^7.20.13"
"@panva/hkdf" "^1.0.2"
cookie "^0.5.0"
jose "^4.11.4"
jose "^4.15.5"
oauth "^0.9.15"
openid-client "^5.4.0"
preact "^10.6.3"
preact-render-to-string "^5.1.19"
uuid "^8.3.2"
"next@^12.2.5 || ^13", next@^13.4.3:
"next@^12.2.5 || ^13 || ^14", next@^13.4.3:
version "13.4.3"
resolved "https://registry.npmjs.org/next/-/next-13.4.3.tgz"
integrity sha512-FV3pBrAAnAIfOclTvncw9dDohyeuEEXPe5KNcva91anT/rdycWbgtu3IjUj4n5yHnWK8YEPo0vrUecHmnmUNbA==
@ -2329,6 +2381,11 @@ prop-types@^15.7.2, prop-types@^15.8.1:
object-assign "^4.1.1"
react-is "^16.13.1"
proxy-from-env@^1.1.0:
version "1.1.0"
resolved "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz"
integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==
punycode@^2.1.0:
version "2.3.0"
resolved "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz"
@ -2388,6 +2445,11 @@ react-hooks-global-state@^2.1.0:
dependencies:
zustand "4.0.0"
react-icons@^5.3.0:
version "5.3.0"
resolved "https://registry.npmjs.org/react-icons/-/react-icons-5.3.0.tgz"
integrity sha512-DnUk8aFbTyQPSkCfF8dbX6kQjXA9DktMeJqfjrg6cK9vwQVMxmcA3BfP4QoiztVmEHtwlTgLFsPuH2NskKT6eg==
react-is@^16.12.0, react-is@^16.13.1:
version "16.13.1"
resolved "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz"

Loading…
Cancel
Save