Skip to content

Commit 540e9f4

Browse files
Merge branch 'main' into fix/scroll-to-top
2 parents 8063f23 + eec69f2 commit 540e9f4

29 files changed

Lines changed: 1508 additions & 437 deletions

File tree

backend/.env.example

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
# Server
2+
PORT=5000
3+
NODE_ENV=development
4+
5+
# MongoDB
6+
MONGO_URI=mongodb://127.0.0.1:27017/github_tracker
7+
8+
# Session
9+
SESSION_SECRET=your_session_secret_here
10+
11+
# CORS — comma-separated list of allowed frontend origins
12+
# In production, set this to your actual frontend URL(s).
13+
# If not set, defaults to http://localhost:5173
14+
ALLOWED_ORIGINS=http://localhost:5173

backend/config/passportConfig.js

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ passport.use(
77
{ usernameField: "email" },
88
async (email, password, done) => {
99
try {
10-
const user = await User.findOne( {email} );
10+
const user = await User.findOne( {email} ).select("+password");;
1111
if (!user) {
1212
return done(null, false, { message: 'Email is invalid '});
1313
}
@@ -20,7 +20,8 @@ passport.use(
2020
return done(null, {
2121
id : user._id.toString(),
2222
username: user.username,
23-
email: user.email
23+
email: user.email,
24+
token: user.token
2425
});
2526
} catch (err) {
2627
return done(err);
@@ -38,7 +39,10 @@ passport.serializeUser((user, done) => {
3839
passport.deserializeUser(async (id, done) => {
3940
try {
4041
const user = await User.findById(id);
41-
done(null, user);
42+
if (!user) {
43+
return done(null, false);
44+
}
45+
done(null,user);
4246
} catch (err) {
4347
done(err, null);
4448
}

backend/models/User.js

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,16 @@ const UserSchema = new mongoose.Schema({
1616
type: String,
1717
required: true,
1818
},
19+
token: {
20+
type: String,
21+
unique: true,
22+
sparse: true,
23+
},
1924
});
2025

2126
// ✅ FIXED: no next()
22-
UserSchema.pre('save', async function () {
23-
if (!this.isModified('password')) return;
27+
UserSchema.pre("save", async function () {
28+
if (!this.isModified("password")) return;
2429

2530
const salt = await bcrypt.genSalt(10);
2631
this.password = await bcrypt.hash(this.password, salt);
@@ -31,4 +36,5 @@ UserSchema.methods.comparePassword = async function (enteredPassword) {
3136
return bcrypt.compare(enteredPassword, this.password);
3237
};
3338

34-
module.exports = mongoose.model("User", UserSchema);
39+
module.exports = mongoose.model("User", UserSchema);
40+

backend/routes/auth.js

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,24 @@ router.post("/login", validateRequest(loginSchema), passport.authenticate('local
3535
res.status(200).json( { message: 'Login successful', user: req.user } );
3636
});
3737

38+
// Save GitHub token route
39+
router.post("/token", async (req, res) => {
40+
if (!req.isAuthenticated()) {
41+
return res.status(401).json({ message: 'Not authenticated' });
42+
}
43+
const { token } = req.body;
44+
if (!token) {
45+
return res.status(400).json({ message: 'Token is required' });
46+
}
47+
try {
48+
await User.findByIdAndUpdate(req.user._id, { token });
49+
req.user.token = token;
50+
res.status(200).json({ success: true, message: 'Token saved successfully' });
51+
} catch (err) {
52+
res.status(500).json({ message: 'Error saving token', error: err.message });
53+
}
54+
});
55+
3856
// Logout route
3957
router.get("/logout", (req, res) => {
4058

backend/server.js

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,19 @@ const logger = require('./logger');
1313

1414
const app = express();
1515

16-
// CORS configuration
17-
const allowedOrigins = ['http://localhost:5173', 'https://github-spy.etlify.app'];
16+
// CORS configuration — allowed origins are read from the ALLOWED_ORIGINS env var
17+
// (comma-separated). Falls back to localhost for local development.
18+
const parsedOrigins = process.env.ALLOWED_ORIGINS
19+
? process.env.ALLOWED_ORIGINS.split(',').map(origin => origin.trim()).filter(Boolean)
20+
: [];
21+
const allowedOrigins = parsedOrigins.length > 0 ? parsedOrigins : ['http://localhost:5173'];
22+
1823
app.use(cors({
1924
origin: function (origin, callback) {
25+
// Allow requests with no origin (e.g. server-to-server, curl, mobile apps)
2026
if (!origin || allowedOrigins.indexOf(origin) !== -1) {
2127
callback(null, true);
22-
} else{
28+
} else {
2329
callback(new Error('Blocked by CORS policy'));
2430
}
2531
},

src/App.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { Toaster } from "react-hot-toast";
66
import Router from "./Routes/Router";
77
import ScrollToTop from "./components/ScrollToTop";
88

9-
const FULLSCREEN_ROUTES = ["/signup", "/login"];
9+
const FULLSCREEN_ROUTES = ["/signup", "/login", "/enterToken"];
1010

1111
function App() {
1212
const location = useLocation();
@@ -20,7 +20,7 @@ function App() {
2020

2121
{!isFullscreen && <Navbar />}
2222

23-
<main className={`flex justify-center items-center ${isFullscreen ? "flex-1" : "flex-grow bg-gray-50 dark:bg-gray-800"}`}>
23+
<main className={`${isFullscreen ? "flex flex-1 justify-center items-center" : "flex-grow bg-gray-50 dark:bg-gray-800"}`}>
2424
<Router />
2525
</main>
2626

src/Routes/Router.tsx

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
import { Routes, Route } from "react-router-dom";
2+
import PrivacyPolicy from "../pages/PrivacyPolicy/PrivacyPolicy";
3+
import TermsAndConditions from "../pages/TermsAndConditions/TermsAndConditions";
24
import Tracker from "../pages/Tracker/Tracker.tsx";
35
import About from "../pages/About/About";
46
import Contact from "../pages/Contact/Contact";
@@ -7,13 +9,17 @@ import Signup from "../pages/Signup/Signup.tsx";
79
import Login from "../pages/Login/Login.tsx";
810
import ContributorProfile from "../pages/ContributorProfile/ContributorProfile.tsx";
911
import Home from "../pages/Home/Home.tsx";
10-
import Activity from "../pages/Activity.tsx";
11-
import PrivacyPolicy from "../pages/Privacy/PrivacyPolicy.tsx"; // ✅ Updated import path to match your new folder structure
12+
import Activity from "../pages/Activity.tsx";
13+
import PrivacyPolicy from "../pages/Privacy/PrivacyPolicy.tsx";
14+
import SetToken from "../components/SetToken.tsx";
15+
import Profile from "../pages/Profile/Profile.tsx";
1216

1317
const Router = () => {
1418
return (
1519
<Routes>
1620
<Route path="/" element={<Home />} />
21+
<Route path="/privacy-policy" element={<PrivacyPolicy />} />
22+
<Route path="/terms-and-conditions" element={<TermsAndConditions />} />
1723
<Route path="/track" element={<Tracker />} />
1824
<Route path="/signup" element={<Signup />} />
1925
<Route path="/login" element={<Login />} />
@@ -22,9 +28,9 @@ const Router = () => {
2228
<Route path="/contributors" element={<Contributors />} />
2329
<Route path="/contributor/:username" element={<ContributorProfile />} />
2430
<Route path="/activity" element={<Activity />} />
25-
26-
{/* Privacy Policy page route */}
2731
<Route path="/privacy" element={<PrivacyPolicy />} />
32+
<Route path="/enterToken" element={<SetToken />} />
33+
<Route path="/profile" element={<Profile />} />
2834
</Routes>
2935
);
3036
};

src/components/Backtotop.tsx

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import React, { useEffect, useState } from "react";
2+
import { ArrowUp } from "lucide-react";
3+
4+
const BackToTopButton: React.FC = () => {
5+
const [isVisible, setIsVisible] = useState(false);
6+
7+
useEffect(() => {
8+
const toggleVisibility = () => {
9+
if (window.scrollY > 300) {
10+
setIsVisible(true);
11+
} else {
12+
setIsVisible(false);
13+
}
14+
};
15+
16+
window.addEventListener("scroll", toggleVisibility);
17+
18+
return () => {
19+
window.removeEventListener("scroll", toggleVisibility);
20+
};
21+
}, []);
22+
23+
const scrollToTop = () => {
24+
window.scrollTo({
25+
top: 0,
26+
behavior: "smooth",
27+
});
28+
};
29+
30+
return (
31+
<>
32+
{isVisible && (
33+
<button
34+
onClick={scrollToTop}
35+
aria-label="Back to top"
36+
className="fixed bottom-6 right-6 z-50 flex h-12 w-12 items-center justify-center rounded-full bg-green-500 text-white shadow-lg transition-all duration-300 hover:scale-110 hover:bg-green-600"
37+
>
38+
<ArrowUp size={22} />
39+
</button>
40+
)}
41+
</>
42+
);
43+
};
44+
45+
export default BackToTopButton;

0 commit comments

Comments
 (0)