diff --git a/backend/package-lock.json b/backend/package-lock.json index 05e6086..2af2553 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -220,7 +220,6 @@ "resolved": "https://registry.npmjs.org/@types/node/-/node-24.9.1.tgz", "integrity": "sha512-QoiaXANRkSXK6p0Duvt56W208du4P9Uye9hWLWgGMDTEoKPhuenzNcC4vGUmrNkiOKTlIrBoyNQYNpSwfEZXSg==", "license": "MIT", - "peer": true, "dependencies": { "undici-types": "~7.16.0" } @@ -2501,7 +2500,6 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true, "license": "Apache-2.0", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" diff --git a/backend/src/controllers/authController.ts b/backend/src/controllers/authController.ts index bb61a2b..017496a 100644 --- a/backend/src/controllers/authController.ts +++ b/backend/src/controllers/authController.ts @@ -1,6 +1,6 @@ import type { Request, Response, NextFunction } from "express"; import bcrypt from "bcryptjs"; -import User, { type IUser } from "../models/userModel.js"; +import User, { type IUser } from "../models/userModel.js"; import { generateToken } from "../utils/generateToken.js"; import { userSchema, loginSchema } from "../utils/validateInputs.js"; import dotenv from "dotenv"; @@ -8,7 +8,7 @@ import dotenv from "dotenv"; dotenv.config(); const asTypedUser = (user: any): IUser & { _id: string } => user as IUser & { _id: string }; -//signup controller +// ✅ SIGNUP CONTROLLER export const registerUser = async (req: Request, res: Response, next: NextFunction) => { try { const parseResult = userSchema.safeParse(req.body); @@ -19,10 +19,15 @@ export const registerUser = async (req: Request, res: Response, next: NextFuncti }); } - const { name, email, password } = parseResult.data; + const { email, password } = parseResult.data; + + // ✅ Auto-derive name from email + const name = email.split("@")[0]; + const existingUser = await User.findOne({ email }); - if (existingUser) + if (existingUser) { return res.status(400).json({ success: false, message: "Email already registered" }); + } const hashedPassword = await bcrypt.hash(password, 10); const newUser = await User.create({ name, email, password: hashedPassword }); @@ -38,7 +43,7 @@ export const registerUser = async (req: Request, res: Response, next: NextFuncti } }; -//sign in controller +// ✅ LOGIN CONTROLLER (same as before) export const loginUser = async (req: Request, res: Response, next: NextFunction) => { try { const parseResult = loginSchema.safeParse(req.body); @@ -53,6 +58,7 @@ export const loginUser = async (req: Request, res: Response, next: NextFunction) const foundUser = await User.findOne({ email }); if (!foundUser) return res.status(400).json({ success: false, message: "Invalid credentials" }); + if (!foundUser.password || foundUser.password === "") { return res.status(400).json({ success: false, @@ -76,22 +82,27 @@ export const loginUser = async (req: Request, res: Response, next: NextFunction) } }; -//OAuth callback handler -export const oauthCallback = (req: Request & { user?: any }, res: Response) => { +// ✅ GET PROFILE CONTROLLER (unchanged) +export const getUserProfile = async (req: Request, res: Response) => { try { - const frontendUrl = process.env.FRONTEND_URL || "http://localhost:5173"; - const user = req.user as IUser & { _id: string } | undefined; + const user = await User.findById(req.userId).select("-password"); if (!user) { - return res.redirect(`${frontendUrl}/signin?error=oauth_failed`); + return res.status(404).json({ + success: false, + message: "User not found", + }); } - const token = generateToken(user._id.toString()); - const redirectUrl = `${frontendUrl}/oauth-success#token=${token}`; - - return res.redirect(redirectUrl); - } catch (err) { - const frontendUrl = process.env.FRONTEND_URL || "http://localhost:5173"; - return res.redirect(`${frontendUrl}/signin?error=server_error`); + res.status(200).json({ + success: true, + user, + }); + } catch (error) { + console.error(error); + res.status(500).json({ + success: false, + message: "Server error", + }); } }; diff --git a/backend/src/routes/authRoutes.ts b/backend/src/routes/authRoutes.ts index 5fb34c0..c56964a 100644 --- a/backend/src/routes/authRoutes.ts +++ b/backend/src/routes/authRoutes.ts @@ -1,11 +1,13 @@ import express from "express"; -import { registerUser, loginUser, oauthCallback } from "../controllers/authController.js"; +import { registerUser, loginUser, getUserProfile } from "../controllers/authController.js"; import passport from "passport"; +import { protect } from "../middleware/authMiddleware.js"; const router = express.Router(); router.post("/signup", registerUser); router.post("/signin", loginUser); +router.get("/me", protect, getUserProfile); // Google OAuth router.get( diff --git a/frontend/package-lock.json b/frontend/package-lock.json index af8281c..ae6eeec 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -16,6 +16,7 @@ "@radix-ui/react-tooltip": "^1.2.8", "@tailwindcss/vite": "^4.1.14", "@tanstack/react-query": "^5.90.3", + "axios": "^1.12.2", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "lucide-react": "^0.545.0", @@ -158,7 +159,6 @@ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.4.tgz", "integrity": "sha512-2BCOP7TN8M+gVDj7/ht3hsaO/B/n5oDbiAyyvnRlNOs+u1o+JWNYTQrmpuNp1/Wq2gcFrI01JAW+paEKDMx/CA==", "license": "MIT", - "peer": true, "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.3", @@ -1385,7 +1385,6 @@ "resolved": "https://registry.npmjs.org/@noble/ciphers/-/ciphers-1.3.0.tgz", "integrity": "sha512-2I0gnIVPtfnMw9ee9h1dJG7tp81+8Ob3OJb3Mv37rx5L40/b0i7djjCVvGOVqc9AEIQyvyu1i6ypKdFw8R8gQw==", "license": "MIT", - "peer": true, "engines": { "node": "^14.21.3 || >=16" }, @@ -1469,7 +1468,6 @@ "resolved": "https://registry.npmjs.org/@octokit/core/-/core-5.2.2.tgz", "integrity": "sha512-/g2d4sW9nUDJOMz3mabVQvOGhVa4e/BN/Um7yca9Bb2XTzPPnfTWHWQg+IsEYO7M3Vx+EXvaM/I2pJWIMun1bg==", "license": "MIT", - "peer": true, "dependencies": { "@octokit/auth-token": "^4.0.0", "@octokit/graphql": "^7.1.0", @@ -3021,7 +3019,6 @@ "integrity": "sha512-QoiaXANRkSXK6p0Duvt56W208du4P9Uye9hWLWgGMDTEoKPhuenzNcC4vGUmrNkiOKTlIrBoyNQYNpSwfEZXSg==", "devOptional": true, "license": "MIT", - "peer": true, "dependencies": { "undici-types": "~7.16.0" } @@ -3147,6 +3144,12 @@ "node": ">=4" } }, + "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/autoprefixer": { "version": "10.4.21", "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.21.tgz", @@ -3185,6 +3188,17 @@ "postcss": "^8.1.0" } }, + "node_modules/axios": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.12.2.tgz", + "integrity": "sha512-vMJzPewAlRyOgxV2dU0Cuz2O8zzzx9VYtbJOaBgXFeLc4IV/Eg50n4LowmehOOR61S8ZMpc2K5Sa7g6A4jfkUw==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.4", + "proxy-from-env": "^1.1.0" + } + }, "node_modules/baseline-browser-mapping": { "version": "2.8.18", "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.18.tgz", @@ -3306,7 +3320,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "baseline-browser-mapping": "^2.8.9", "caniuse-lite": "^1.0.30001746", @@ -3343,15 +3356,6 @@ "node": ">= 0.4" } }, - "node_modules/call-bind-apply-helpers/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/call-bound": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", @@ -3368,88 +3372,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/call-bound/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/call-bound/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/call-bound/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/call-bound/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/call-bound/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/call-bound/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/callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", @@ -3644,6 +3566,18 @@ "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==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/commander": { "version": "14.0.1", "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.1.tgz", @@ -3849,6 +3783,15 @@ "node": ">=0.10.0" } }, + "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/depd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", @@ -3908,18 +3851,6 @@ "node": ">= 0.4" } }, - "node_modules/dunder-proto/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/eciesjs": { "version": "0.4.16", "resolved": "https://registry.npmjs.org/eciesjs/-/eciesjs-0.4.16.tgz", @@ -4076,6 +4007,21 @@ "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.11", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.11.tgz", @@ -4408,6 +4354,63 @@ "node": ">= 0.8" } }, + "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/form-data/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/form-data/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/forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", @@ -4454,6 +4457,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", @@ -4463,6 +4475,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/get-stream": { "version": "9.0.1", "license": "MIT", @@ -4489,6 +4538,57 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "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/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/https-proxy-agent": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", @@ -5530,7 +5630,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", @@ -5606,6 +5705,12 @@ "node": ">= 0.10" } }, + "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", @@ -5726,7 +5831,6 @@ "resolved": "https://registry.npmjs.org/react/-/react-19.2.0.tgz", "integrity": "sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ==", "license": "MIT", - "peer": true, "engines": { "node": ">=0.10.0" } @@ -5736,7 +5840,6 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.0.tgz", "integrity": "sha512-UlbRu4cAiGaIewkPyiRGJk0imDN2T3JjieT6spoL2UeSf5od4n5LB/mQ4ejmxhCFT1tYe8IvaFulzynWovsEFQ==", "license": "MIT", - "peer": true, "dependencies": { "scheduler": "^0.27.0" }, @@ -6231,88 +6334,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/side-channel-map/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/side-channel-map/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/side-channel-map/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/side-channel-map/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/side-channel-map/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/side-channel-map/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/side-channel-weakmap": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", @@ -6332,88 +6353,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/side-channel-weakmap/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/side-channel-weakmap/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/side-channel-weakmap/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/side-channel-weakmap/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/side-channel-weakmap/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/side-channel-weakmap/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/signal-exit": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", @@ -6954,7 +6893,6 @@ "resolved": "https://registry.npmjs.org/vite/-/vite-7.1.11.tgz", "integrity": "sha512-uzcxnSDVjAopEUjljkWh8EIrg6tlzrjFUfMcR1EVsRDGwf/ccef0qQPRyOrROwhrTDaApueq+ja+KLPlzR/zdg==", "license": "MIT", - "peer": true, "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.5.0", @@ -7259,7 +7197,6 @@ "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", "license": "MIT", - "peer": true, "funding": { "url": "https://github.com/sponsors/colinhacks" } diff --git a/frontend/package.json b/frontend/package.json index 010b77d..115447e 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -19,6 +19,7 @@ "@radix-ui/react-tooltip": "^1.2.8", "@tailwindcss/vite": "^4.1.14", "@tanstack/react-query": "^5.90.3", + "axios": "^1.12.2", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "lucide-react": "^0.545.0", diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 08ef271..0562910 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -7,6 +7,7 @@ import Index from "./pages/Index.js"; import SignUp from "./pages/Signup.js"; import SignIn from "./pages/Signin.js"; import OAuthSuccess from "./pages/OAuthSuccess.js"; +import RoomActions from "./pages/RoomActions.js"; import "./index.css" const queryClient = new QueryClient(); @@ -20,6 +21,7 @@ const App = () => ( } /> } /> } /> + } /> } /> diff --git a/frontend/src/components/Header.tsx b/frontend/src/components/Header.tsx index 44fe861..b4b6503 100644 --- a/frontend/src/components/Header.tsx +++ b/frontend/src/components/Header.tsx @@ -1,21 +1,43 @@ import { useState, useEffect } from "react"; import { Button } from "../components/ui/button.js"; import { useNavigate } from "react-router-dom"; -import { Menu, X } from "lucide-react"; +import { Menu, X, PlusSquare, LogIn, LogOut } from "lucide-react"; +import axios from "axios"; function Header() { const [isScrolled, setIsScrolled] = useState(false); const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false); + const [user, setUser] = useState<{ _id: string; name: string } | null>(null); + const [isDropdownOpen, setIsDropdownOpen] = useState(false); const navigate = useNavigate(); + // Scroll effect useEffect(() => { - const handleScroll = () => { - setIsScrolled(window.scrollY > 20); - }; + const handleScroll = () => setIsScrolled(window.scrollY > 20); window.addEventListener("scroll", handleScroll); return () => window.removeEventListener("scroll", handleScroll); }, []); + // Fetch authenticated user + useEffect(() => { + const token = localStorage.getItem("token"); + if (!token) return; + + const fetchUser = async () => { + try { + const res = await axios.get("http://localhost:3000/api/auth/me", { + headers: { Authorization: `Bearer ${token}` }, + }); + setUser(res.data.user); + } catch { + setUser(null); + localStorage.removeItem("token"); // remove invalid token + } + }; + + fetchUser(); + }, []); + const scrollToSection = (id: string) => { const element = document.getElementById(id); if (element) { @@ -24,25 +46,36 @@ function Header() { } }; + // Navigate to a separate page which provides actions (join/create/logout) + const handleJoinNow = () => { + if (!user) { + navigate("/signin"); + } else { + navigate("/room-actions"); + } + }; + + const handleLogout = () => { + localStorage.removeItem("token"); + setUser(null); + setIsDropdownOpen(false); + navigate("/"); + }; + return (
diff --git a/frontend/src/components/Hero.tsx b/frontend/src/components/Hero.tsx index 227d165..8129e79 100644 --- a/frontend/src/components/Hero.tsx +++ b/frontend/src/components/Hero.tsx @@ -1,9 +1,39 @@ import { Button } from "./ui/button.js"; import { ArrowRight, Play } from "lucide-react"; import { useNavigate } from "react-router-dom"; +import axios from "axios"; +import { useState } from "react"; const Hero = () => { const navigate = useNavigate(); + const [loading, setLoading] = useState(false); + + const handleJoinNow = async () => { + const token = localStorage.getItem("token"); + if (!token) { + navigate("/signin"); + return; + } + + setLoading(true); + try { + const res = await axios.get("http://localhost:3000/api/auth/me", { + headers: { Authorization: `Bearer ${token}` }, + }); + + if (res?.data?.user) { + navigate("/room-actions"); + } else { + localStorage.removeItem("token"); + navigate("/signin"); + } + } catch (err) { + localStorage.removeItem("token"); + navigate("/signin"); + } finally { + setLoading(false); + } + }; return (
@@ -27,8 +57,8 @@ const Hero = () => {

- diff --git a/frontend/src/pages/RoomActions.tsx b/frontend/src/pages/RoomActions.tsx new file mode 100644 index 0000000..5dccac5 --- /dev/null +++ b/frontend/src/pages/RoomActions.tsx @@ -0,0 +1,77 @@ +import { useEffect, useState } from "react"; +import { useNavigate } from "react-router-dom"; +import { Button } from "../components/ui/button.js"; +import { PlusSquare, LogIn, LogOut } from "lucide-react"; +import axios from "axios"; + +export default function RoomActions() { + const [user, setUser] = useState<{ _id: string; name: string } | null>(null); + const [loading, setLoading] = useState(true); + const navigate = useNavigate(); + + useEffect(() => { + const token = localStorage.getItem("token"); + if (!token) { + navigate("/signin"); + return; + } + + const fetchUser = async () => { + try { + const res = await axios.get("http://localhost:3000/api/auth/me", { + headers: { Authorization: `Bearer ${token}` }, + }); + setUser(res.data.user); + } catch (err) { + // invalid token or user not found + localStorage.removeItem("token"); + navigate("/signin"); + } finally { + setLoading(false); + } + }; + + fetchUser(); + }, [navigate]); + + const handleCreate = () => navigate("/create-room"); + const handleJoin = () => navigate("/join-room"); + const handleLogout = () => { + localStorage.removeItem("token"); + navigate("/"); + }; + + if (loading) return
Loading...
; + + return ( +
+
+

Room Actions

+

Hello {user?.name ?? "user"}, choose an action:

+ +
+ + + + + +
+
+
+ ); +} diff --git a/frontend/src/pages/Signin.tsx b/frontend/src/pages/Signin.tsx index dc17796..151c257 100644 --- a/frontend/src/pages/Signin.tsx +++ b/frontend/src/pages/Signin.tsx @@ -1,37 +1,56 @@ import { useState } from "react"; import { useForm } from "react-hook-form"; -import { Link } from "react-router-dom"; -import { Button } from "../components/ui/button.js" -import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "../components/ui/card.js"; -import { InputField } from "../components/InputField.js"; -import { toast } from "../hooks/use-toast.js"; -import { mockLogin, SignInData } from "../lib/api.js"; +import { Link, useNavigate } from "react-router-dom"; +import axios from "axios"; +import { Button } from "../components/ui/button"; +import { + Card, + CardContent, + CardDescription, + CardFooter, + CardHeader, + CardTitle, +} from "../components/ui/card"; +import { InputField } from "../components/InputField"; +import { toast } from "../hooks/use-toast"; import { Loader2 } from "lucide-react"; -import SocialLogin from "../components/SocialLogin.js"; +import SocialLogin from "../components/SocialLogin"; + +interface SignInData { + email: string; + password: string; +} const SignIn = () => { const [isLoading, setIsLoading] = useState(false); - const { - register, - handleSubmit, - formState: { errors }, - reset, - } = useForm(); + const navigate = useNavigate(); + const { register, handleSubmit, formState: { errors }, reset } = useForm(); const onSubmit = async (data: SignInData) => { setIsLoading(true); try { - const response = await mockLogin(data); + const response = await axios.post( + "http://localhost:3000/api/auth/signin", + { email: data.email, password: data.password }, + { headers: { "Content-Type": "application/json" } } + ); + toast({ title: "Welcome back!", - description: response.message, + description: response.data.message || "Login successful", }); + + // store JWT token in localStorage + if (response.data.token) { + localStorage.setItem("token", response.data.token); + } + reset(); - // Navigate home page + navigate("/"); // redirect to home/dashboard } catch (error: any) { toast({ title: "Error", - description: error.message || "Login failed. Please try again.", + description: error.response?.data?.message || "Login failed. Please try again.", variant: "destructive", }); } finally { @@ -50,6 +69,7 @@ const SignIn = () => { Sign in to your account +
{ )} +
-
Or continue with
+
+ Or continue with +
diff --git a/frontend/src/pages/Signup.tsx b/frontend/src/pages/Signup.tsx index eab0769..6a4c6ff 100644 --- a/frontend/src/pages/Signup.tsx +++ b/frontend/src/pages/Signup.tsx @@ -1,11 +1,18 @@ import { useState } from "react"; import { useForm } from "react-hook-form"; -import { Link } from "react-router-dom"; +import { Link, useNavigate } from "react-router-dom"; +import axios from "axios"; import { Button } from "../components/ui/button"; -import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "../components/ui/card"; +import { + Card, + CardContent, + CardDescription, + CardFooter, + CardHeader, + CardTitle, +} from "../components/ui/card"; import { InputField } from "../components/InputField"; import { toast } from "../hooks/use-toast"; -import { mockRegister, SignUpData } from "../lib/api"; import { Loader2 } from "lucide-react"; interface SignUpFormData { @@ -16,6 +23,8 @@ interface SignUpFormData { const SignUp = () => { const [isLoading, setIsLoading] = useState(false); + const navigate = useNavigate(); + const { register, handleSubmit, @@ -27,18 +36,49 @@ const SignUp = () => { const password = watch("password"); const onSubmit = async (data: SignUpFormData) => { + if (data.password !== data.confirmPassword) { + toast({ + title: "Error", + description: "Passwords do not match", + variant: "destructive", + }); + return; + } + setIsLoading(true); try { - const response = await mockRegister(data); + // Derive name from email (part before @) + const name = data.email.split("@")[0]; + + const response = await axios.post( + "http://localhost:3000/api/auth/signup", + { + name, + email: data.email, + password: data.password, + }, + { + headers: { "Content-Type": "application/json" }, + } + ); + toast({ title: "Success!", - description: response.message, + description: response.data.message || "Registration successful", }); + + // Optional: save token for auto-login + if (response.data.token) { + localStorage.setItem("token", response.data.token); + } + reset(); + navigate("/signin"); } catch (error: any) { toast({ title: "Error", - description: error.message || "Registration failed. Please try again.", + description: + error.response?.data?.message || "Registration failed. Please try again.", variant: "destructive", }); } finally { @@ -57,6 +97,7 @@ const SignUp = () => { Create your account to get started +
{ placeholder="••••••••" {...register("confirmPassword", { required: "Please confirm your password", - validate: (value) => - value === password || "Passwords do not match", })} error={errors.confirmPassword?.message} />