Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
162 changes: 162 additions & 0 deletions Dashboard-backup.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
import React, { useState, useEffect } from "react";
import Sidebar from "./DashBoard/Sidebar";
import Topbar from "./DashBoard/Topbar";
import ProfileCard from "./DashBoard/ProfileCard";
import PlatformLinks from "./DashBoard/PlatformLinks";
import StreakCard from "./DashBoard/StreakCard";
import GoalsCard from "./DashBoard/GoalsCard";
import TimeSpentCard from "./DashBoard/TimeSpentCard";
import ActivityHeatmap from "./DashBoard/ActivityHeatMap";
import NotesCard from "./DashBoard/NotesCard";
import { useNavigate } from "react-router-dom";
import GitHubCard from "@/Components/GitHubCard";

export default function Dashboard() {
const [profile, setProfile] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
const [goals, setGoals] = useState([]);
const navigate = useNavigate();

useEffect(() => {
// Capture token issued by backend OAuth redirect: /dashboard?token=...
const params = new URLSearchParams(window.location.search);
const oauthToken = params.get("token");

if (oauthToken) {
try {
localStorage.setItem("token", oauthToken);
// Also store in sessionStorage for GitHub API calls (from your branch)
sessionStorage.setItem("github_token", oauthToken);
} catch (e) {
console.error("Failed to persist OAuth token:", e);
}

// Clean up URL after capturing token (avoid keeping token in address bar)
const cleanUrl = window.location.origin + window.location.pathname;
window.history.replaceState({}, document.title, cleanUrl);
}

const fetchProfile = async () => {
try {
const token = localStorage.getItem("token");

if (!token) {
navigate("/login");
setLoading(false);
return;
}

// For session auth, keeping both auth methods (your custom + main branch approach)
const res = await fetch(`${import.meta.env.VITE_API_URL}/api/profile`, {
headers: { "x-auth-token": token },
credentials: 'include', // Important for session-based auth (from your branch)
});

const data = await res.json();
if (!res.ok) {
throw new Error(data.errors?.[0]?.msg || "Failed to load profile");
}

// Use DevSync activity data or initialize empty array
if (!data.activity || !Array.isArray(data.activity)) {
data.activity = [];
}

setProfile(data);
setGoals(data.goals || []);
} catch (err) {
console.error("Error fetching profile:", err);
setError(err.message);
} finally {
setLoading(false);
}
};

fetchProfile();
}, [navigate]);

// Show loading state
if (loading) {
return (
<div className="flex items-center justify-center h-screen">
<div className="animate-spin rounded-full h-12 w-12 border-t-2 border-b-2 border-blue-500"></div>
</div>
);
}

// Show error state
if (error) {
return (
<div className="p-6 text-red-500">
<p>Error: {error}</p>
<button
onClick={() => window.location.reload()}
className="mt-4 px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600"
>
Try Again
</button>
</div>
);
}

// Show message if no profile data is available
if (!profile) {
return (
<div className="p-6">
<p>No profile data available. Please try logging in again.</p>
</div>
);
}

// Safely destructure with default values
const {
socialLinks = [],
streak = 0,
githubUsername = null,
timeSpent = "0 minutes",
activity = [],
notes = []
} = profile;

return (
<div className="flex flex-col h-screen">
<Topbar />
<div className="flex flex-1">
<Sidebar />
<main className="flex-1 p-6 bg-[#d1e4f3]">
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4 p-4">
{/* Row 1 */}
<ProfileCard user={profile} className="col-span-1" />
<PlatformLinks platforms={profile?.platforms || []} className="col-span-1" />
<StreakCard streak={streak} className="col-span-1" />

{/* GitHub Card (conditionally rendered) */}
{githubUsername ? (
<GitHubCard githubUsername={githubUsername} className="col-span-1" />
) : (
<div className="col-span-1 p-4 border rounded-lg shadow-sm bg-gray-100 text-gray-500 flex items-center justify-center">
GitHub profile not linked
</div>
)}

{/* Row 2: Goals, Time Spent, Notes */}
<GoalsCard goals={goals} onGoalsChange={setGoals} />
<TimeSpentCard time={timeSpent} />
<NotesCard
notes={notes}
onNotesChange={(updatedNotes) =>
setProfile({ ...profile, notes: updatedNotes })
}
/>

{/* Row 3: Activity heatmap full width */}
<div className="col-span-1 sm:col-span-2 lg:col-span-3">
<ActivityHeatmap activityData={activity} />
</div>
</div>
</main>
</div>
</div>
);
}
Binary file added backend/.gitignore
Binary file not shown.
186 changes: 153 additions & 33 deletions backend/config/passport.js
Original file line number Diff line number Diff line change
@@ -1,50 +1,170 @@
const passport = require("passport");
const GoogleStrategy = require("passport-google-oauth20").Strategy;
const GitHubStrategy = require("passport-github2").Strategy;
const User = require("../models/User");

console.log('Initializing Google OAuth strategy...');
console.log('Callback URL:', process.env.GOOGLE_CALLBACK_URL);
// Only use Google Strategy if credentials are provided
if (process.env.GOOGLE_CLIENT_ID && process.env.GOOGLE_CLIENT_SECRET) {
console.log('Initializing Google OAuth strategy...');
console.log('Callback URL:', process.env.GOOGLE_CALLBACK_URL);

passport.use(
new GoogleStrategy(
{
clientID: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
callbackURL: process.env.GOOGLE_CALLBACK_URL || "http://localhost:5000/auth/callback",
},
async (accessToken, refreshToken, profile, done) => {
console.log('Google profile received:', profile);
try {
let user = await User.findOne({ googleId: profile.id });

if (!user) {
console.log('Creating new user from Google profile');
user = new User({
passport.use(
new GoogleStrategy(
{
clientID: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
callbackURL: process.env.GOOGLE_CALLBACK_URL || "http://localhost:5000/auth/callback",
},
async (accessToken, refreshToken, profile, done) => {
console.log('Google profile received:', profile);
try {
// Simplified to avoid MongoDB dependency
const user = {
id: profile.id,
googleId: profile.id,
name: profile.displayName,
email: profile.emails && profile.emails[0] ? profile.emails[0].value : null,
});
await user.save();
isEmailVerified: true
};
return done(null, user);
} catch (err) {
return done(err, null);
}
}
)
);
}

return done(null, user);
} catch (err) {
return done(err, null);
// GitHub OAuth Strategy - Only use if credentials are provided
if (process.env.GITHUB_CLIENT_ID && process.env.GITHUB_CLIENT_SECRET) {
console.log("GitHub OAuth is configured with:");
console.log("- Client ID:", process.env.GITHUB_CLIENT_ID.substring(0, 5) + "...");
console.log("- Callback URL:", process.env.GITHUB_CALLBACK_URL);

passport.use(
new GitHubStrategy(
{
clientID: process.env.GITHUB_CLIENT_ID,
clientSecret: process.env.GITHUB_CLIENT_SECRET,
callbackURL: process.env.GITHUB_CALLBACK_URL || "/api/auth/github/callback",
scope: ["user:email", "read:user", "public_repo", "read:org", "user:follow"],
passReqToCallback: true
},
async (req, accessToken, refreshToken, profile, done) => {
try {
console.log("GitHub authentication callback received");
console.log("Profile:", JSON.stringify({
id: profile.id,
username: profile.username,
displayName: profile.displayName,
emails: profile.emails,
photos: profile.photos
}, null, 2));

// Save access token for API calls - make sure it's directly accessible
profile.accessToken = accessToken;

console.log("Received access token:", accessToken ? "Yes (token available)" : "No (token missing)");

// For the PR implementation, we're just returning the profile information
// No MongoDB interaction needed for implementing the GitHub authentication

// Try to fetch additional GitHub profile data
const fetchGithubData = async () => {
try {
// GitHub API - User endpoint
const userResponse = await fetch(`https://api.github.com/user/${profile.id}`, {
headers: {
'Accept': 'application/vnd.github.v3+json',
'Authorization': `token ${accessToken}`
}
});

if (userResponse.ok) {
const userData = await userResponse.json();
console.log("GitHub user data:", userData);
return userData;
}
return null;
} catch (error) {
console.error("Error fetching GitHub user data:", error);
return null;
}
};

// Try to get additional GitHub data
let githubData = null;
try {
githubData = await fetchGithubData();
} catch (error) {
console.error("Error in GitHub data fetch:", error);
}

const user = {
id: profile.id,
githubId: profile.id,
username: profile.username, // Save GitHub username for API calls
name: profile.displayName || profile.username,
email: profile.emails && profile.emails[0] ? profile.emails[0].value : null,
avatar: profile.photos && profile.photos[0] ? profile.photos[0].value : undefined,
// Store the access token explicitly
accessToken: accessToken,
isEmailVerified: true, // GitHub email is already verified
// Add GitHub-specific profile data
platforms: [
{
name: 'GitHub',
username: profile.username,
url: profile._json?.html_url || `https://github.com/${profile.username}`,
followers: githubData?.followers || 0,
following: githubData?.following || 0,
repos: githubData?.public_repos || 0
}
],
streak: 0,
timeSpent: "0 minutes",
notes: [],
activity: [],
goals: []
};

console.log("Created user object:", JSON.stringify(user, null, 2));
return done(null, user);
} catch (err) {
console.error("Error in GitHub strategy:", err);
return done(err, null);
}
}
}
)
);
)
);
}

// serialize + deserialize
// serialize + deserialize (improved to store full user object)
passport.serializeUser((user, done) => {
done(null, user.id);
console.log("Serializing user:", user.id || user.githubId || user.googleId);
// Store the whole user object instead of just the ID
// This avoids needing to retrieve the user from the database on every request

// Make sure we're preserving the access token
if (user.accessToken) {
console.log("Access token preserved in session");
} else {
console.log("WARNING: No access token available in user object during serialization");
}

done(null, user);
});

passport.deserializeUser(async (id, done) => {
try {
const user = await User.findById(id);
done(null, user);
} catch (err) {
done(err, null);
passport.deserializeUser((user, done) => {
console.log("Deserializing user:", user.id || user.githubId || user.googleId);

// Confirm access token availability during deserialization
if (user.accessToken) {
console.log("Access token available during deserialization");
} else {
console.log("WARNING: Access token missing during deserialization");
}

// Simply pass through the user object
done(null, user);
});
16 changes: 11 additions & 5 deletions backend/db/connection.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,15 @@
require('dotenv').config();
const mongoose = require('mongoose');

// Only connect to MongoDB if MONGODB_URI is provided and not testing mode
const dburl = process.env.MONGODB_URI;
mongoose.connect(dburl).then(() => {
console.log("Connected to DB Successfully ");
}).catch((err) => {
console.log(err.message);
});
if (dburl && process.env.NODE_ENV !== 'test-auth') {
mongoose.connect(dburl).then(() => {
console.log("Connected to DB Successfully ");
}).catch((err) => {
console.log("MongoDB connection error:", err.message);
console.log("Continuing without MongoDB for authentication testing...");
});
} else {
console.log("MongoDB connection skipped for authentication testing");
}
3 changes: 3 additions & 0 deletions backend/env.example
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ JWT_SECRET=
GOOGLE_CLIENT_ID=
GOOGLE_CLIENT_SECRET=
GOOGLE_CALLBACK_URL=
GITHUB_CLIENT_ID=
GITHUB_CLIENT_SECRET=
GITHUB_CALLBACK_URL=
CLIENT_URL=
SESSION_SECRET=
ADMIN_EMAIL=
Expand Down
Loading