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 ( -
-
- reg-image -
- close-icon -

Register Your Hotel

- {/* Hotel Name */} -
- - - -
- {/* Phone */} -
- - - -
- {/* Address */} -
- - - -
- {/* City */} -
- - -
- -
-
+
+
+ reg-image +
+ close-iconnavigate("/rooms")} + className="absolute top-4 right-4 h-4 w-4 cursor-pointer" + /> +

Register Your Hotel

+ + {/* Hotel Name */} +
+ + + {errors.name && ( +

{errors.name}

+ )} +
+ + {/* Phone */} +
+ + + {errors.contact && ( +

{errors.contact}

+ )} +
+ + {/* Address */} +
+ + + {errors.address && ( +

{errors.address}

+ )} +
+ + {/* City */} +
+ + + {errors.city && ( +

{errors.city}

+ )} +
+ +
+
- ) -} + ); +}; -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 ( - - ); -} - -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 ( + + ); +}; + +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} +

+ +
+
+ ); + } + + 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 ( +
+
+

Select Your Role

+
+
+ + +
+ + +
+
+
+ ); +} \ 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"); });