From bf30c5cd8b828ee1dd81a4b65b218bb919e759d5 Mon Sep 17 00:00:00 2001
From: "[soumashree das]" <[soumshreedas2004@gmail.com]>
Date: Thu, 7 Aug 2025 00:39:18 +0530
Subject: [PATCH] fixed issues 17 and 18
---
Readme.txt | 51 +++
client/package-lock.json | 307 +++++++++++++++++
client/package.json | 2 +
client/sample.env | 3 +-
client/src/App.jsx | 86 +++--
client/src/components/HotelReg.jsx | 308 ++++++++++++++---
client/src/components/Navbar.jsx | 326 ++++++++++++------
client/src/components/ProtectedRoute.jsx | 64 ++++
client/src/pages/ClerkSignIn.jsx | 23 ++
client/src/pages/ClerkSignUp.jsx | 19 +
client/src/pages/SelectRolePage.jsx | 74 ++++
client/src/pages/Unauthorized.jsx | 16 +
.../src/pages/hotelOwner/HotelOwnerRoute.jsx | 12 +
server/configs/db.js | 4 +-
server/controller/HotelController.js | 27 ++
server/controllers/clerkWebhooks.js | 1 +
server/middleware/hotelOwnerMiddleware.js | 19 +
server/models/Hotel.js | 29 ++
server/package-lock.json | 102 +++++-
server/package.json | 1 +
server/routes/hotelRoutes.js | 11 +
server/server.js | 11 +-
22 files changed, 1317 insertions(+), 179 deletions(-)
create mode 100644 Readme.txt
create mode 100644 client/src/components/ProtectedRoute.jsx
create mode 100644 client/src/pages/ClerkSignIn.jsx
create mode 100644 client/src/pages/ClerkSignUp.jsx
create mode 100644 client/src/pages/SelectRolePage.jsx
create mode 100644 client/src/pages/Unauthorized.jsx
create mode 100644 client/src/pages/hotelOwner/HotelOwnerRoute.jsx
create mode 100644 server/controller/HotelController.js
create mode 100644 server/middleware/hotelOwnerMiddleware.js
create mode 100644 server/models/Hotel.js
create mode 100644 server/routes/hotelRoutes.js
diff --git a/Readme.txt b/Readme.txt
new file mode 100644
index 0000000..99d00cf
--- /dev/null
+++ b/Readme.txt
@@ -0,0 +1,51 @@
+to solve the following issues-
+
+#17
+Description
+The HotelReg component currently uses basic HTML required attributes for validation. This provides a minimal and often inconsistent user experience. To improve usability and guide the user, we should implement robust, real-time client-side validation.
+
+Proposed Enhancements
+1.Add onChange handlers to the form inputs.
+2.Use component state to track not just the input values, but also any validation errors.
+3.Display clear, specific error messages directly below each field as the user types (e.g., "Please enter a valid phone number," "Hotel name cannot be empty.").
+4.The "Register" button should be disabled until the form is valid, preventing invalid submissions.
+5.This task is purely a frontend component enhancement and can be worked on independently of the backend implementation for hotel registration, making it a non-conflicting issue.
+
+
+#18
+Description
+The codebase contains an incomplete component, , which is currently unused and set to {false && } in App.jsx. This component provides the UI for a hotel owner to register a new hotel but lacks the necessary backend logic and integration.
+
+This feature is a core part of the "Admin Functionality" mentioned in the README ("Add new hotel listings").
+
+Proposed Plan to Complete the Feature
+ - Create a Backend Endpoint:
+
+ * Design a Hotel model in Mongoose.
+ * Create a new API endpoint on the server, e.g., POST /api/hotels.
+ * This endpoint must be a protected route that only users with the 'hotelOwner' role can access. It will receive hotel data from the form and save it to the database.
+ - Activate the Frontend Component:
+
+ * Wire up the form's onSubmit handler to make an authenticated axios POST request to the new /api/hotels endpoint.
+ * Implement state management for loading and error states during form submission.
+ * Provide user feedback upon success (e.g., "Hotel registered successfully!") or failure using a toast notification system.
+Integrate into the UI:
+
+Add a "Register Hotel" button to the Owner Dashboard (/owner) that opens the component as a modal.
+Completing this feature is a major step towards fulfilling the project's core promise for hotel owners. I am prepared to work on both the frontend and backend parts of this task.
+
+#24
+Hi team! ๐
+
+-I noticed a few areas in the project that could use improvements for better user flow and functionality:
+
+-Most of the navbar buttons (like Home, Hotels, Experience, etc.) donโt seem to be functional or redirect anywhere.
+
+-The Dashboard is accessible even without authentication or login, which breaks the security and user flow.
+
+-Dropdowns or interactive UI elements (e.g., date pickers, guest selectors) are present but not fully wired up or responsive in -behavior.
+
+-Iโd love to contribute and improve the UX by fixing these issues one at a time.
+-Iโd like to start with making the navbar fully functional
+
+Please let me know if itโs okay to proceed โ I can then create a PR with the first batch of fixes
\ No newline at end of file
diff --git a/client/package-lock.json b/client/package-lock.json
index 1118d87..8749a39 100644
--- a/client/package-lock.json
+++ b/client/package-lock.json
@@ -10,8 +10,10 @@
"dependencies": {
"@clerk/clerk-react": "^5.31.9",
"@tailwindcss/vite": "^4.1.8",
+ "axios": "^1.11.0",
"react": "^19.1.0",
"react-dom": "^19.1.0",
+ "react-hot-toast": "^2.5.2",
"react-router-dom": "^7.6.2",
"tailwindcss": "^4.1.8"
},
@@ -1754,6 +1756,23 @@
"dev": true,
"license": "Python-2.0"
},
+ "node_modules/asynckit": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
+ "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
+ "license": "MIT"
+ },
+ "node_modules/axios": {
+ "version": "1.11.0",
+ "resolved": "https://registry.npmjs.org/axios/-/axios-1.11.0.tgz",
+ "integrity": "sha512-1Lx3WLFQWm3ooKDYZD1eXmoGO9fxYQjrycfHFC8P0sCfQVXyROp0p9PFWBehewBOdCwHc+f/b8I0fMto5eSfwA==",
+ "license": "MIT",
+ "dependencies": {
+ "follow-redirects": "^1.15.6",
+ "form-data": "^4.0.4",
+ "proxy-from-env": "^1.1.0"
+ }
+ },
"node_modules/balanced-match": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
@@ -1805,6 +1824,19 @@
"node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
}
},
+ "node_modules/call-bind-apply-helpers": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
+ "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "function-bind": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
"node_modules/callsites": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
@@ -1882,6 +1914,18 @@
"dev": true,
"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==",
+ "license": "MIT",
+ "dependencies": {
+ "delayed-stream": "~1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
"node_modules/concat-map": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
@@ -1951,6 +1995,15 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/delayed-stream": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
+ "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
"node_modules/dequal": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz",
@@ -1969,6 +2022,20 @@
"node": ">=8"
}
},
+ "node_modules/dunder-proto": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
+ "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bind-apply-helpers": "^1.0.1",
+ "es-errors": "^1.3.0",
+ "gopd": "^1.2.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
"node_modules/electron-to-chromium": {
"version": "1.5.165",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.165.tgz",
@@ -1989,6 +2056,51 @@
"node": ">=10.13.0"
}
},
+ "node_modules/es-define-property": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
+ "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-errors": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
+ "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-object-atoms": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
+ "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-set-tostringtag": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
+ "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "get-intrinsic": "^1.2.6",
+ "has-tostringtag": "^1.0.2",
+ "hasown": "^2.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
"node_modules/esbuild": {
"version": "0.25.5",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.5.tgz",
@@ -2316,6 +2428,42 @@
"dev": true,
"license": "ISC"
},
+ "node_modules/follow-redirects": {
+ "version": "1.15.11",
+ "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz",
+ "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==",
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://github.com/sponsors/RubenVerborgh"
+ }
+ ],
+ "license": "MIT",
+ "engines": {
+ "node": ">=4.0"
+ },
+ "peerDependenciesMeta": {
+ "debug": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/form-data": {
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz",
+ "integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==",
+ "license": "MIT",
+ "dependencies": {
+ "asynckit": "^0.4.0",
+ "combined-stream": "^1.0.8",
+ "es-set-tostringtag": "^2.1.0",
+ "hasown": "^2.0.2",
+ "mime-types": "^2.1.12"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
"node_modules/fsevents": {
"version": "2.3.3",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
@@ -2330,6 +2478,15 @@
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
}
},
+ "node_modules/function-bind": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
+ "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
"node_modules/gensync": {
"version": "1.0.0-beta.2",
"resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
@@ -2340,6 +2497,43 @@
"node": ">=6.9.0"
}
},
+ "node_modules/get-intrinsic": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
+ "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bind-apply-helpers": "^1.0.2",
+ "es-define-property": "^1.0.1",
+ "es-errors": "^1.3.0",
+ "es-object-atoms": "^1.1.1",
+ "function-bind": "^1.1.2",
+ "get-proto": "^1.0.1",
+ "gopd": "^1.2.0",
+ "has-symbols": "^1.1.0",
+ "hasown": "^2.0.2",
+ "math-intrinsics": "^1.1.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/get-proto": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
+ "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
+ "license": "MIT",
+ "dependencies": {
+ "dunder-proto": "^1.0.1",
+ "es-object-atoms": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
"node_modules/glob-parent": {
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
@@ -2372,6 +2566,27 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/goober": {
+ "version": "2.1.16",
+ "resolved": "https://registry.npmjs.org/goober/-/goober-2.1.16.tgz",
+ "integrity": "sha512-erjk19y1U33+XAMe1VTvIONHYoSqE4iS7BYUZfHaqeohLmnC0FdxEh7rQU+6MZ4OajItzjZFSRtVANrQwNq6/g==",
+ "license": "MIT",
+ "peerDependencies": {
+ "csstype": "^3.0.10"
+ }
+ },
+ "node_modules/gopd": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
+ "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
"node_modules/graceful-fs": {
"version": "4.2.11",
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
@@ -2388,6 +2603,45 @@
"node": ">=8"
}
},
+ "node_modules/has-symbols": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
+ "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/has-tostringtag": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
+ "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
+ "license": "MIT",
+ "dependencies": {
+ "has-symbols": "^1.0.3"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/hasown": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
+ "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
+ "license": "MIT",
+ "dependencies": {
+ "function-bind": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
"node_modules/ignore": {
"version": "5.3.2",
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
@@ -2834,6 +3088,36 @@
"@jridgewell/sourcemap-codec": "^1.5.0"
}
},
+ "node_modules/math-intrinsics": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
+ "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "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==",
+ "license": "MIT",
+ "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==",
+ "license": "MIT",
+ "dependencies": {
+ "mime-db": "1.52.0"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
"node_modules/minimatch": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
@@ -3061,6 +3345,12 @@
"node": ">= 0.8.0"
}
},
+ "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==",
+ "license": "MIT"
+ },
"node_modules/punycode": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
@@ -3092,6 +3382,23 @@
"react": "^19.1.0"
}
},
+ "node_modules/react-hot-toast": {
+ "version": "2.5.2",
+ "resolved": "https://registry.npmjs.org/react-hot-toast/-/react-hot-toast-2.5.2.tgz",
+ "integrity": "sha512-Tun3BbCxzmXXM7C+NI4qiv6lT0uwGh4oAfeJyNOjYUejTsm35mK9iCaYLGv8cBz9L5YxZLx/2ii7zsIwPtPUdw==",
+ "license": "MIT",
+ "dependencies": {
+ "csstype": "^3.1.3",
+ "goober": "^2.1.16"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "peerDependencies": {
+ "react": ">=16",
+ "react-dom": ">=16"
+ }
+ },
"node_modules/react-refresh": {
"version": "0.17.0",
"resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz",
diff --git a/client/package.json b/client/package.json
index f0b3d94..1b0dc20 100644
--- a/client/package.json
+++ b/client/package.json
@@ -12,8 +12,10 @@
"dependencies": {
"@clerk/clerk-react": "^5.31.9",
"@tailwindcss/vite": "^4.1.8",
+ "axios": "^1.11.0",
"react": "^19.1.0",
"react-dom": "^19.1.0",
+ "react-hot-toast": "^2.5.2",
"react-router-dom": "^7.6.2",
"tailwindcss": "^4.1.8"
},
diff --git a/client/sample.env b/client/sample.env
index 7c6199a..5789276 100644
--- a/client/sample.env
+++ b/client/sample.env
@@ -1,3 +1,4 @@
VITE_CLERK_PUBLISHABLE_KEY=Enter_the_key
VITE_BACKEND_URL=your_backend_url
-VITE_CURRENCY=your_preferred_currency
\ No newline at end of file
+VITE_CURRENCY=your_preferred_currency
+CLERK_SECRET_KEY=your_secret_key
diff --git a/client/src/App.jsx b/client/src/App.jsx
index 3a8fc1a..6d9c141 100644
--- a/client/src/App.jsx
+++ b/client/src/App.jsx
@@ -1,41 +1,69 @@
-import React from 'react'
-import Navbar from './components/Navbar'
-import { Route, Routes, useLocation } from 'react-router-dom'
-import Home from './pages/Home';
-import Footer from './components/Footer';
-import AllRooms from './pages/AllRooms';
-import RoomDetails from './pages/RoomDetails';
-import MyBookings from './pages/MyBookings';
-import HotelReg from './components/HotelReg';
-import Layout from './pages/hotelOwner/Layout';
-import Dashboard from './pages/hotelOwner/Dashboard';
-import AddRoom from './pages/hotelOwner/AddRoom';
-import ListRoom from './pages/hotelOwner/ListRoom';
+import React from "react";
+import Navbar from "./components/Navbar";
+import { Route, Routes, useLocation } from "react-router-dom";
+import Home from "./pages/Home";
+import Footer from "./components/Footer";
+import AllRooms from "./pages/AllRooms";
+import RoomDetails from "./pages/RoomDetails";
+import MyBookings from "./pages/MyBookings";
+import HotelReg from "./components/HotelReg";
+import Layout from "./pages/hotelOwner/Layout";
+import Dashboard from "./pages/hotelOwner/Dashboard";
+import AddRoom from "./pages/hotelOwner/AddRoom";
+import ListRoom from "./pages/hotelOwner/ListRoom";
+import SignUpPage from "./pages/ClerkSignUp.jsx";
+import SignInPage from "./pages/ClerkSignIn.jsx";
+import SelectRolePage from "./pages/SelectRolePage.jsx";
+import HotelOwnerRoute from "./pages/hotelOwner/HotelOwnerRoute.jsx";
+import Unauthorized from "./pages/Unauthorized";
const App = () => {
-
const isOwnerPath = useLocation().pathname.includes("owner");
return (
{!isOwnerPath &&
}
- {false &&
}
-
+ {/*
*/}
+ {/* {false &&
} */}
+
- } />
- } />
- } />
- } />
- }>
- }/>
- }/>
- }/>
-
+ } />
+ } />
+ } />
+ } />
+
+ } />
+ } />
+ } />
+
+
+
+
+ }
+ >
+ } />
+ } />
+ } />
+
+
+
+
+
+ }
+ />
+
+ } />
-
+
- )
-}
+ );
+};
-export default App
\ No newline at end of file
+export default App;
diff --git a/client/src/components/HotelReg.jsx b/client/src/components/HotelReg.jsx
index 5df4ccd..41a7178 100644
--- a/client/src/components/HotelReg.jsx
+++ b/client/src/components/HotelReg.jsx
@@ -1,48 +1,272 @@
-import React from 'react'
-import { assets, cities } from '../assets/assets'
+import React, { useState,useEffect } from "react";
+import { assets, cities } from "../assets/assets";
+import { useAuth } from '@clerk/clerk-react';
+import axios from 'axios';
+import { useNavigate } from "react-router-dom";
+import { toast } from 'react-hot-toast';
+
+const BASE_URL = import.meta.env.VITE_BACKEND_URL;
const HotelReg = () => {
+ const navigate = useNavigate();
+
+ const { getToken } = useAuth();
+ const [loading, setLoading] = useState(false);
+ const [formData, setFormData] = useState({
+ name: "",
+ contact: "",
+ address: "",
+ city: "",
+ });
+
+ const [errors, setErrors] = useState({
+ name: "",
+ contact: "",
+ address: "",
+ city: "",
+ });
+
+ const [isFormValid, setIsFormValid] = useState(false);
+
+ const validateField = (id, value) => {
+ let error = "";
+ switch (id) {
+ case "name":
+ if (!value.trim()) {
+ error = "Hotel name cannot be empty.";
+ } else if (!/^[A-Za-z0-9&\-',.() ]{2,100}$/.test(value.trim())) {
+ error = "Please enter a valid hotel name.";
+ }
+ break;
+ case "contact":
+ if (!/^\d{10}$/.test(value)) {
+ error = "Please enter a valid 10-digit phone number.";
+ }
+ break;
+ case "address":
+ if (!value.trim()) {
+ error = "Address cannot be empty.";
+ }
+ break;
+ case "city":
+ if (!value) {
+ error = "Please select a city.";
+ }
+ break;
+ default:
+ break;
+ }
+ setErrors((prev) => ({
+ ...prev,
+ [id]: error,
+ }));
+ return error;
+ };
+
+ const handleChange = (e) => {
+ const { id, value } = e.target;
+ setFormData((prev) => ({
+ ...prev,
+ [id]: value,
+ }));
+ };
+
+ const handleBlur = async(e) => {
+ const { id, value } = e.target;
+ validateField(id, value);
+ };
+
+ const handleSubmit = async(e) => {
+ e.preventDefault();
+ let isValid = true;
+ let newErrors = {};
+
+ Object.entries(formData).forEach(([key, value]) => {
+ const error = validateField(key, value);
+ newErrors[key] = error;
+ if (error) isValid = false;
+ });
+
+ setErrors(newErrors);
+
+ if (isValid) {
+ console.log("Form submitted successfully!", formData);
+ alert("Hotel registered successfully!");
+ // // You can add actual submission logic here
+ try {
+ const token = await getToken();
+
+ const response = await axios.post('/api/hotels/register', {
+ name: formData.name,
+ contact: formData.contact,
+ address: formData.address,
+ city: formData.city
+ }, {
+ headers: {
+ Authorization: `Bearer ${token}`,
+ 'Content-Type': 'application/json'
+ }
+ });
+
+ if (response.data.success) {
+ toast.success('Hotel registered successfully! ๐');
+
+ // Reset form
+ setFormData({
+ name: '',
+ location: '',
+ pricePerNight: '',
+ imageUrl: '',
+ description: ''
+ });
+ }
+ } catch (error) {
+ console.error('Error registering hotel:', error);
+
+ if (error.response?.status === 403) {
+ toast.error('Access denied. Hotel owner role required.');
+ } else if (error.response?.data?.message) {
+ toast.error(error.response.data.message);
+ } else if (error.response?.data?.errors) {
+ toast.error(error.response.data.errors.join(', '));
+ } else {
+ toast.error('Failed to register hotel. Please try again.');
+ }
+ } finally {
+ setLoading(false);
+ }
+ }
+ };
+
+ useEffect(() => {
+ const hasErrors = Object.values(errors).some((err) => err !== "");
+ const hasEmptyFields = Object.values(formData).some(
+ (val) => val.trim() === ""
+ );
+ setIsFormValid(!hasErrors && !hasEmptyFields);
+ }, [formData, errors]);
+
return (
-
-
+
- )
-}
+ );
+};
-export default HotelReg
\ No newline at end of file
+export default HotelReg;
diff --git a/client/src/components/Navbar.jsx b/client/src/components/Navbar.jsx
index 79b8dff..876e78c 100644
--- a/client/src/components/Navbar.jsx
+++ b/client/src/components/Navbar.jsx
@@ -1,106 +1,228 @@
-import React, { useEffect, useState } from 'react';
-import { Link, useLocation, useNavigate } from 'react-router-dom';
-import { assets } from '../assets/assets';
-import { useClerk, useUser, UserButton } from '@clerk/clerk-react';
+import React, { useEffect, useState } from "react";
+import { Link, useLocation, useNavigate } from "react-router-dom";
+import { assets } from "../assets/assets";
+import { useClerk, useUser, UserButton } from "@clerk/clerk-react";
+// import RoleSwitchButton from "./RoleSwitchButton";
const BookIcon = () => (
-
-
-
-)
+
+
+
+);
const Navbar = () => {
- const navLinks = [
- { name: 'Home', path: '/' },
- { name: 'Hotels', path: '/rooms' },
- { name: 'Experience', path: '/' },
- { name: 'About', path: '/' },
- ];
-
- const [isScrolled, setIsScrolled] = useState(false);
- const [isMenuOpen, setIsMenuOpen] = useState(false);
-
- const {openSignIn} = useClerk()
- const {user} = useUser()
- const navigate = useNavigate()
- const location = useLocation()
-
- useEffect(() => {
- setIsScrolled(prev => location.pathname !== '/' ? true : prev)
-
- const handleScroll = () => {
- setIsScrolled(window.scrollY > 10);
- };
- window.addEventListener("scroll", handleScroll);
- return () => window.removeEventListener("scroll", handleScroll);
- }, [location.pathname]);
-
- return (
-
-
- {/* Logo */}
-
-
-
-
- {/* Desktop Nav */}
-
- {navLinks.map((link, i) => (
-
- {link.name}
-
-
- ))}
-
navigate('/owner')}>
- Dashboard
-
-
-
- {/* Desktop Right */}
-
-
- {user ? (
-
- } onClick={()=> navigate('/my-bookings')}/>
-
- ) : (
- Login
- )}
-
-
-
- {/* Mobile Menu Button */}
-
- {user &&
-
- } onClick={()=> navigate('/my-bookings')}/>
-
- }
-
setIsMenuOpen(!isMenuOpen)} src={assets.menuIcon} alt="" className={`${isScrolled && 'invert'} h-4`}/>
-
-
- {/* Mobile Menu */}
-
-
- );
-}
-
-export default Navbar
\ No newline at end of file
+ const navLinks = [
+ { name: "Home", path: "/" },
+ { name: "Hotels", path: "/rooms" },
+ { name: "Experience", path: "/" },
+ { name: "About", path: "/" },
+ ];
+
+ const [isScrolled, setIsScrolled] = useState(false);
+ const [isMenuOpen, setIsMenuOpen] = useState(false);
+
+ const { openSignIn } = useClerk();
+ const { user } = useUser();
+ const navigate = useNavigate();
+ const location = useLocation();
+
+ const isAdmin = user?.publicMetadata?.role === "admin";
+
+ useEffect(() => {
+ setIsScrolled((prev) => (location.pathname !== "/" ? true : prev));
+
+ const handleScroll = () => {
+ setIsScrolled(window.scrollY > 10);
+ };
+ window.addEventListener("scroll", handleScroll);
+ return () => window.removeEventListener("scroll", handleScroll);
+ }, [location.pathname]);
+
+ return (
+
+ {/* Logo */}
+
+
+
+
+ {/* Desktop Nav */}
+
+ {navLinks.map((link, i) => (
+
+ {link.name}
+
+
+ ))}
+
navigate("/owner")}
+ >
+ Dashboard
+
+
+
+ {/* Desktop Right */}
+
+
+ {user ? (
+
+
+ }
+ onClick={() => navigate("/my-bookings")}
+ />
+
+
+ ) : (
+ //
navigate("/sign-up")}
+ // // onClick={openSignIn}
+ // className="bg-black text-white px-8 py-2.5 rounded-full ml-4 transition-all duration-500"
+ // >
+ // Login
+ //
+
+
+ navigate("/sign-in")}
+ className="bg-black text-white px-8 py-2.5 rounded-full ml-4 transition-all duration-500"
+ >
+ Sign In
+
+ navigate("/sign-up")}
+ className="bg-black text-white px-8 py-2.5 rounded-full ml-4 transition-all duration-500"
+ >
+ Sign Up
+
+
+
+ )}
+
+
+ {/* Mobile Menu Button */}
+
+ {user && (
+
+
+ }
+ onClick={() => navigate("/my-bookings")}
+ />
+
+
+ )}
+
setIsMenuOpen(!isMenuOpen)}
+ src={assets.menuIcon}
+ alt=""
+ className={`${isScrolled && "invert"} h-4`}
+ />
+
+
+ {/* Mobile Menu */}
+
+
setIsMenuOpen(false)}
+ >
+
+
+
+ {navLinks.map((link, i) => (
+
setIsMenuOpen(false)}>
+ {link.name}
+
+ ))}
+
+ {user && (
+
navigate("/owner")}
+ >
+ Dashboard
+
+ )}
+
+ {!user && (
+ //
navigate("/sign-up")}
+ // className="bg-black text-white px-8 py-2.5 rounded-full transition-all duration-500"
+ // >
+ // Login
+ //
+
+
+ Account
+
+
+ navigate("/sign-in")}
+ className="block w-full text-left px-4 py-2 hover:bg-gray-100"
+ >
+ Sign In
+
+ navigate("/sign-up")}
+ className="block w-full text-left px-4 py-2 hover:bg-gray-100"
+ >
+ Sign Up
+
+
+
+ )}
+
+
+ );
+};
+
+export default Navbar;
diff --git a/client/src/components/ProtectedRoute.jsx b/client/src/components/ProtectedRoute.jsx
new file mode 100644
index 0000000..d9729f1
--- /dev/null
+++ b/client/src/components/ProtectedRoute.jsx
@@ -0,0 +1,64 @@
+import { useUser } from "@clerk/clerk-react";
+import { Navigate } from "react-router-dom";
+import Unauthorized from "../pages/Unauthorized";
+
+const ProtectedRoute = ({ children, allowedRoles = [] }) => {
+ const { user, isLoaded } = useUser();
+
+ // Show loading while user data is being fetched
+ if (!isLoaded) {
+ return (
+
+ );
+ }
+
+ // If user is not authenticated, redirect to sign-in
+ if (!user) {
+ return
;
+ }
+
+ // Get user role from metadata (adjust based on your solution)
+ const userRole = user.unsafeMetadata?.role || user.publicMetadata?.role;
+
+ // If user doesn't have a role assigned, redirect to role selection
+ if (!userRole) {
+ return
;
+ }
+
+ if (!allowedRoles.includes(userRole)) {
+ // Logged in but not authorized
+ return
;
+ }
+
+ // If specific roles are required, check if user has the required role
+ if (allowedRoles.length > 0 && !allowedRoles.includes(userRole)) {
+ return (
+
+
+
Access Denied
+
+ You don't have permission to access this page.
+
+
+ Required role: {allowedRoles.join(" or ")}
+
+
+ Your role: {userRole}
+
+
window.history.back()}
+ className="mt-4 px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600"
+ >
+ Go Back
+
+
+
+ );
+ }
+
+ return children;
+};
+
+export default ProtectedRoute;
\ No newline at end of file
diff --git a/client/src/pages/ClerkSignIn.jsx b/client/src/pages/ClerkSignIn.jsx
new file mode 100644
index 0000000..192e01a
--- /dev/null
+++ b/client/src/pages/ClerkSignIn.jsx
@@ -0,0 +1,23 @@
+import { useNavigate } from "react-router-dom";
+import { SignIn } from "@clerk/clerk-react";
+
+export default function SignInPage() {
+ const navigate = useNavigate();
+ return (
+
+ {
+ navigate("/select-role"); // โ
Custom redirect after success
+ }}
+ // redirectUrl="/select-role"
+ />
+
+ );
+}
+
diff --git a/client/src/pages/ClerkSignUp.jsx b/client/src/pages/ClerkSignUp.jsx
new file mode 100644
index 0000000..ef7c042
--- /dev/null
+++ b/client/src/pages/ClerkSignUp.jsx
@@ -0,0 +1,19 @@
+
+import { SignUp } from "@clerk/clerk-react";
+
+export default function SignUpPage() {
+ return (
+
+
+
+ );
+}
+
diff --git a/client/src/pages/SelectRolePage.jsx b/client/src/pages/SelectRolePage.jsx
new file mode 100644
index 0000000..0f22ed5
--- /dev/null
+++ b/client/src/pages/SelectRolePage.jsx
@@ -0,0 +1,74 @@
+import { useUser } from "@clerk/clerk-react";
+import { useState } from "react";
+
+export default function SelectRolePage() {
+ const { user, isLoaded } = useUser();
+ const [role, setRole] = useState("");
+ const [isSubmitting, setIsSubmitting] = useState(false);
+
+ const handleSubmit = async (e) => {
+ e.preventDefault();
+ if (!isLoaded || !user) return;
+
+ setIsSubmitting(true);
+
+ try {
+ await user.update({
+ unsafeMetadata: {
+ ...user.unsafeMetadata,
+ role: role
+ }
+ });
+
+ // Redirect to dashboard or home page
+ window.location.href = "/";
+ } catch (error) {
+ console.error("Error updating role:", error);
+ } finally {
+ setIsSubmitting(false);
+ }
+ };
+
+ if (!isLoaded) {
+ return
Loading...
;
+ }
+
+ // If user already has a role, redirect them
+ if (user?.unsafeMetadata?.role) {
+ window.location.href = "/";
+ return null;
+ }
+
+ return (
+
+ );
+}
\ No newline at end of file
diff --git a/client/src/pages/Unauthorized.jsx b/client/src/pages/Unauthorized.jsx
new file mode 100644
index 0000000..a820b49
--- /dev/null
+++ b/client/src/pages/Unauthorized.jsx
@@ -0,0 +1,16 @@
+import React from 'react'
+
+function Unauthorized() {
+ return (
+
+
+
Access Denied
+
+ You must be a Hotel Owner to access this page and register hotels.
+
+
+
+ )
+}
+
+export default Unauthorized;
\ No newline at end of file
diff --git a/client/src/pages/hotelOwner/HotelOwnerRoute.jsx b/client/src/pages/hotelOwner/HotelOwnerRoute.jsx
new file mode 100644
index 0000000..7acad2f
--- /dev/null
+++ b/client/src/pages/hotelOwner/HotelOwnerRoute.jsx
@@ -0,0 +1,12 @@
+import ProtectedRoute from "../../components/ProtectedRoute";
+
+const HotelOwnerRoute = ({ children }) => {
+
+ return (
+
+ {children}
+
+ );
+};
+
+export default HotelOwnerRoute;
\ No newline at end of file
diff --git a/server/configs/db.js b/server/configs/db.js
index c9c0cae..6ee9f57 100644
--- a/server/configs/db.js
+++ b/server/configs/db.js
@@ -5,9 +5,9 @@ const connectDB = async () => {
mongoose.connection.on('connected', () => {
console.log('Database connected');
});
- await mongoose.connect(`${process.env.MONGODB_URI}/hotel-booking`);
+ await mongoose.connect(`${process.env.MONGO_URI}`);
} catch (error) {
- console.log(error.message);
+ console.log("db error:",error.message);
}
};
diff --git a/server/controller/HotelController.js b/server/controller/HotelController.js
new file mode 100644
index 0000000..4865426
--- /dev/null
+++ b/server/controller/HotelController.js
@@ -0,0 +1,27 @@
+import Hotel from "../models/Hotel.js";
+
+export const registerHotel = async (req, res) => {
+ try {
+ const { name, contact, address, city } = req.body;
+ const ownerId = req.auth.userId; // Clerk user ID
+
+ if (!name || !contact || !address || !city) {
+ return res.status(400).json({ success: false, message: "All fields are required." });
+ }
+
+ const newHotel = new Hotel({
+ name,
+ contact,
+ address,
+ city,
+ ownerId,
+ });
+
+ await newHotel.save();
+
+ return res.status(201).json({ success: true, message: "Hotel registered successfully." });
+ } catch (error) {
+ console.error("Error in registerHotel:", error);
+ return res.status(500).json({ success: false, message: "Internal server error." });
+ }
+};
diff --git a/server/controllers/clerkWebhooks.js b/server/controllers/clerkWebhooks.js
index b8fe030..52f7e78 100644
--- a/server/controllers/clerkWebhooks.js
+++ b/server/controllers/clerkWebhooks.js
@@ -19,6 +19,7 @@ const clerkWebhooks = async (req, res) => {
email: data.email_addresses[0].email_address,
username: data.first_name + " " + data.last_name,
image: data.image_url,
+ role:ata.public_metadata?.role || "user"
}
//Switch case for different types of events
diff --git a/server/middleware/hotelOwnerMiddleware.js b/server/middleware/hotelOwnerMiddleware.js
new file mode 100644
index 0000000..9136443
--- /dev/null
+++ b/server/middleware/hotelOwnerMiddleware.js
@@ -0,0 +1,19 @@
+import User from '../models/User.js';
+
+const hotelOwner = async (req, res, next) => {
+ try {
+ const userId = req.auth.userId;
+
+ const user = await User.findById(userId);
+ if (!user || user.role !== 'hotelOwner') {
+ return res.status(403).json({ success: false, message: 'Access denied. Hotel owner role required.' });
+ }
+
+ next();
+ } catch (error) {
+ console.error("Role check error:", error);
+ res.status(500).json({ success: false, message: "Server error during role check." });
+ }
+};
+
+export default hotelOwner;
diff --git a/server/models/Hotel.js b/server/models/Hotel.js
new file mode 100644
index 0000000..d150f97
--- /dev/null
+++ b/server/models/Hotel.js
@@ -0,0 +1,29 @@
+import mongoose from 'mongoose';
+
+const hotelSchema = new mongoose.Schema({
+ name: {
+ type: String,
+ required: true,
+ trim: true,
+ },
+ contact: {
+ type: String,
+ required: true,
+ match: [/^\d{10}$/, "Invalid 10-digit contact number"],
+ },
+ address: {
+ type: String,
+ required: true,
+ },
+ city: {
+ type: String,
+ required: true,
+ },
+ ownerId: {
+ type: String,
+ required: true,
+ },
+}, { timestamps: true });
+
+const Hotel = mongoose.model('Hotel', hotelSchema);
+export default Hotel;
diff --git a/server/package-lock.json b/server/package-lock.json
index df5ecf3..4c29a2a 100644
--- a/server/package-lock.json
+++ b/server/package-lock.json
@@ -14,6 +14,7 @@
"cors": "^2.8.5",
"dotenv": "^16.6.0",
"express": "^5.1.0",
+ "jsonwebtoken": "^9.0.2",
"mongoose": "^8.16.1",
"multer": "^2.0.1",
"svix": "^1.68.0"
@@ -252,6 +253,12 @@
"node": ">=16.20.1"
}
},
+ "node_modules/buffer-equal-constant-time": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz",
+ "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==",
+ "license": "BSD-3-Clause"
+ },
"node_modules/buffer-from": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
@@ -496,6 +503,15 @@
"node": ">= 0.4"
}
},
+ "node_modules/ecdsa-sig-formatter": {
+ "version": "1.0.11",
+ "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz",
+ "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "safe-buffer": "^5.0.1"
+ }
+ },
"node_modules/ee-first": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
@@ -904,6 +920,49 @@
"node": ">=14"
}
},
+ "node_modules/jsonwebtoken": {
+ "version": "9.0.2",
+ "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz",
+ "integrity": "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==",
+ "license": "MIT",
+ "dependencies": {
+ "jws": "^3.2.2",
+ "lodash.includes": "^4.3.0",
+ "lodash.isboolean": "^3.0.3",
+ "lodash.isinteger": "^4.0.4",
+ "lodash.isnumber": "^3.0.3",
+ "lodash.isplainobject": "^4.0.6",
+ "lodash.isstring": "^4.0.1",
+ "lodash.once": "^4.0.0",
+ "ms": "^2.1.1",
+ "semver": "^7.5.4"
+ },
+ "engines": {
+ "node": ">=12",
+ "npm": ">=6"
+ }
+ },
+ "node_modules/jwa": {
+ "version": "1.4.2",
+ "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.2.tgz",
+ "integrity": "sha512-eeH5JO+21J78qMvTIDdBXidBd6nG2kZjg5Ohz/1fpa28Z4CcsWUzJ1ZZyFq/3z3N17aZy+ZuBoHljASbL1WfOw==",
+ "license": "MIT",
+ "dependencies": {
+ "buffer-equal-constant-time": "^1.0.1",
+ "ecdsa-sig-formatter": "1.0.11",
+ "safe-buffer": "^5.0.1"
+ }
+ },
+ "node_modules/jws": {
+ "version": "3.2.2",
+ "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz",
+ "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==",
+ "license": "MIT",
+ "dependencies": {
+ "jwa": "^1.4.1",
+ "safe-buffer": "^5.0.1"
+ }
+ },
"node_modules/kareem": {
"version": "2.6.3",
"resolved": "https://registry.npmjs.org/kareem/-/kareem-2.6.3.tgz",
@@ -919,6 +978,48 @@
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
"license": "MIT"
},
+ "node_modules/lodash.includes": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz",
+ "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==",
+ "license": "MIT"
+ },
+ "node_modules/lodash.isboolean": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz",
+ "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==",
+ "license": "MIT"
+ },
+ "node_modules/lodash.isinteger": {
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz",
+ "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==",
+ "license": "MIT"
+ },
+ "node_modules/lodash.isnumber": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz",
+ "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==",
+ "license": "MIT"
+ },
+ "node_modules/lodash.isplainobject": {
+ "version": "4.0.6",
+ "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz",
+ "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==",
+ "license": "MIT"
+ },
+ "node_modules/lodash.isstring": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz",
+ "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==",
+ "license": "MIT"
+ },
+ "node_modules/lodash.once": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz",
+ "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==",
+ "license": "MIT"
+ },
"node_modules/lower-case": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz",
@@ -1544,7 +1645,6 @@
"version": "7.7.2",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz",
"integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==",
- "dev": true,
"license": "ISC",
"bin": {
"semver": "bin/semver.js"
diff --git a/server/package.json b/server/package.json
index 697af2b..c8e8e32 100644
--- a/server/package.json
+++ b/server/package.json
@@ -17,6 +17,7 @@
"cors": "^2.8.5",
"dotenv": "^16.6.0",
"express": "^5.1.0",
+ "jsonwebtoken": "^9.0.2",
"mongoose": "^8.16.1",
"multer": "^2.0.1",
"svix": "^1.68.0"
diff --git a/server/routes/hotelRoutes.js b/server/routes/hotelRoutes.js
new file mode 100644
index 0000000..f42befb
--- /dev/null
+++ b/server/routes/hotelRoutes.js
@@ -0,0 +1,11 @@
+import express from 'express';
+import { registerHotel } from '../controller/HotelController.js';
+import { requireAuth } from '@clerk/express'; // Auth middleware from Clerk
+import hotelOwner from '../middleware/hotelOwnerMiddleware.js'; // Custom role check middleware
+
+const router = express.Router();
+
+// POST /api/hotels/register
+router.post('/register', requireAuth(), hotelOwner, registerHotel);
+
+export default router;
diff --git a/server/server.js b/server/server.js
index 6a10a2b..9e98089 100644
--- a/server/server.js
+++ b/server/server.js
@@ -4,6 +4,7 @@ import cors from "cors";
import connectDB from "./configs/db.js";
import { clerkMiddleware } from '@clerk/express'
import clerkWebhooks from "./controllers/clerkWebhooks.js";
+import hotelRoutes from "./routes/hotelRoutes.js";
connectDB();
const app = express();
@@ -13,11 +14,17 @@ app.use(cors()); // Enable Cross-Origin Resource Sharing
app.use(express.json());
app.use(clerkMiddleware());
-
//API to listen to clerk Webhooks
-
app.use('/api/clerk', clerkWebhooks);
+//hotel routes
+app.use('/api/hotels',hotelRoutes);
+
+//user routes
+// import userRoutes from "./routes/userRoutes.js";
+// app.use("/api/users", userRoutes);
+
+//test api
app.get('/', (req, res) => {
res.send("API is Up and running");
});