Welcome back, Learner! 👋
+You're doing great on your open source journey. Keep it up!
+ +0% of the roadmap completed
+0
+Guides Finished
+5
+Guides in Progress
+Novice
+Contribution Rank
+diff --git a/backend/.env.example b/backend/.env.example index 08be54b6..33affa53 100644 --- a/backend/.env.example +++ b/backend/.env.example @@ -1,4 +1,15 @@ PORT=5000 MONGO_URI=mongodb://localhost:27017/os-compass +JWT_SECRET=your_jwt_secret_here +SESSION_SECRET=your_session_secret_here +GITHUB_CLIENT_ID=your_github_client_id +GITHUB_CLIENT_SECRET=your_github_client_secret +GITHUB_CALLBACK_URL=http://localhost:5000/api/auth/github/callback +FRONTEND_URL=http://localhost:5500 +EMAIL_HOST=smtp.mailtrap.io +EMAIL_PORT=2525 +EMAIL_USER=your_user +EMAIL_PASS=your_pass +EMAIL_FROM=noreply@opensource-compass.org GEMINI_API_KEY= -GEMINI_MODEL=gemini-2.5-flash +GEMINI_MODEL=gemini-2.0-flash diff --git a/backend/config/passport.js b/backend/config/passport.js new file mode 100644 index 00000000..59c5dee9 --- /dev/null +++ b/backend/config/passport.js @@ -0,0 +1,63 @@ +import passport from "passport"; +import { Strategy as GitHubStrategy } from "passport-github2"; +import User from "../models/User.js"; +import dotenv from "dotenv"; + +dotenv.config(); + +passport.use( + new GitHubStrategy( + { + clientID: process.env.GITHUB_CLIENT_ID, + clientSecret: process.env.GITHUB_CLIENT_SECRET, + callbackURL: process.env.GITHUB_CALLBACK_URL || "http://localhost:5000/api/auth/github/callback", + }, + async (accessToken, refreshToken, profile, done) => { + try { + let user = await User.findOne({ githubId: profile.id }); + + if (!user) { + // If user doesn't exist by githubId, check if one exists by email + const email = profile.emails && profile.emails[0] ? profile.emails[0].value : null; + + if (email) { + user = await User.findOne({ email }); + } + + if (user) { + // Update existing user with githubId + user.githubId = profile.id; + await user.save(); + } else { + // Create new user + user = await User.create({ + name: profile.displayName || profile.username, + email: email || `${profile.username}@github.com`, + githubId: profile.id, + password: Math.random().toString(36).slice(-8), // Dummy password + }); + } + } + + return done(null, user); + } catch (error) { + return done(error, null); + } + } + ) +); + +passport.serializeUser((user, done) => { + done(null, user.id); +}); + +passport.deserializeUser(async (id, done) => { + try { + const user = await User.findById(id); + done(null, user); + } catch (error) { + done(error, null); + } +}); + +export default passport; diff --git a/backend/controllers/authController.js b/backend/controllers/authController.js index 71df9430..5654d517 100644 --- a/backend/controllers/authController.js +++ b/backend/controllers/authController.js @@ -31,17 +31,22 @@ export const registerUser = async (req, res) => { }); // Send Welcome Email - await sendEmail({ - to: email, - subject: "Welcome to AlgoAI 🚀", - html: ` -
Welcome to AlgoAI.
-You can now start using AI tools.
-— Team AlgoAI
- `, - }); + try { + await sendEmail({ + to: email, + subject: "Welcome to OpenSource Compass 🚀", + html: ` +Welcome to OpenSource Compass.
+You can now track your progress and personalize your learning journey.
+— The OpenSource Compass Team
+ `, + }); + } catch (error) { + console.error("Welcome email failed to send:", error); + // Continue even if email fails - don't block registration + } res.status(201).json({ success: true, @@ -74,7 +79,7 @@ export const loginUser = async (req, res) => { { expiresIn: "1d" } ); - + res.cookie("token", token, { httpOnly: true, secure: process.env.NODE_ENV === "production", @@ -108,6 +113,48 @@ export const logoutUser = (req, res) => { export const getUserProfile = async (req, res) => { - const user = await User.findById(req.userId).select("-password"); - res.json({ success: true, user }); + // User is already attached to req.user by protect middleware + res.json({ success: true, user: req.user }); +}; + +export const trackGuideCompletion = async (req, res) => { + const { guideId } = req.body; + if (!guideId) return res.status(400).json({ message: "Guide ID required" }); + + try { + const user = await User.findById(req.user._id); + if (!user) return res.status(404).json({ message: "User not found" }); + + // Check if already completed + const isAlreadyCompleted = user.completedGuides.some((g) => g.guideId === guideId); + if (isAlreadyCompleted) { + return res.status(200).json({ success: true, message: "Guide already completed", user }); + } + + user.completedGuides.push({ guideId, completedAt: new Date() }); + await user.save(); + + res.json({ success: true, message: "Guide progress tracked", user }); + } catch (error) { + res.status(500).json({ message: "Server error" }); + } +}; + +export const updateUserProfile = async (req, res) => { + const { name, password } = req.body; + try { + const user = await User.findById(req.user._id); + if (!user) return res.status(404).json({ message: "User not found" }); + + if (name) user.name = name; + if (password) { + const hashedPassword = await bcrypt.hash(password, 10); + user.password = hashedPassword; + } + + await user.save(); + res.json({ success: true, message: "Profile updated", user: { id: user._id, name: user.name, email: user.email } }); + } catch (error) { + res.status(500).json({ message: "Server error" }); + } }; diff --git a/backend/models/User.js b/backend/models/User.js index 3b38bef0..672fc336 100644 --- a/backend/models/User.js +++ b/backend/models/User.js @@ -21,6 +21,13 @@ const UserSchema = new mongoose.Schema( minlength: [6, "Password must be at least 6 characters"], }, + // GITHUB AUTH + githubId: { + type: String, + unique: true, + sparse: true, + }, + // NEW FIELDS FOR CONTRIBUTOR PROGRESS progress: { issuesSelected: { @@ -42,6 +49,14 @@ const UserSchema = new mongoose.Schema( }, }, + // TRACKING GUIDES AND MODULES + completedGuides: [ + { + guideId: { type: String, required: true }, + completedAt: { type: Date, default: Date.now }, + } + ], + // ONBOARDING LEVEL level: { type: String, diff --git a/backend/package-lock.json b/backend/package-lock.json index 7bab12b7..5dca7347 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -16,9 +16,12 @@ "cors": "^2.8.6", "dotenv": "^17.2.3", "express": "^5.2.1", + "express-session": "^1.19.0", "jsonwebtoken": "^9.0.3", "mongoose": "^9.1.1", - "nodemailer": "^7.0.13" + "nodemailer": "^7.0.13", + "passport": "^0.7.0", + "passport-github2": "^0.1.12" } }, "node_modules/@google/generative-ai": { @@ -84,6 +87,15 @@ "proxy-from-env": "^1.1.0" } }, + "node_modules/base64url": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/base64url/-/base64url-3.0.1.tgz", + "integrity": "sha512-ir1UPr3dkwexU7FdV8qBBbNDRUhMmIekYMFZfi+C/sLNnRESKPl23nB9b2pltqfOQNnGzsDdId90AEtG5tCx4A==", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/bcryptjs": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-3.0.3.tgz", @@ -446,6 +458,50 @@ "url": "https://opencollective.com/express" } }, + "node_modules/express-session": { + "version": "1.19.0", + "resolved": "https://registry.npmjs.org/express-session/-/express-session-1.19.0.tgz", + "integrity": "sha512-0csaMkGq+vaiZTmSMMGkfdCOabYv192VbytFypcvI0MANrp+4i/7yEkJ0sbAEhycQjntaKGzYfjfXQyVb7BHMA==", + "license": "MIT", + "dependencies": { + "cookie": "~0.7.2", + "cookie-signature": "~1.0.7", + "debug": "~2.6.9", + "depd": "~2.0.0", + "on-headers": "~1.1.0", + "parseurl": "~1.3.3", + "safe-buffer": "~5.2.1", + "uid-safe": "~2.1.5" + }, + "engines": { + "node": ">= 0.8.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/express-session/node_modules/cookie-signature": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.7.tgz", + "integrity": "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==", + "license": "MIT" + }, + "node_modules/express-session/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/express-session/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, "node_modules/finalhandler": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz", @@ -973,6 +1029,12 @@ "node": ">=6.0.0" } }, + "node_modules/oauth": { + "version": "0.10.2", + "resolved": "https://registry.npmjs.org/oauth/-/oauth-0.10.2.tgz", + "integrity": "sha512-JtFnB+8nxDEXgNyniwz573xxbKSOu3R8D40xQKqcjwJ2CDkYqUDI53o6IuzDJBx60Z8VKCm271+t8iFjakrl8Q==", + "license": "MIT" + }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -1006,6 +1068,15 @@ "node": ">= 0.8" } }, + "node_modules/on-headers": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.1.0.tgz", + "integrity": "sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -1024,6 +1095,63 @@ "node": ">= 0.8" } }, + "node_modules/passport": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/passport/-/passport-0.7.0.tgz", + "integrity": "sha512-cPLl+qZpSc+ireUvt+IzqbED1cHHkDoVYMo30jbJIdOOjQ1MQYZBPiNvmi8UM6lJuOpTPXJGZQk0DtC4y61MYQ==", + "license": "MIT", + "dependencies": { + "passport-strategy": "1.x.x", + "pause": "0.0.1", + "utils-merge": "^1.0.1" + }, + "engines": { + "node": ">= 0.4.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/jaredhanson" + } + }, + "node_modules/passport-github2": { + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/passport-github2/-/passport-github2-0.1.12.tgz", + "integrity": "sha512-3nPUCc7ttF/3HSP/k9sAXjz3SkGv5Nki84I05kSQPo01Jqq1NzJACgMblCK0fGcv9pKCG/KXU3AJRDGLqHLoIw==", + "dependencies": { + "passport-oauth2": "1.x.x" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/passport-oauth2": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/passport-oauth2/-/passport-oauth2-1.8.0.tgz", + "integrity": "sha512-cjsQbOrXIDE4P8nNb3FQRCCmJJ/utnFKEz2NX209f7KOHPoX18gF7gBzBbLLsj2/je4KrgiwLLGjf0lm9rtTBA==", + "license": "MIT", + "dependencies": { + "base64url": "3.x.x", + "oauth": "0.10.x", + "passport-strategy": "1.x.x", + "uid2": "0.0.x", + "utils-merge": "1.x.x" + }, + "engines": { + "node": ">= 0.4.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/jaredhanson" + } + }, + "node_modules/passport-strategy": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/passport-strategy/-/passport-strategy-1.0.0.tgz", + "integrity": "sha512-CB97UUvDKJde2V0KDWWB3lyf6PC3FaZP7YxZ2G8OAtn9p4HI9j9JLP9qjOGZFvyl8uwNT8qM+hGnz/n16NI7oA==", + "engines": { + "node": ">= 0.4.0" + } + }, "node_modules/path-to-regexp": { "version": "8.3.0", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.3.0.tgz", @@ -1034,6 +1162,11 @@ "url": "https://opencollective.com/express" } }, + "node_modules/pause": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/pause/-/pause-0.0.1.tgz", + "integrity": "sha512-KG8UEiEVkR3wGEb4m5yZkVCzigAD+cVEJck2CzYZO37ZGJfctvVptVO192MwrtPhzONn6go8ylnOdMhKqi4nfg==" + }, "node_modules/proxy-addr": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", @@ -1077,6 +1210,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/random-bytes": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/random-bytes/-/random-bytes-1.0.0.tgz", + "integrity": "sha512-iv7LhNVO047HzYR3InF6pUcUsPQiHTM1Qal51DcGSuZFBil1aBBWG5eHPNek7bvILMaYJ/8RU1e8w1AMdHmLQQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/range-parser": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", @@ -1337,6 +1479,24 @@ "node": ">= 0.6" } }, + "node_modules/uid-safe": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.5.tgz", + "integrity": "sha512-KPHm4VL5dDXKz01UuEd88Df+KzynaohSL9fBh096KWAxSKZQDI2uBrVqtvRM4rwrIrRRKsdLNML/lnaaVSRioA==", + "license": "MIT", + "dependencies": { + "random-bytes": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/uid2": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/uid2/-/uid2-0.0.4.tgz", + "integrity": "sha512-IevTus0SbGwQzYh3+fRsAMTVVPOoIVufzacXcHPmdlle1jUpq7BRL+mw3dgeLanvGZdwwbWhRV6XrcFNdBmjWA==", + "license": "MIT" + }, "node_modules/unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", @@ -1346,6 +1506,15 @@ "node": ">= 0.8" } }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "license": "MIT", + "engines": { + "node": ">= 0.4.0" + } + }, "node_modules/vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", diff --git a/backend/package.json b/backend/package.json index 9b30e1ea..13052d61 100644 --- a/backend/package.json +++ b/backend/package.json @@ -18,9 +18,12 @@ "cors": "^2.8.6", "dotenv": "^17.2.3", "express": "^5.2.1", + "express-session": "^1.19.0", "jsonwebtoken": "^9.0.3", "mongoose": "^9.1.1", - "nodemailer": "^7.0.13" + "nodemailer": "^7.0.13", + "passport": "^0.7.0", + "passport-github2": "^0.1.12" }, "description": "" } diff --git a/backend/routes/authRoutes.js b/backend/routes/authRoutes.js index e889d182..a89d9342 100644 --- a/backend/routes/authRoutes.js +++ b/backend/routes/authRoutes.js @@ -15,12 +15,15 @@ // router.get("/profile", protect, getUserProfile); // Protected route // export default router; -import express from "express"; +import passport from "passport"; +import jwt from "jsonwebtoken"; import { registerUser, loginUser, logoutUser, getUserProfile, + trackGuideCompletion, + updateUserProfile, } from "../controllers/authController.js"; import { protect } from "../middleware/authMiddleware.js"; @@ -31,4 +34,33 @@ router.post("/login", loginUser); router.post("/logout", logoutUser); router.get("/me", protect, getUserProfile); +// GitHub OAuth Routes +router.get("/github", passport.authenticate("github", { scope: ["user:email"] })); + +router.get( + "/github/callback", + passport.authenticate("github", { failureRedirect: "/login" }), + (req, res) => { + // Generate JWT for the user authenticated via GitHub + const token = jwt.sign( + { id: req.user._id }, + process.env.JWT_SECRET, + { expiresIn: "1d" } + ); + + res.cookie("token", token, { + httpOnly: true, + secure: process.env.NODE_ENV === "production", + sameSite: process.env.NODE_ENV === "production" ? "none" : "lax", + maxAge: 24 * 60 * 60 * 1000, + }); + + // Redirect to frontend dashboard or home + res.redirect(`${process.env.FRONTEND_URL || "http://localhost:5500"}/frontend/pages/dashboard.html`); + } +); + +// Progress Tracking Route +router.post("/track-guide", protect, trackGuideCompletion); + export default router; diff --git a/backend/server.js b/backend/server.js index c2514416..86d9125f 100644 --- a/backend/server.js +++ b/backend/server.js @@ -7,13 +7,29 @@ import contributorProgressRoutes from "./routes/contributorProgressRoutes.js"; import chatRoute from "./routes/chat.route.js"; import prRoutes from "./routes/prRoutes.js"; import issueRoutes from "./routes/issueRoutes.js"; +import session from "express-session"; +import passport from "./config/passport.js"; +import cookieParser from "cookie-parser"; dotenv.config(); const app = express(); // Middleware -app.use(cors()); +app.use(cors({ + origin: ["http://localhost:3000", "http://localhost:5500", "http://127.0.0.1:5500", "http://127.0.0.1:3000"], + credentials: true +})); app.use(express.json()); +app.use(cookieParser()); +app.use( + session({ + secret: process.env.SESSION_SECRET || "opensource-compass-secret", + resave: false, + saveUninitialized: false, + }) +); +app.use(passport.initialize()); +app.use(passport.session()); app.use("/api", chatRoute); app.use("/api/pr", prRoutes); app.use("/api/issue", issueRoutes); diff --git a/frontend/css/style.css b/frontend/css/style.css index 91207a62..fa850f7c 100644 --- a/frontend/css/style.css +++ b/frontend/css/style.css @@ -4,71 +4,86 @@ /* ========================= 1. UNIFIED THEME VARIABLES ========================= */ - :root { - /* Primary Palette - Light Mode */ - --bg-main: #fdf4e3; /* Warm Cream */ - --bg-alt: #ffffff; /* Pure White */ - --surface: rgba(255, 255, 255, 0.8); - - --text-high: #1b263b; /* Deep Navy */ - --text-mid: #5a5a5a; /* Gray */ - --text-inv: #ffffff; - - --gold-bright: #d4af37; - --gold-deep: #b8860b; - --gold-glow: rgba(212, 175, 55, 0.3); - - --border: rgba(212, 175, 55, 0.2); - --shadow: 0 8px 32px rgba(0, 0, 0, 0.1); - --glass: blur(12px) saturate(180%); +:root { + /* Primary Palette - Light Mode */ + --bg-main: #fdf4e3; + /* Warm Cream */ + --bg-alt: #ffffff; + /* Pure White */ + --surface: rgba(255, 255, 255, 0.8); + + --text-high: #1b263b; + /* Deep Navy */ + --text-mid: #5a5a5a; + /* Gray */ + --text-inv: #ffffff; + + --gold-bright: #d4af37; + --gold-deep: #b8860b; + --gold-glow: rgba(212, 175, 55, 0.3); + + --border: rgba(212, 175, 55, 0.2); + --shadow: 0 8px 32px rgba(0, 0, 0, 0.1); + --glass: blur(12px) saturate(180%); } body.dark-mode { - /* Primary Palette - Dark Mode */ - --bg-main: #0b1220; /* Deep Navy Background */ - --bg-alt: #16213e; /* Lighter Navy Surface */ - --surface: rgba(22, 33, 62, 0.8); - - --text-high: #fdf4e3; /* Warm Cream Text */ - --text-mid: #b9bfd1; /* Muted Slate */ - - --gold-bright: #e4c76a; - --gold-glow: rgba(228, 199, 106, 0.25); - - --border: rgba(228, 199, 106, 0.15); - --shadow: 0 12px 40px rgba(0, 0, 0, 0.6); + /* Primary Palette - Dark Mode */ + --bg-main: #0b1220; + /* Deep Navy Background */ + --bg-alt: #16213e; + /* Lighter Navy Surface */ + --surface: rgba(22, 33, 62, 0.8); + + --text-high: #fdf4e3; + /* Warm Cream Text */ + --text-mid: #b9bfd1; + /* Muted Slate */ + + --gold-bright: #e4c76a; + --gold-glow: rgba(228, 199, 106, 0.25); + + --border: rgba(228, 199, 106, 0.15); + --shadow: 0 12px 40px rgba(0, 0, 0, 0.6); } /* ========================= 2. GLOBAL VISIBILITY FIXES ========================= */ body { - background: var(--bg-main); - color: var(--text-mid); - transition: background 0.4s ease, color 0.4s ease; + background: var(--bg-main); + color: var(--text-mid); + transition: background 0.4s ease, color 0.4s ease; } -h1, h2, h3, h4 { - color: var(--text-high); +h1, +h2, +h3, +h4 { + color: var(--text-high); } /* ========================= 3. ADAPTIVE CARD & NESTING FIXES ========================= */ -.card, .why-card, .stat-card, .newsletter-container { - background: var(--surface) !important; - backdrop-filter: var(--glass); - -webkit-backdrop-filter: var(--glass); - border: 1px solid var(--border) !important; - box-shadow: var(--shadow); - color: var(--text-high) !important; - transition: transform 0.4s cubic-bezier(0.34, 1.56, 0.64, 1), box-shadow 0.4s ease; -} - -.card:hover, .why-card:hover { - transform: translateY(-10px); - box-shadow: 0 20px 40px var(--gold-glow); - border-color: var(--gold-bright) !important; +.card, +.why-card, +.stat-card, +.newsletter-container { + background: var(--surface) !important; + backdrop-filter: var(--glass); + -webkit-backdrop-filter: var(--glass); + border: 1px solid var(--border) !important; + box-shadow: var(--shadow); + color: var(--text-high) !important; + transition: transform 0.4s cubic-bezier(0.34, 1.56, 0.64, 1), box-shadow 0.4s ease; +} + +.card:hover, +.why-card:hover { + transform: translateY(-10px); + box-shadow: 0 20px 40px var(--gold-glow); + border-color: var(--gold-bright) !important; } /* ========================= @@ -77,45 +92,47 @@ h1, h2, h3, h4 { /* Newsletter adaptive colors */ .newsletter-form input { - background: var(--bg-alt) !important; - color: var(--text-high) !important; - border: 2px solid var(--border) !important; + background: var(--bg-alt) !important; + color: var(--text-high) !important; + border: 2px solid var(--border) !important; } /* Badge Glow (Visible in Dark Mode) */ .badge { - background: linear-gradient(135deg, var(--gold-bright), var(--gold-deep)); - box-shadow: 0 4px 15px var(--gold-glow); + background: linear-gradient(135deg, var(--gold-bright), var(--gold-deep)); + box-shadow: 0 4px 15px var(--gold-glow); } /* Icon visibility */ -.stat-icon i, .why-card-icon i { - color: var(--gold-bright); - filter: drop-shadow(0 0 8px var(--gold-glow)); +.stat-icon i, +.why-card-icon i { + color: var(--gold-bright); + filter: drop-shadow(0 0 8px var(--gold-glow)); } /* Navbar Adaptive Blur */ header { - background: var(--surface); - backdrop-filter: var(--glass); - border-bottom: 3px solid var(--gold-bright); + background: var(--surface); + backdrop-filter: var(--glass); + border-bottom: 3px solid var(--gold-bright); } /* ========================= 5. UTILITY & SCROLLBAR ========================= */ ::-webkit-scrollbar { - width: 10px; + width: 10px; } ::-webkit-scrollbar-track { - background: var(--bg-main); + background: var(--bg-main); } ::-webkit-scrollbar-thumb { - background: var(--gold-bright); - border-radius: 5px; + background: var(--gold-bright); + border-radius: 5px; } + * { margin: 0; padding: 0; @@ -140,6 +157,106 @@ body { } } +/* Auth & Profile Styles */ +.profile-dropdown { + position: relative; + display: flex; + align-items: center; + cursor: pointer; + margin-left: 1rem; +} + +.profile-icon { + width: 38px; + height: 38px; + background: linear-gradient(135deg, var(--gold-bright), var(--gold-deep)); + color: white; + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + font-weight: 700; + box-shadow: 0 4px 10px var(--gold-glow); +} + +.profile-menu { + position: absolute; + top: 50px; + right: 0; + background: var(--bg-alt); + border: 1px solid var(--border); + border-radius: 12px; + padding: 10px; + width: 200px; + box-shadow: var(--shadow); + display: none; + flex-direction: column; + z-index: 1001; +} + +.profile-dropdown:hover .profile-menu { + display: flex; +} + +.profile-menu-item { + padding: 10px; + color: var(--text-high); + text-decoration: none; + font-size: 0.9rem; + border-radius: 8px; + transition: background 0.2s; +} + +.profile-menu-item:hover { + background: rgba(212, 175, 55, 0.1); +} + +.track-guide-btn { + background: white; + border: 1px solid var(--gold-bright); + color: var(--gold-deep); + padding: 8px 16px; + border-radius: 20px; + cursor: pointer; + font-weight: 600; + transition: all 0.3s ease; +} + +.track-guide-btn:hover { + background: var(--gold-bright); + color: white; +} + +.track-guide-btn.completed-style { + background: #27ae60; + color: white; + border-color: #27ae60; + cursor: default; +} + +.btn-auth { + width: 100%; + padding: 1rem; + border-radius: 12px; + background: linear-gradient(135deg, var(--gold-bright), var(--gold-deep)); + color: white; + border: none; + font-weight: 700; + cursor: pointer; + transition: all 0.3s ease; +} + +.btn-auth:hover { + transform: translateY(-2px); + box-shadow: 0 5px 15px var(--gold-glow); +} + +.btn-small { + padding: 0.5rem 1rem; + font-size: 0.85rem; + border-radius: 20px; +} + /* ========================= THEME VARIABLES (CLASSIC) @@ -157,32 +274,52 @@ body { --shadow-main: 0 8px 32px rgb(0 0 0 / 12%); --shadow-hover: 0 20px 40px rgb(0 0 0 / 18%); --bounce-curve: cubic-bezier(0.34, 1.56, 0.64, 1); - --newsletter-bg: #fcfbf7; /* Light cream */ - --newsletter-card: #ffffff; /* Pure white */ - --newsletter-text-h: #1a1a1a; /* Deep navy/black */ - --newsletter-text-p: #555555; /* Gray */ - --newsletter-border: #e2d1a8; /* Golden beige */ - --newsletter-btn: #b59d5c; /* Primary gold */ + --newsletter-bg: #fcfbf7; + /* Light cream */ + --newsletter-card: #ffffff; + /* Pure white */ + --newsletter-text-h: #1a1a1a; + /* Deep navy/black */ + --newsletter-text-p: #555555; + /* Gray */ + --newsletter-border: #e2d1a8; + /* Golden beige */ + --newsletter-btn: #b59d5c; + /* Primary gold */ --newsletter-input-bg: #ffffff; - --programs-header-h3: #1b263b; /* Deep Navy */ - --programs-header-p: #5a5a5a; /* Muted Gray */ - --programs-bg: #ffffff; /* Pure White */ + --programs-header-h3: #1b263b; + /* Deep Navy */ + --programs-header-p: #5a5a5a; + /* Muted Gray */ + --programs-bg: #ffffff; + /* Pure White */ /* Light Mode - Stats */ /* Light Mode - Stats */ - --stats-bg: #fdf4e3; /* Warm Cream */ - --stats-card-bg: #ffffff; /* Pure White */ - --stats-text-h: #1b263b; /* Deep Navy */ - --stats-text-p: #5a5a5a; /* Charcoal Gray */ - --stats-card-border: #e2d1a8; /* Golden Beige */ + --stats-bg: #fdf4e3; + /* Warm Cream */ + --stats-card-bg: #ffffff; + /* Pure White */ + --stats-text-h: #1b263b; + /* Deep Navy */ + --stats-text-p: #5a5a5a; + /* Charcoal Gray */ + --stats-card-border: #e2d1a8; + /* Golden Beige */ --stats-shadow: rgba(0, 0, 0, 0.08); - --programs-bg: #ffffff; /* Pure White */ - --programs-text-h3: #1b263b; /* Deep Navy */ - --programs-text-p: #5a5a5a; /* Muted Gray */ - --programs-underline: #d4af37; /* Primary Gold */ + --programs-bg: #ffffff; + /* Pure White */ + --programs-text-h3: #1b263b; + /* Deep Navy */ + --programs-text-p: #5a5a5a; + /* Muted Gray */ + --programs-underline: #d4af37; + /* Primary Gold */ --video-section-bg: #ffffff; - --video-h3-color: #ffffff; /* Deep Navy */ - --video-eyebrow-color: #b8860b; /* Secondary Gold */ - + --video-h3-color: #ffffff; + /* Deep Navy */ + --video-eyebrow-color: #b8860b; + /* Secondary Gold */ + } body.dark-mode { @@ -204,46 +341,69 @@ body.dark-mode { --border-subtle: rgba(255, 255, 255, 0.08); /* Dark Mode Overrides */ - --newsletter-bg: #121212; /* Dark background */ - --newsletter-card: #1e1e1e; /* Slightly lighter dark card */ - --newsletter-text-h: #f0f0f0; /* Off-white */ - --newsletter-text-p: #b0b0b0; /* Light gray */ - --newsletter-border: #333333; /* Subtle dark border */ - --newsletter-input-bg: #2d2d2d; /* Dark input field */ + --newsletter-bg: #121212; + /* Dark background */ + --newsletter-card: #1e1e1e; + /* Slightly lighter dark card */ + --newsletter-text-h: #f0f0f0; + /* Off-white */ + --newsletter-text-p: #b0b0b0; + /* Light gray */ + --newsletter-border: #333333; + /* Subtle dark border */ + --newsletter-input-bg: #2d2d2d; + /* Dark input field */ /* Dark Mode Overrides */ - --programs-header-h3: #fdf4e3; /* Warm Cream / Off-white */ - --programs-header-p: #b9bfd1; /* Lighter Slate Gray */ + --programs-header-h3: #fdf4e3; + /* Warm Cream / Off-white */ + --programs-header-p: #b9bfd1; + /* Lighter Slate Gray */ --programs-bg: #0b1220; /* Dark Mode - Stats */ /* Dark Mode - Stats */ - --stats-bg: #d5e2b4; /* Solid Dark Background */ - --stats-card-bg: rgba(20, 26, 43, 0.95); /* Deep Navy Glass */ - --stats-text-h: #f5f4f0; /* Off-White Header */ - --stats-text-p: #b9bfd1; /* Muted Slate Text */ - --stats-card-border: rgba(228, 199, 106, 0.3); /* Softer Gold Border */ + --stats-bg: #d5e2b4; + /* Solid Dark Background */ + --stats-card-bg: rgba(20, 26, 43, 0.95); + /* Deep Navy Glass */ + --stats-text-h: #f5f4f0; + /* Off-White Header */ + --stats-text-p: #b9bfd1; + /* Muted Slate Text */ + --stats-card-border: rgba(228, 199, 106, 0.3); + /* Softer Gold Border */ --stats-shadow: rgba(0, 0, 0, 0.6); - --programs-bg: #0b1220; /* Deep Space Navy */ - --programs-text-h3: #fdf4e3; /* Warm Cream */ - --programs-text-p: #b9bfd1; /* Slate Blue Gray */ + --programs-bg: #0b1220; + /* Deep Space Navy */ + --programs-text-h3: #fdf4e3; + /* Warm Cream */ + --programs-text-p: #b9bfd1; + /* Slate Blue Gray */ --programs-underline: #e4c76a; - --video-section-bg: #0b1220; /* Deep Space Navy */ - --video-h3-color: #fdf4e3; /* Warm Cream */ - --video-eyebrow-color: #ffca28; /* Bright Gold */ + --video-section-bg: #0b1220; + /* Deep Space Navy */ + --video-h3-color: #fdf4e3; + /* Warm Cream */ + --video-eyebrow-color: #ffca28; + /* Bright Gold */ } + :root { /* Light Mode - Standard High Contrast */ - --h3-color: #1b263b; /* Deep Navy */ + --h3-color: #1b263b; + /* Deep Navy */ } body.dark-mode { /* Dark Mode - High Visibility */ - --h3-color: #fdf4e3; /* Warm Cream */ + --h3-color: #fdf4e3; + /* Warm Cream */ } /* Global H3 Application */ h3 { color: var(--h3-color); - transition: color 0.4s ease; /* Smooth theme switching */ + transition: color 0.4s ease; + /* Smooth theme switching */ } /* ========================= @@ -259,6 +419,7 @@ body { color: var(--charcoal-dark); padding-top: 90px; } + .video-section { padding: 60px 20px; text-align: center; @@ -293,6 +454,7 @@ body { background: linear-gradient(90deg, var(--primary-gold), var(--secondary-gold)); border-radius: 2px; } + .programs { background-color: var(--programs-bg); padding: 80px 6%; @@ -327,6 +489,7 @@ body { margin: 0 auto 3.5rem; transition: color 0.4s ease; } + .programs { background-color: var(--programs-bg); transition: background-color 0.4s ease; @@ -360,6 +523,7 @@ body { border-radius: 2px; box-shadow: 0 0 10px var(--gold-glow); } + .stats { background-color: var(--stats-bg); padding: 80px 6%; @@ -385,7 +549,8 @@ body { border-radius: 20px; text-align: center; transition: all 0.4s cubic-bezier(0.34, 1.56, 0.64, 1); - backdrop-filter: blur(10px); /* Adds premium glass feel in dark mode */ + backdrop-filter: blur(10px); + /* Adds premium glass feel in dark mode */ } .stat-card:hover { diff --git a/frontend/js/auth.js b/frontend/js/auth.js index 9a2611d3..d085b3eb 100644 --- a/frontend/js/auth.js +++ b/frontend/js/auth.js @@ -1,156 +1,192 @@ -// Authentication logic using localStorage +const API_URL = 'http://localhost:5000/api'; -// --- Event Listeners --- -document.addEventListener('DOMContentLoaded', function() { - updateProfileIcon(); // Initialize nav on every page load - +document.addEventListener('DOMContentLoaded', function () { + initAuth(); +}); + +async function initAuth() { + await checkAuthStatus(); + + // Attach form listeners if they exist const signupForm = document.getElementById('signupForm'); if (signupForm) signupForm.addEventListener('submit', handleSignup); const loginForm = document.getElementById('loginForm'); if (loginForm) loginForm.addEventListener('submit', handleLogin); -}); - -// --- Auth Handlers --- -function handleSignup(e) { - e.preventDefault(); - const name = document.getElementById('name').value.trim(); - const email = document.getElementById('email').value.trim(); - const password = document.getElementById('password').value; - const confirmPassword = document.getElementById('confirmPassword').value; - - clearErrors(); - let isValid = true; - - if (name.length < 2) { showError('nameError', 'Name must be at least 2 characters'); isValid = false; } - if (!isValidEmail(email)) { showError('emailError', 'Please enter a valid email address'); isValid = false; } - if (password.length < 6) { showError('passwordError', 'Password must be at least 6 characters'); isValid = false; } - if (password !== confirmPassword) { showError('confirmPasswordError', 'Passwords do not match'); isValid = false; } - - if (!isValid) return; - - const users = JSON.parse(localStorage.getItem('users') || '[]'); - if (users.find(user => user.email === email)) { - showError('emailError', 'An account with this email already exists'); - return; - } - - const newUser = { - id: Date.now().toString(), - name: name, - email: email, - password: password, - createdAt: new Date().toISOString() - }; - - users.push(newUser); - localStorage.setItem('users', JSON.stringify(users)); - localStorage.setItem('currentUser', JSON.stringify({ id: newUser.id, name: newUser.name, email: newUser.email })); - - alert('Account created successfully!'); - window.location.href = 'index.html'; } -function handleLogin(e) { - e.preventDefault(); - const email = document.getElementById('loginEmail').value.trim(); - const password = document.getElementById('loginPassword').value; - - clearErrors(); - const users = JSON.parse(localStorage.getItem('users') || '[]'); - const user = users.find(u => u.email === email && u.password === password); - - if (!user) { - showError('loginPasswordError', 'Invalid email or password'); - return; +async function checkAuthStatus() { + try { + const response = await fetch(`${API_URL}/auth/me`, { + headers: { 'Content-Type': 'application/json' }, + credentials: 'include' // Required to send cookies cross-origin + }); + + if (response.ok) { + const data = await response.json(); + localStorage.setItem('currentUser', JSON.stringify(data.user)); + updateUI(data.user); + } else { + localStorage.removeItem('currentUser'); + updateUI(null); + } + } catch (error) { + console.error('Auth check failed:', error); + updateUI(null); } - - localStorage.setItem('currentUser', JSON.stringify({ id: user.id, name: user.name, email: user.email })); - window.location.href = 'index.html'; } -// --- Profile Icon & Dropdown Logic --- -function updateProfileIcon() { - const nav = document.querySelector('nav'); - if (!nav) return; +function updateUI(user) { + const navLinks = document.querySelector('.nav-links'); + if (!navLinks) return; - const existingProfile = nav.querySelector('.profile-dropdown'); - if (existingProfile) existingProfile.remove(); + // Clean up existing auth elements + const authElements = navLinks.querySelectorAll('.auth-item'); + authElements.forEach(el => el.remove()); + + if (user) { + // User is logged in - add Dashboard link as first item + const dashboardLink = document.createElement('a'); + dashboardLink.href = getCorrectPath('frontend/pages/dashboard.html'); + dashboardLink.textContent = 'Dashboard'; + dashboardLink.className = 'auth-item'; + + // Find first link to insert before + const firstLink = navLinks.querySelector('a'); + if (firstLink) { + navLinks.insertBefore(dashboardLink, firstLink); + } else { + navLinks.appendChild(dashboardLink); + } - const currentUser = getCurrentUser(); - - if (currentUser) { const profileDropdown = document.createElement('div'); - profileDropdown.className = 'profile-dropdown'; - - const profileIcon = document.createElement('div'); - profileIcon.className = 'profile-icon'; - profileIcon.textContent = currentUser.name.charAt(0).toUpperCase(); - profileIcon.onclick = toggleProfileMenu; - - const profileMenu = document.createElement('div'); - profileMenu.className = 'profile-menu'; - profileMenu.id = 'profileMenu'; - - profileMenu.innerHTML = ` - - - 👤 Profile Settings -You're doing great on your open source journey. Keep it up!
+ +0% of the roadmap completed
+Guides Finished
+Guides in Progress
+Contribution Rank
+Open source is a way to collaborate on projects where the code is publicly available. Contributing helps you learn, gain experience, and give back to the community.
- -Start by understanding Git version control and creating repositories on GitHub. This is the foundation for contributing to open source projects.
-Contribute to beginner-friendly projects to build confidence and experience. Look for tags like "good first issue."
-Learn how to read issues, use labels, and understand project workflows to know where you can contribute effectively.
-Submit your first PR to fix a bug or add a small feature. This helps you practice collaboration and the contribution process.
-Create your own copy of the repository to work on without affecting the original project.
-Download the repository to your computer using Git to start making changes.
-Use a descriptive branch name to organize your work and keep main safe.
-Edit the code, add features or fixes, and commit your changes with clear messages.
-Send your branch to GitHub and create a pull request to propose your changes.
-Respond to comments, make any requested edits, and merge once maintainers approve.
-Mastering GitHub issues and workflows helps you contribute effectively and communicate clearly with project maintainers.
- -Learn to identify beginner-friendly issues or areas of the project that match your skills and interest.
-Recognize labels like "good first issue" or "help wanted" to know what the project expects and where you can contribute.
-Communicate clearly in issue threads to ask questions, offer help, or provide updates on your work.
-Understand the review process, how maintainers provide feedback, and the workflow for merging your pull requests.
+Open source is a way to collaborate on projects where the code is publicly available. Contributing helps you + learn, gain experience, and give back to the community.
+ + +Start by understanding Git version control and creating repositories on GitHub. This is the foundation for + contributing to open source projects.
+Contribute to beginner-friendly projects to build confidence and experience. Look for tags like "good first + issue."
+Learn how to read issues, use labels, and understand project workflows to know where you can contribute + effectively.
+Submit your first PR to fix a bug or add a small feature. This helps you practice collaboration and the + contribution process.
+Create your own copy of the repository to work on without affecting the original project.
+Download the repository to your computer using Git to start making changes.
+Use a descriptive branch name to organize your work and keep main safe.
+Edit the code, add features or fixes, and commit your changes with clear messages.
+Send your branch to GitHub and create a pull request to propose your changes.
+Respond to comments, make any requested edits, and merge once maintainers approve.
+Mastering GitHub issues and workflows helps you contribute effectively and communicate clearly with project + maintainers.
+ +Learn to identify beginner-friendly issues or areas of the project that match your skills and interest. +
+Recognize labels like "good first issue" or "help wanted" to know what the project expects and where you + can contribute.
+Communicate clearly in issue threads to ask questions, offer help, or provide updates on your work.
+Understand the review process, how maintainers provide feedback, and the workflow for merging your pull + requests.
+Follow these best practices to make your contributions effective, professional, and easy for maintainers to + review.
+ +Meaningful commit messages make it easier for others to understand your changes.
+Adhering to project guidelines ensures your PR meets expectations and saves time for maintainers.
+Keep your code consistent with the project's style and formatting conventions.
+Be polite, constructive, and respectful when discussing changes or providing feedback.
++ Review open and closed issues to avoid duplicate efforts and to understand + current discussions or decisions. +
++ Discuss major changes with maintainers beforehand to ensure alignment with + the project's direction. +
++ Small, well-scoped pull requests are easier to review, test, and merge. +
++ Make sure README files or guides reflect any changes introduced by your + contribution. +
+Follow these best practices to make your contributions effective, professional, and easy for maintainers to review.
- -Meaningful commit messages make it easier for others to understand your changes.
-Adhering to project guidelines ensures your PR meets expectations and saves time for maintainers.
-Keep your code consistent with the project's style and formatting conventions.
-Be polite, constructive, and respectful when discussing changes or providing feedback.
-- Review open and closed issues to avoid duplicate efforts and to understand - current discussions or decisions. -
-- Discuss major changes with maintainers beforehand to ensure alignment with - the project's direction. -
-- Small, well-scoped pull requests are easier to review, test, and merge. -
-- Make sure README files or guides reflect any changes introduced by your - contribution. -
-Not reading the README
-Skipping contributing guidelines
-Making large or unclear pull requests
-Not testing changes before submitting
-Not reading the README
+Opening issues or pull requests without checking for duplicates
-Ignoring or misunderstanding maintainers' feedback
-Making large or breaking changes without prior discussion
-Poor or unclear pull request descriptions
-Skipping contributing guidelines
+ +Making large or unclear pull requests
+Not testing changes before submitting
+Opening issues or pull requests without checking for duplicates
+Ignoring or misunderstanding maintainers' feedback
+Making large or breaking changes without prior discussion
+Poor or unclear pull request descriptions
+