Skip to content
Open
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
47 changes: 34 additions & 13 deletions frontend/src/pages/AccountCenterPage.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@ import { useAuth } from "../context/AuthContext";
import { useState, useEffect } from "react";
import { Link, useSearchParams } from "react-router-dom";
import { getProfile, deleteAccount } from "../services/userService";

const API_BASE = import.meta.env.VITE_API_BASE_URL;
import api from "../services/api";

// ── Helpers ──────────────────────────────────────────────────────────────────

Expand All @@ -21,9 +20,6 @@ const RANK_COLORS = {
unrated: "text-gray-400",
};

const rankColor = (rank = "") =>
RANK_COLORS[(rank || "").toLowerCase()] || "text-black";

// ── Sub-components ────────────────────────────────────────────────────────────

function SectionLabel({ text }) {
Expand Down Expand Up @@ -103,11 +99,31 @@ function GitHubCard({ user }) {
const ghUsername = ghIdentity?.username || user?.handles?.github;
const ghAvatar = user?.profile?.avatar;
const [msg, setMsg] = useState("");
const [connecting, setConnecting] = useState(false);

const handleConnect = async () => {
setConnecting(true);
setMsg("");

const handleConnect = () => {
// Encode current path so backend redirects back here after connect
const redirectPath = encodeURIComponent("/account-center");
window.location.href = `${API_BASE}/auth/github/connect?redirectPath=${redirectPath}`;
try {
const response = await api.post("/auth/github/connect/start", null, {
params: { redirectPath: "/account-center" },
});
const authUrl = response.data?.data?.authUrl;

if (!authUrl) {
throw new Error("GitHub authorization URL was not returned.");
}

window.location.href = authUrl;
} catch (error) {
const message =
error?.response?.data?.message ||
error?.message ||
"Unable to start GitHub connection.";
setMsg(message);
setConnecting(false);
}
};

const handleDisconnect = () => {
Expand Down Expand Up @@ -196,12 +212,13 @@ function GitHubCard({ user }) {

<button
onClick={handleConnect}
disabled={connecting}
className="w-full sm:w-auto px-8 py-5 border-[4px] border-black bg-black text-white text-sm font-black uppercase tracking-widest hover:bg-white hover:text-black transition-colors flex items-center gap-3"
>
<svg width="18" height="18" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
<path d="M12 0C5.37 0 0 5.37 0 12c0 5.3 3.44 9.8 8.21 11.39.6.11.82-.26.82-.58v-2.03c-3.34.73-4.04-1.61-4.04-1.61-.55-1.39-1.34-1.76-1.34-1.76-1.09-.75.08-.73.08-.73 1.21.08 1.84 1.24 1.84 1.24 1.07 1.84 2.81 1.31 3.5 1 .11-.78.42-1.31.76-1.61-2.66-.3-5.47-1.33-5.47-5.93 0-1.31.47-2.38 1.24-3.22-.12-.3-.54-1.52.12-3.18 0 0 1.01-.32 3.3 1.23a11.5 11.5 0 0 1 3-.4c1.02 0 2.04.14 3 .4 2.29-1.55 3.3-1.23 3.3-1.23.66 1.66.24 2.88.12 3.18.77.84 1.24 1.91 1.24 3.22 0 4.61-2.81 5.63-5.48 5.92.43.37.82 1.1.82 2.22v3.29c0 .32.21.7.83.58C20.56 21.8 24 17.3 24 12c0-6.63-5.37-12-12-12z"/>
</svg>
Connect GitHub Account
{connecting ? "Opening GitHub..." : "Connect GitHub Account"}
</button>
</>
)}
Expand Down Expand Up @@ -273,7 +290,7 @@ function DangerZone({ onLogout }) {
try {
await deleteAccount();
onLogout(); // Logs the user out and redirects to login
} catch (err) {
} catch {
alert("Failed to delete account. Please try again later.");
setLoading(false);
setConfirm(false);
Expand Down Expand Up @@ -337,15 +354,19 @@ export default function AccountCenterPage() {
const username = searchParams.get("githubUsername");

if (status === "connected") {
setBanner(`GitHub account @${username || "connected"} linked successfully!`);
const bannerTimer = window.setTimeout(() => {
setBanner(`GitHub account @${username || "connected"} linked successfully!`);
}, 0);
// Refresh user profile so the card shows the new GitHub identity
getProfile()
.then((res) => setUser(res.data))
.catch(() => {});
// Clean URL
window.history.replaceState({}, "", "/account-center");

return () => window.clearTimeout(bannerTimer);
}
}, [searchParams]);
}, [searchParams, setUser]);

return (
<div className="w-full flex-1 bg-white px-4 sm:px-6 md:px-8 py-12 sm:py-16">
Expand Down
17 changes: 17 additions & 0 deletions server/modules/auth/controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,23 @@ class AuthController {
}
}

static async createGithubConnectUrl(req, res, next) {
try {
const { redirectPath } = req.validatedQuery || req.query;
const authUrl = AuthService.getGithubAuthorizationUrl({
mode: "connect",
userId: req.user?._id,
redirectPath
});

return res.status(200).json(
ApiResponse.success("GitHub connect URL generated", { authUrl })
);
} catch (error) {
next(error instanceof ApiError ? error : new ApiError(500, error.message));
}
}

static async githubCallback(req, res, next) {
try {
const { code, state } = req.validatedQuery || req.query;
Expand Down
7 changes: 7 additions & 0 deletions server/modules/auth/routes.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,13 @@ router.get(
validateQuery(githubStartSchema),
AuthController.startGithubConnect
);
router.post(
"/github/connect/start",
githubConnectRateLimit,
authMiddleware,
validateQuery(githubStartSchema),
AuthController.createGithubConnectUrl
);
router.get("/github/callback", validateQuery(githubCallbackSchema), AuthController.githubCallback);

export default router;
Loading