diff --git a/auth.js b/auth.js
new file mode 100644
index 0000000..909b7e7
--- /dev/null
+++ b/auth.js
@@ -0,0 +1,15 @@
+router.get("/logout", (req, res) => {
+ req.logout(err => {
+ if(err) return res.status(500).json({ message: "Logout failed" });
+ res.json({ message: "Logged out successfully" });
+ });
+});
+
+// Get logged-in user
+router.get("/current", (req, res) => {
+ if (req.isAuthenticated()) {
+ res.json({ user: { username: req.user.username } });
+ } else {
+ res.status(401).json({ user: null });
+ }
+});
diff --git a/backend/config/db.js b/backend/config/db.js
new file mode 100644
index 0000000..6311040
--- /dev/null
+++ b/backend/config/db.js
@@ -0,0 +1,16 @@
+const mongoose = require('mongoose');
+
+const connectDB = async () => {
+ try {
+ await mongoose.connect('mongodb://127.0.0.1:27017/githubTracker', {
+ useNewUrlParser: true,
+ useUnifiedTopology: true,
+ });
+ console.log('MongoDB connected');
+ } catch (err) {
+ console.error(err.message);
+ process.exit(1);
+ }
+};
+
+module.exports = connectDB;
diff --git a/backend/config/passport.js b/backend/config/passport.js
new file mode 100644
index 0000000..9f8c896
--- /dev/null
+++ b/backend/config/passport.js
@@ -0,0 +1,37 @@
+import passport from 'passport';
+import { Strategy as LocalStrategy } from 'passport-local';
+import bcrypt from 'bcryptjs';
+import User from '../models/User.js';
+
+passport.use(
+ new LocalStrategy(async (username, password, done) => {
+ try {
+ // Find user by username
+ const user = await User.findOne({ username });
+ if (!user) return done(null, false, { message: 'Incorrect username' });
+
+ // Compare passwords
+ const isMatch = await bcrypt.compare(password, user.password);
+ if (!isMatch) return done(null, false, { message: 'Incorrect password' });
+
+ return done(null, user);
+ } catch (err) {
+ return done(err);
+ }
+ })
+);
+
+// Serialize user into the session
+passport.serializeUser((user, done) => {
+ done(null, user.id);
+});
+
+// Deserialize user from the session
+passport.deserializeUser(async (id, done) => {
+ try {
+ const user = await User.findById(id).select('-password'); // Exclude password
+ done(null, user);
+ } catch (err) {
+ done(err);
+ }
+});
diff --git a/backend/config/passportConfig.js b/backend/config/passportConfig.js
index 842f50c..13a0a1b 100644
--- a/backend/config/passportConfig.js
+++ b/backend/config/passportConfig.js
@@ -2,14 +2,15 @@ const passport = require("passport");
const LocalStrategy = require('passport-local').Strategy;
const User = require("../models/User");
+// Local strategy for login using email
passport.use(
new LocalStrategy(
- { usernameField: "email" },
+ { usernameField: "email" }, // use email instead of username
async (email, password, done) => {
try {
- const user = await User.findOne( {email} );
+ const user = await User.findOne({ email });
if (!user) {
- return done(null, false, { message: 'Email is invalid '});
+ return done(null, false, { message: 'Email is invalid' });
}
const isMatch = await user.comparePassword(password);
@@ -17,8 +18,9 @@ passport.use(
return done(null, false, { message: 'Invalid password' });
}
+ // Return user object
return done(null, {
- id : user._id.toString(),
+ id: user._id.toString(),
username: user.username,
email: user.email
});
@@ -29,7 +31,7 @@ passport.use(
)
);
-// Serialize user (store user info in session)
+// Serialize user (store user id in session)
passport.serializeUser((user, done) => {
done(null, user.id);
});
diff --git a/backend/routes/auth.js b/backend/routes/auth.js
index e26c7a9..f0c2d76 100644
--- a/backend/routes/auth.js
+++ b/backend/routes/auth.js
@@ -1,42 +1,44 @@
-const express = require("express");
-const passport = require("passport");
-const User = require("../models/User");
+const express = require('express');
const router = express.Router();
+const passport = require('passport');
+const User = require('../models/User');
+
+// Signup
+router.post('/signup', async (req, res) => {
+ const { username, email, password } = req.body;
+ try {
+ const user = new User({ username, email, password });
+ await user.save();
+ res.status(201).json({ message: 'User created successfully' });
+ } catch (err) {
+ res.status(400).json({ error: err.message });
+ }
+});
-// Signup route
-router.post("/signup", async (req, res) => {
-
- const { username, email, password } = req.body;
-
- try {
- const existingUser = await User.findOne( {email} );
-
- if (existingUser)
- return res.status(400).json( {message: 'User already exists'} );
+// Login
+router.post('/login', (req, res, next) => {
+ passport.authenticate('local', (err, user, info) => {
+ if (err) return next(err);
+ if (!user) return res.status(400).json({ message: info.message });
- const newUser = new User( {username, email, password} );
- await newUser.save();
- res.status(201).json( {message: 'User created successfully'} );
- } catch (err) {
- res.status(500).json({ message: 'Error creating user', error: err.message });
- }
+ req.login(user, (err) => {
+ if (err) return next(err);
+ res.json({ message: 'Logged in successfully', user });
+ });
+ })(req, res, next);
});
-// Login route
-router.post("/login", passport.authenticate('local'), (req, res) => {
- res.status(200).json( { message: 'Login successful', user: req.user } );
+// Logout
+router.get('/logout', (req, res) => {
+ req.logout(() => {
+ res.json({ message: 'Logged out successfully' });
+ });
});
-// Logout route
-router.get("/logout", (req, res) => {
-
- req.logout((err) => {
-
- if (err)
- return res.status(500).json({ message: 'Logout failed', error: err.message });
- else
- res.status(200).json({ message: 'Logged out successfully' });
- });
+// Protected route (example)
+router.get('/profile', (req, res) => {
+ if (!req.isAuthenticated()) return res.status(401).json({ message: 'Not authenticated' });
+ res.json({ user: req.user });
});
module.exports = router;
diff --git a/client/.gitignore b/client/.gitignore
new file mode 100644
index 0000000..a547bf3
--- /dev/null
+++ b/client/.gitignore
@@ -0,0 +1,24 @@
+# Logs
+logs
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+pnpm-debug.log*
+lerna-debug.log*
+
+node_modules
+dist
+dist-ssr
+*.local
+
+# Editor directories and files
+.vscode/*
+!.vscode/extensions.json
+.idea
+.DS_Store
+*.suo
+*.ntvs*
+*.njsproj
+*.sln
+*.sw?
diff --git a/client/README.md b/client/README.md
new file mode 100644
index 0000000..7059a96
--- /dev/null
+++ b/client/README.md
@@ -0,0 +1,12 @@
+# React + Vite
+
+This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
+
+Currently, two official plugins are available:
+
+- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Babel](https://babeljs.io/) for Fast Refresh
+- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh
+
+## Expanding the ESLint configuration
+
+If you are developing a production application, we recommend using TypeScript with type-aware lint rules enabled. Check out the [TS template](https://github.com/vitejs/vite/tree/main/packages/create-vite/template-react-ts) for information on how to integrate TypeScript and [`typescript-eslint`](https://typescript-eslint.io) in your project.
diff --git a/client/eslint.config.js b/client/eslint.config.js
new file mode 100644
index 0000000..cee1e2c
--- /dev/null
+++ b/client/eslint.config.js
@@ -0,0 +1,29 @@
+import js from '@eslint/js'
+import globals from 'globals'
+import reactHooks from 'eslint-plugin-react-hooks'
+import reactRefresh from 'eslint-plugin-react-refresh'
+import { defineConfig, globalIgnores } from 'eslint/config'
+
+export default defineConfig([
+ globalIgnores(['dist']),
+ {
+ files: ['**/*.{js,jsx}'],
+ extends: [
+ js.configs.recommended,
+ reactHooks.configs['recommended-latest'],
+ reactRefresh.configs.vite,
+ ],
+ languageOptions: {
+ ecmaVersion: 2020,
+ globals: globals.browser,
+ parserOptions: {
+ ecmaVersion: 'latest',
+ ecmaFeatures: { jsx: true },
+ sourceType: 'module',
+ },
+ },
+ rules: {
+ 'no-unused-vars': ['error', { varsIgnorePattern: '^[A-Z_]' }],
+ },
+ },
+])
diff --git a/client/index.html b/client/index.html
new file mode 100644
index 0000000..0c589ec
--- /dev/null
+++ b/client/index.html
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+ Vite + React
+
+
+
+
+
+
diff --git a/client/package.json b/client/package.json
new file mode 100644
index 0000000..8d688cf
--- /dev/null
+++ b/client/package.json
@@ -0,0 +1,29 @@
+{
+ "name": "client",
+ "private": true,
+ "version": "0.0.0",
+ "type": "module",
+ "scripts": {
+ "dev": "vite",
+ "build": "vite build",
+ "lint": "eslint .",
+ "preview": "vite preview"
+ },
+ "dependencies": {
+ "axios": "^1.11.0",
+ "react": "^19.1.1",
+ "react-dom": "^19.1.1",
+ "react-router-dom": "^7.8.2"
+ },
+ "devDependencies": {
+ "@eslint/js": "^9.33.0",
+ "@types/react": "^19.1.10",
+ "@types/react-dom": "^19.1.7",
+ "@vitejs/plugin-react": "^5.0.0",
+ "eslint": "^9.33.0",
+ "eslint-plugin-react-hooks": "^5.2.0",
+ "eslint-plugin-react-refresh": "^0.4.20",
+ "globals": "^16.3.0",
+ "vite": "^7.1.2"
+ }
+}
diff --git a/client/public/vite.svg b/client/public/vite.svg
new file mode 100644
index 0000000..e7b8dfb
--- /dev/null
+++ b/client/public/vite.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/client/src/App.css b/client/src/App.css
new file mode 100644
index 0000000..b9d355d
--- /dev/null
+++ b/client/src/App.css
@@ -0,0 +1,42 @@
+#root {
+ max-width: 1280px;
+ margin: 0 auto;
+ padding: 2rem;
+ text-align: center;
+}
+
+.logo {
+ height: 6em;
+ padding: 1.5em;
+ will-change: filter;
+ transition: filter 300ms;
+}
+.logo:hover {
+ filter: drop-shadow(0 0 2em #646cffaa);
+}
+.logo.react:hover {
+ filter: drop-shadow(0 0 2em #61dafbaa);
+}
+
+@keyframes logo-spin {
+ from {
+ transform: rotate(0deg);
+ }
+ to {
+ transform: rotate(360deg);
+ }
+}
+
+@media (prefers-reduced-motion: no-preference) {
+ a:nth-of-type(2) .logo {
+ animation: logo-spin infinite 20s linear;
+ }
+}
+
+.card {
+ padding: 2em;
+}
+
+.read-the-docs {
+ color: #888;
+}
diff --git a/client/src/App.jsx b/client/src/App.jsx
new file mode 100644
index 0000000..f67355a
--- /dev/null
+++ b/client/src/App.jsx
@@ -0,0 +1,35 @@
+import { useState } from 'react'
+import reactLogo from './assets/react.svg'
+import viteLogo from '/vite.svg'
+import './App.css'
+
+function App() {
+ const [count, setCount] = useState(0)
+
+ return (
+ <>
+
+ Vite + React
+
+
+
+ Edit src/App.jsx and save to test HMR
+
+
+
+ Click on the Vite and React logos to learn more
+
+ >
+ )
+}
+
+export default App
diff --git a/client/src/assets/react.svg b/client/src/assets/react.svg
new file mode 100644
index 0000000..6c87de9
--- /dev/null
+++ b/client/src/assets/react.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/client/src/index.css b/client/src/index.css
new file mode 100644
index 0000000..08a3ac9
--- /dev/null
+++ b/client/src/index.css
@@ -0,0 +1,68 @@
+:root {
+ font-family: system-ui, Avenir, Helvetica, Arial, sans-serif;
+ line-height: 1.5;
+ font-weight: 400;
+
+ color-scheme: light dark;
+ color: rgba(255, 255, 255, 0.87);
+ background-color: #242424;
+
+ font-synthesis: none;
+ text-rendering: optimizeLegibility;
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+}
+
+a {
+ font-weight: 500;
+ color: #646cff;
+ text-decoration: inherit;
+}
+a:hover {
+ color: #535bf2;
+}
+
+body {
+ margin: 0;
+ display: flex;
+ place-items: center;
+ min-width: 320px;
+ min-height: 100vh;
+}
+
+h1 {
+ font-size: 3.2em;
+ line-height: 1.1;
+}
+
+button {
+ border-radius: 8px;
+ border: 1px solid transparent;
+ padding: 0.6em 1.2em;
+ font-size: 1em;
+ font-weight: 500;
+ font-family: inherit;
+ background-color: #1a1a1a;
+ cursor: pointer;
+ transition: border-color 0.25s;
+}
+button:hover {
+ border-color: #646cff;
+}
+button:focus,
+button:focus-visible {
+ outline: 4px auto -webkit-focus-ring-color;
+}
+
+@media (prefers-color-scheme: light) {
+ :root {
+ color: #213547;
+ background-color: #ffffff;
+ }
+ a:hover {
+ color: #747bff;
+ }
+ button {
+ background-color: #f9f9f9;
+ }
+}
diff --git a/client/src/main.jsx b/client/src/main.jsx
new file mode 100644
index 0000000..b9a1a6d
--- /dev/null
+++ b/client/src/main.jsx
@@ -0,0 +1,10 @@
+import { StrictMode } from 'react'
+import { createRoot } from 'react-dom/client'
+import './index.css'
+import App from './App.jsx'
+
+createRoot(document.getElementById('root')).render(
+
+
+ ,
+)
diff --git a/client/vite.config.js b/client/vite.config.js
new file mode 100644
index 0000000..8b0f57b
--- /dev/null
+++ b/client/vite.config.js
@@ -0,0 +1,7 @@
+import { defineConfig } from 'vite'
+import react from '@vitejs/plugin-react'
+
+// https://vite.dev/config/
+export default defineConfig({
+ plugins: [react()],
+})
diff --git a/config/passport.js b/config/passport.js
new file mode 100644
index 0000000..2ce6848
--- /dev/null
+++ b/config/passport.js
@@ -0,0 +1,33 @@
+import passport from 'passport';
+import { Strategy as LocalStrategy } from 'passport-local';
+import bcrypt from 'bcryptjs';
+import User from '../models/User.js';
+
+export default function configurePassport() {
+ passport.use(
+ new LocalStrategy(async (username, password, done) => {
+ try {
+ const user = await User.findOne({ username });
+ if (!user) return done(null, false, { message: 'Incorrect username.' });
+
+ const isMatch = await bcrypt.compare(password, user.password);
+ if (!isMatch) return done(null, false, { message: 'Incorrect password.' });
+
+ return done(null, user);
+ } catch (err) {
+ return done(err);
+ }
+ })
+ );
+
+ passport.serializeUser((user, done) => done(null, user.id));
+
+ passport.deserializeUser(async (id, done) => {
+ try {
+ const user = await User.findById(id);
+ done(null, user);
+ } catch (err) {
+ done(err, null);
+ }
+ });
+}
diff --git a/github-tracker-auth/index.js b/github-tracker-auth/index.js
new file mode 100644
index 0000000..3ac3e42
--- /dev/null
+++ b/github-tracker-auth/index.js
@@ -0,0 +1,54 @@
+const express = require("express");
+const bcrypt = require("bcryptjs");
+const jwt = require("jsonwebtoken");
+
+const app = express();
+app.use(express.json());
+
+const users = []; // Temporary in-memory storage
+
+// 🔑 Register route
+app.post("/register", async (req, res) => {
+ const { username, password } = req.body;
+
+ // check if user exists
+ const existingUser = users.find(u => u.username === username);
+ if (existingUser) {
+ return res.status(400).json({ message: "User already exists" });
+ }
+
+ // hash password
+ const hashedPassword = await bcrypt.hash(password, 10);
+ users.push({ username, password: hashedPassword });
+ res.json({ message: "User registered successfully 🚀" });
+});
+
+// 🔑 Login route
+app.post("/login", async (req, res) => {
+ const { username, password } = req.body;
+
+ const user = users.find(u => u.username === username);
+ if (!user) return res.status(400).json({ message: "Invalid credentials" });
+
+ const isMatch = await bcrypt.compare(password, user.password);
+ if (!isMatch) return res.status(400).json({ message: "Invalid credentials" });
+
+ // generate token
+ const token = jwt.sign({ username }, "secretKey123", { expiresIn: "1h" });
+ res.json({ message: "Login successful 🎉", token });
+});
+
+// Protected route
+app.get("/profile", (req, res) => {
+ const authHeader = req.headers["authorization"];
+ if (!authHeader) return res.status(401).json({ message: "No token provided" });
+
+ const token = authHeader.split(" ")[1];
+ jwt.verify(token, "secretKey123", (err, decoded) => {
+ if (err) return res.status(403).json({ message: "Invalid token" });
+
+ res.json({ message: `Welcome ${decoded.username} 🎉`, user: decoded });
+ });
+});
+
+app.listen(3000, () => console.log("Server is running 🚀"));
diff --git a/github-tracker-auth/package.json b/github-tracker-auth/package.json
new file mode 100644
index 0000000..08a686e
--- /dev/null
+++ b/github-tracker-auth/package.json
@@ -0,0 +1,19 @@
+{
+ "name": "github-tracker-auth",
+ "version": "1.0.0",
+ "description": "",
+ "main": "index.js",
+ "scripts": {
+ "test": "echo \"Error: no test specified\" && exit 1"
+ },
+ "keywords": [],
+ "author": "",
+ "license": "ISC",
+ "dependencies": {
+ "bcryptjs": "^3.0.2",
+ "express": "^5.1.0",
+ "express-session": "^1.18.2",
+ "passport": "^0.7.0",
+ "passport-local": "^1.0.0"
+ }
+}
diff --git a/middleware/auth.js b/middleware/auth.js
new file mode 100644
index 0000000..28f3675
--- /dev/null
+++ b/middleware/auth.js
@@ -0,0 +1,13 @@
+export function ensureAuthenticated(req, res, next) {
+ if (req.isAuthenticated()) {
+ return next();
+ }
+ res.status(401).json({ msg: 'You must be logged in to access this route' });
+}
+
+export function forwardAuthenticated(req, res, next) {
+ if (!req.isAuthenticated()) {
+ return next();
+ }
+ res.status(403).json({ msg: 'You are already logged in' });
+}
diff --git a/models/User.js b/models/User.js
new file mode 100644
index 0000000..a194b85
--- /dev/null
+++ b/models/User.js
@@ -0,0 +1,9 @@
+// models/User.js
+import mongoose from 'mongoose';
+
+const userSchema = new mongoose.Schema({
+ username: { type: String, required: true, unique: true },
+ password: { type: String, required: true },
+});
+
+export default mongoose.model('User', userSchema);
diff --git a/package.json b/package.json
index f2d89f5..d8a14ee 100644
--- a/package.json
+++ b/package.json
@@ -1,56 +1,40 @@
{
- "name": "my-react-tailwind-app",
- "private": true,
- "version": "0.0.0",
+ "name": "github-tracker-server",
+ "version": "1.0.0",
+ "description": "Backend for GitHub Tracker",
+ "main": "server.js",
"type": "module",
"scripts": {
- "dev": "vite --host",
- "build": "vite build",
- "lint": "eslint .",
- "preview": "vite preview",
- "docker:dev": "docker compose --profile dev up --build",
- "docker:prod": "docker compose --profile prod up -d --build"
+ "start": "node server.js",
+ "dev": "nodemon server.js",
+ "test": "jasmine"
},
"dependencies": {
- "@emotion/react": "^11.11.3",
- "@emotion/styled": "^11.11.0",
- "@mui/icons-material": "^5.15.6",
- "@mui/material": "^5.15.6",
- "@primer/octicons-react": "^19.15.5",
- "@vitejs/plugin-react": "^4.3.3",
- "axios": "^1.7.7",
- "framer-motion": "^12.23.12",
- "lucide-react": "^0.525.0",
- "octokit": "^4.0.2",
- "postcss": "^8.4.47",
- "react": "^18.3.1",
- "react-dom": "^18.3.1",
- "react-hot-toast": "^2.4.1",
- "react-icons": "^5.3.0",
- "react-router-dom": "^6.28.0",
- "tailwindcss": "^3.4.14"
- },
- "devDependencies": {
- "@eslint/js": "^9.13.0",
- "@types/jasmine": "^5.1.8",
- "@types/node": "^22.10.1",
- "@types/react": "^18.3.23",
- "@types/react-dom": "^18.3.7",
- "@types/react-redux": "^7.1.34",
- "@types/react-router-dom": "^5.3.3",
- "@vitejs/plugin-react-swc": "^3.5.0",
- "autoprefixer": "^10.4.20",
"bcryptjs": "^3.0.2",
- "eslint": "^9.13.0",
- "eslint-plugin-react": "^7.37.2",
- "eslint-plugin-react-hooks": "^5.0.0",
- "eslint-plugin-react-refresh": "^0.4.14",
+ "body-parser": "^1.20.2",
+ "connect-flash": "^0.1.1",
+ "cors": "^2.8.5",
+ "dotenv": "^16.3.1",
+ "express": "^4.18.2",
"express-session": "^1.18.2",
- "globals": "^15.11.0",
- "jasmine": "^5.9.0",
+ "mongoose": "^7.5.0",
"passport": "^0.7.0",
- "passport-local": "^1.0.0",
+ "passport-local": "^1.0.0"
+ },
+ "devDependencies": {
+ "jasmine": "^5.9.0",
"supertest": "^7.1.4",
- "vite": "^5.4.10"
- }
+ "nodemon": "^3.0.1"
+ },
+ "repository": {
+ "type": "git",
+ "url": "git+https://github.com/gaurivvv/github_tracker.git"
+ },
+ "keywords": [],
+ "author": "",
+ "license": "ISC",
+ "bugs": {
+ "url": "https://github.com/gaurivvv/github_tracker/issues"
+ },
+ "homepage": "https://github.com/gaurivvv/github_tracker#readme"
}
diff --git a/routes/auth.js b/routes/auth.js
new file mode 100644
index 0000000..69538f0
--- /dev/null
+++ b/routes/auth.js
@@ -0,0 +1,55 @@
+// routes/auth.js
+import express from 'express';
+import bcrypt from 'bcryptjs';
+import passport from 'passport';
+import User from '../models/User.js';
+
+const router = express.Router();
+
+// ===== Signup Route =====
+router.post('/signup', async (req, res) => {
+ const { username, password } = req.body;
+
+ if (!username || !password) {
+ return res.status(400).json({ msg: 'Please enter all fields' });
+ }
+
+ try {
+ const existingUser = await User.findOne({ username });
+ if (existingUser) {
+ return res.status(400).json({ msg: 'Username already exists' });
+ }
+
+ const hashedPassword = await bcrypt.hash(password, 10);
+ const newUser = new User({ username, password: hashedPassword });
+ await newUser.save();
+
+ res.status(201).json({ msg: 'User registered successfully' });
+ } catch (err) {
+ res.status(500).json({ error: err.message });
+ }
+});
+
+// ===== Login Route =====
+router.post('/login', (req, res, next) => {
+ passport.authenticate('local', (err, user, info) => {
+ if (err) return next(err);
+ if (!user) return res.status(400).json({ msg: info.message });
+
+ req.logIn(user, (err) => {
+ if (err) return next(err);
+ res.json({ msg: 'Logged in successfully', user: { username: user.username } });
+ });
+ })(req, res, next);
+});
+
+// ===== Logout Route =====
+router.get('/logout', (req, res) => {
+ req.logout((err) => {
+ if (err) return res.status(500).json({ error: err.message });
+ res.json({ msg: 'Logged out successfully' });
+ });
+});
+
+// Export router as default
+export default router;
diff --git a/server.js b/server.js
new file mode 100644
index 0000000..70b311b
--- /dev/null
+++ b/server.js
@@ -0,0 +1,47 @@
+import express from 'express';
+import mongoose from 'mongoose';
+import session from 'express-session';
+import passport from 'passport';
+import cors from 'cors';
+import dotenv from 'dotenv';
+
+import authRoutes from './routes/auth.js';
+import './config/passport.js'; // make sure passport config exports nothing; it just sets up strategies
+
+dotenv.config();
+
+const app = express();
+
+// Middleware
+app.use(cors());
+app.use(express.json());
+app.use(express.urlencoded({ extended: true }));
+
+// Sessions
+app.use(
+ session({
+ secret: process.env.SESSION_SECRET || 'defaultsecret',
+ resave: false,
+ saveUninitialized: false,
+ })
+);
+
+// Passport middleware
+app.use(passport.initialize());
+app.use(passport.session());
+
+// Routes
+app.use('/api/auth', authRoutes);
+
+// Connect to MongoDB
+mongoose
+ .connect(process.env.MONGO_URI, {
+ useNewUrlParser: true,
+ useUnifiedTopology: true,
+ })
+ .then(() => console.log('MongoDB connected'))
+ .catch(err => console.log('MongoDB connection error:', err));
+
+// Start server
+const PORT = process.env.PORT || 5000;
+app.listen(PORT, () => console.log(`Server running on port ${PORT}`));
diff --git a/server/server.js b/server/server.js
new file mode 100644
index 0000000..b45eb01
--- /dev/null
+++ b/server/server.js
@@ -0,0 +1,56 @@
+// server.js
+import express from 'express';
+import mongoose from 'mongoose';
+import dotenv from 'dotenv';
+import session from 'express-session';
+import passport from 'passport';
+import cors from 'cors';
+import bodyParser from 'body-parser';
+
+import authRoutes from './routes/auth.js'; // Auth routes
+import configurePassport from './config/passport.js'; // Passport config
+
+// Load environment variables
+dotenv.config();
+
+// Initialize Express app
+const app = express();
+
+// Middleware
+app.use(cors());
+app.use(bodyParser.json());
+app.use(bodyParser.urlencoded({ extended: true }));
+
+// Session configuration
+app.use(
+ session({
+ secret: process.env.SESSION_SECRET || 'secretkey',
+ resave: false,
+ saveUninitialized: false,
+ })
+);
+
+// Passport configuration
+configurePassport();
+app.use(passport.initialize());
+app.use(passport.session());
+
+// Routes
+app.use('/api/auth', authRoutes);
+
+// MongoDB connection
+const MONGO_URI = process.env.MONGO_URI || 'mongodb://127.0.0.1:27017/github_tracker';
+
+mongoose
+ .connect(MONGO_URI, {
+ useNewUrlParser: true,
+ useUnifiedTopology: true,
+ })
+ .then(() => console.log('MongoDB connected'))
+ .catch((err) => console.error('MongoDB connection error:', err));
+
+// Start server
+const PORT = process.env.PORT || 5000;
+app.listen(PORT, () => {
+ console.log(`Server running on http://localhost:${PORT}`);
+});
diff --git a/src/App.jsx b/src/App.jsx
new file mode 100644
index 0000000..1e7d384
--- /dev/null
+++ b/src/App.jsx
@@ -0,0 +1,26 @@
+import React from "react";
+import { Routes, Route, Navigate } from "react-router-dom";
+import Signup from "./pages/Signup";
+import Login from "./pages/Login";
+import Dashboard from "./pages/Dashboard";
+import React, { useEffect } from "react";
+import axios from "axios";
+
+function App() {
+ const [user, setUser] = React.useState(null); // store logged-in user
+ useEffect(() => {
+ axios.get("http://localhost:5000/api/auth/current", { withCredentials: true })
+ .then(res => setUser(res.data.user))
+ .catch(err => setUser(null));
+ }, []);
+ return (
+
+ : } />
+ } />
+ } />
+ : } />
+
+ );
+}
+
+export default App;
diff --git a/src/App.tsx b/src/App.tsx
index b00eba8..bd6aa09 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -2,7 +2,7 @@ import Navbar from "./components/Navbar";
import Footer from "./components/Footer";
import ScrollProgressBar from "./components/ScrollProgressBar";
import { Toaster } from "react-hot-toast";
-import Router from "./Routes/Router";
+import Router from "./routes/Router";
import ThemeWrapper from "./context/ThemeContext";
function App() {
diff --git a/src/Routes/Router.tsx b/src/Routes/Router.tsx
index 40a7861..fde5e22 100644
--- a/src/Routes/Router.tsx
+++ b/src/Routes/Router.tsx
@@ -1,8 +1,8 @@
import { Routes, Route } from "react-router-dom";
import Tracker from "../pages/Tracker/Tracker.tsx";
-import About from "../pages/About/About";
-import Contact from "../pages/Contact/Contact";
-import Contributors from "../pages/Contributors/Contributors";
+import About from "../pages/About/About.tsx";
+import Contact from "../pages/Contact/Contact.tsx";
+import Contributors from "../pages/Contributors/Contributors.tsx";
import Signup from "../pages/Signup/Signup.tsx";
import Login from "../pages/Login/Login.tsx";
import ContributorProfile from "../pages/ContributorProfile/ContributorProfile.tsx";
diff --git a/src/Routes/auth.js b/src/Routes/auth.js
new file mode 100644
index 0000000..d95564a
--- /dev/null
+++ b/src/Routes/auth.js
@@ -0,0 +1,49 @@
+import express from 'express';
+import bcrypt from 'bcryptjs';
+import passport from 'passport';
+import User from '../models/User.js';
+
+const router = express.Router();
+
+// Signup
+router.post('/signup', async (req, res) => {
+ const { username, password } = req.body;
+
+ if (!username || !password) return res.status(400).json({ msg: 'Please enter all fields' });
+
+ try {
+ const existingUser = await User.findOne({ username });
+ if (existingUser) return res.status(400).json({ msg: 'Username already exists' });
+
+ const hashedPassword = await bcrypt.hash(password, 10);
+ const newUser = new User({ username, password: hashedPassword });
+ await newUser.save();
+
+ res.status(201).json({ msg: 'User registered successfully' });
+ } catch (err) {
+ res.status(500).json({ error: err.message });
+ }
+});
+
+// Login
+router.post('/login', (req, res, next) => {
+ passport.authenticate('local', (err, user, info) => {
+ if (err) return next(err);
+ if (!user) return res.status(400).json({ msg: info.message });
+
+ req.logIn(user, err => {
+ if (err) return next(err);
+ res.json({ msg: 'Logged in successfully', user: { username: user.username } });
+ });
+ })(req, res, next);
+});
+
+// Logout
+router.get('/logout', (req, res) => {
+ req.logout(err => {
+ if (err) return res.status(500).json({ error: err.message });
+ res.json({ msg: 'Logged out successfully' });
+ });
+});
+
+export default router;
diff --git a/src/Signup.jsx b/src/Signup.jsx
new file mode 100644
index 0000000..0bb39d2
--- /dev/null
+++ b/src/Signup.jsx
@@ -0,0 +1,29 @@
+import { useState } from 'react';
+import axios from 'axios';
+import { useNavigate } from 'react-router-dom';
+
+export default function Signup() {
+ const [username, setUsername] = useState('');
+ const [password, setPassword] = useState('');
+ const navigate = useNavigate();
+
+ const handleSignup = async (e) => {
+ e.preventDefault();
+ try {
+ const res = await axios.post('http://localhost:5000/auth/signup', { username, password }, { withCredentials: true });
+ alert(res.data.msg);
+ navigate('/login');
+ } catch (err) {
+ alert(err.response?.data?.msg || 'Signup failed');
+ }
+ };
+
+ return (
+
+ );
+}
diff --git a/src/dashboard.jsx b/src/dashboard.jsx
new file mode 100644
index 0000000..7775dd3
--- /dev/null
+++ b/src/dashboard.jsx
@@ -0,0 +1,36 @@
+import React, { useEffect, useState } from "react";
+import axios from "axios";
+import { useNavigate } from "react-router-dom";
+
+const Dashboard = ({ user, setUser }) => {
+ const [message, setMessage] = useState("");
+ const navigate = useNavigate();
+
+ useEffect(() => {
+ axios.get("http://localhost:5000/api/auth/dashboard", { withCredentials: true })
+ .then(res => setMessage(res.data.message))
+ .catch(err => setMessage(err.response.data.message));
+ }, []);
+
+ const handleLogout = async () => {
+ try {
+ await axios.get("http://localhost:5000/api/auth/logout", { withCredentials: true });
+ setUser(null); // clear user in state
+ navigate("/login");
+ } catch (err) {
+ alert("Logout failed");
+ }
+ };
+
+ return (
+
+
Dashboard
+
{message}
+
+
+ );
+};
+
+export default Dashboard;
diff --git a/src/login.jsx b/src/login.jsx
new file mode 100644
index 0000000..9f2ba00
--- /dev/null
+++ b/src/login.jsx
@@ -0,0 +1,29 @@
+import { useState } from 'react';
+import axios from 'axios';
+import { useNavigate } from 'react-router-dom';
+
+export default function Login() {
+ const [username, setUsername] = useState('');
+ const [password, setPassword] = useState('');
+ const navigate = useNavigate();
+
+ const handleLogin = async (e) => {
+ e.preventDefault();
+ try {
+ const res = await axios.post('http://localhost:5000/auth/login', { username, password }, { withCredentials: true });
+ alert(res.data.msg);
+ navigate('/dashboard');
+ } catch (err) {
+ alert(err.response?.data?.msg || 'Login failed');
+ }
+ };
+
+ return (
+
+ );
+}
diff --git a/src/main.tsx b/src/main.tsx
index 4c5b79d..0495981 100644
--- a/src/main.tsx
+++ b/src/main.tsx
@@ -1,16 +1,20 @@
-import { StrictMode } from "react";
-import { createRoot } from "react-dom/client";
-import App from "./App.tsx";
-import "./index.css";
-import { BrowserRouter } from "react-router-dom";
-import ThemeWrapper from "./context/ThemeContext.tsx";
+import React from 'react';
+import ReactDOM from 'react-dom/client';
+import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
+import App from './App';
+import Login from './pages/Login';
+import Signup from './pages/Signup';
+import Dashboard from './pages/Dashboard';
-createRoot(document.getElementById("root")!).render(
-
-
-
-
-
-
-
-);
\ No newline at end of file
+ReactDOM.createRoot(document.getElementById('root')).render(
+
+
+
+ } />
+ } />
+ } />
+ } />
+
+
+
+);
diff --git a/tailwind.config.js b/tailwind.config.js
index 4cdd0f6..76034e5 100644
--- a/tailwind.config.js
+++ b/tailwind.config.js
@@ -1,13 +1,11 @@
/** @type {import('tailwindcss').Config} */
-module.exports = {
- darkMode: 'class',
- content: [
- "./index.html", // For any HTML files in the root
- "./src/**/*.{js,jsx,ts,tsx}", // For all JS/JSX/TS/TSX files inside src folder
- ],
- theme: {
- extend: {},
- },
- plugins: [],
- }
-
\ No newline at end of file
+export default {
+ content: [
+ "./index.html",
+ "./src/**/*.{js,jsx,ts,tsx}"
+ ],
+ theme: {
+ extend: {},
+ },
+ plugins: [],
+}