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
159 changes: 81 additions & 78 deletions frontend/src/pages/AccountCenterPage.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ function StatusBadge({ connected }) {

function AuthMethodCard({ user }) {
const isGitHub = user?.authProvider === "github";
const isLocal = user?.authProvider === "local" || !user?.authProvider;
const isLocal = user?.authProvider === "local" || !user?.authProvider;

return (
<div className="border-[4px] border-black bg-white p-6 sm:p-8">
Expand All @@ -73,11 +73,11 @@ function AuthMethodCard({ user }) {
</div>

<div className="space-y-1">
<InfoRow label="Provider" value={isGitHub ? "GitHub OAuth" : "Email & Password"} />
<InfoRow label="Name" value={user?.name} />
<InfoRow label="Email" value={user?.email} />
<InfoRow label="Verified" value={user?.isVerified ? "Yes" : "Pending"} />
<InfoRow label="Role" value={user?.role || "User"} />
<InfoRow label="Provider" value={isGitHub ? "GitHub OAuth" : "Email & Password"} />
<InfoRow label="Name" value={user?.name} />
<InfoRow label="Email" value={user?.email} />
<InfoRow label="Verified" value={user?.isVerified ? "Yes" : "Pending"} />
<InfoRow label="Role" value={user?.role || "User"} />
</div>

{isLocal && (
Expand All @@ -97,10 +97,10 @@ function AuthMethodCard({ user }) {
// ── GitHub Connection Card ────────────────────────────────────────────────────

function GitHubCard({ user }) {
const ghIdentity = user?.oauth?.github;
const isConnected = !!ghIdentity?.id;
const ghUsername = ghIdentity?.username || user?.handles?.github;
const ghAvatar = user?.profile?.avatar;
const ghIdentity = user?.oauth?.github;
const isConnected = !!ghIdentity?.id;
const ghUsername = ghIdentity?.username || user?.handles?.github;
const ghAvatar = user?.profile?.avatar;
const [msg, setMsg] = useState("");

const [connectLoading, setConnectLoading] = useState(false);
Expand Down Expand Up @@ -138,7 +138,7 @@ function GitHubCard({ user }) {
<div className="flex items-center gap-3">
{/* GitHub SVG icon */}
<svg width="24" height="24" 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"/>
<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>
<h2 className="text-xl sm:text-2xl font-black uppercase tracking-tighter leading-none">
GitHub
Expand Down Expand Up @@ -176,8 +176,8 @@ function GitHubCard({ user }) {
</div>

<div className="space-y-1">
<InfoRow label="GitHub ID" value={ghIdentity?.id} />
<InfoRow label="Username" value={ghUsername} />
<InfoRow label="GitHub ID" value={ghIdentity?.id} />
<InfoRow label="Username" value={ghUsername} />
</div>

<div className="mt-6 flex flex-wrap gap-3">
Expand Down Expand Up @@ -219,7 +219,7 @@ function GitHubCard({ user }) {
<span className="w-[18px] h-[18px] border-[3px] border-white border-t-transparent animate-spin" />
) : (
<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"/>
<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>
)}
{connectLoading ? "Connecting..." : "Connect GitHub Account"}
Expand All @@ -239,7 +239,7 @@ function GitHubCard({ user }) {
// ── Codeforces Card ───────────────────────────────────────────────────────────

function CodeforcesCard({ user }) {
const cfHandle = user?.handles?.codeforces;
const cfHandle = user?.handles?.codeforces;
const isConnected = !!cfHandle;

return (
Expand Down Expand Up @@ -330,7 +330,7 @@ function DangerZone({ onLogout }) {
>
Cancel
</button>
<button
<button
onClick={handleDelete}
disabled={loading}
className="px-6 py-3 bg-white text-black text-sm font-black uppercase tracking-widest hover:bg-gray-200 transition-colors disabled:opacity-50"
Expand All @@ -349,90 +349,93 @@ function DangerZone({ onLogout }) {

export default function AccountCenterPage() {
const { user, logout, refreshUser } = useAuth();
const [searchParams] = useSearchParams();
const [searchParams] = useSearchParams();
const [banner, setBanner] = useState("");

// Backend redirects here after GitHub connect: /account-center?githubStatus=connected
useEffect(() => {
const status = searchParams.get("githubStatus");
const status = searchParams.get("githubStatus");
const username = searchParams.get("githubUsername");

if (status === "connected") {
setBanner(`GitHub account @${username || "connected"} linked successfully!`);
// Refresh user profile via /auth/me so the card shows the new GitHub identity
// WITHOUT needing a full page refresh
refreshUser().catch(() => {});
refreshUser().catch(() => { });
// Clean URL
window.history.replaceState({}, "", "/account-center");
}
}, [searchParams]);

return (
<div className="w-full flex-1 bg-white px-4 sm:px-6 md:px-8 py-12 sm:py-16">
<div className="max-w-5xl mx-auto">

{/* ── Page Header ──────────────────────────────────────────────── */}
<header className="mb-12 border-b-[4px] border-black pb-8">
<p className="text-xs font-black uppercase tracking-[0.22em] text-gray-500 mb-3">
Settings & Connections
</p>
<h1 className="text-4xl sm:text-6xl font-black uppercase tracking-tighter leading-none mb-4">
Account Center
</h1>
<p className="text-sm font-bold uppercase tracking-widest text-gray-600 max-w-2xl leading-relaxed">
Manage your authentication methods, connected platforms, and account preferences. All connections unlock richer analytics and AI insights.
</p>
</header>

{/* ── Success banner ────────────────────────────────────────── */}
{banner && (
<div className="mb-8 border-[4px] border-black bg-black text-white px-6 py-4 flex items-center justify-between gap-4">
<p className="font-black uppercase tracking-widest text-sm">{banner}</p>
<button
onClick={() => setBanner("")}
className="text-white font-black text-lg leading-none hover:text-gray-300"
aria-label="Dismiss"
>
βœ•
</button>
</div>
)}
<main>
<title>Account Center - CodeLens</title>
<div className="w-full flex-1 bg-white px-4 sm:px-6 md:px-8 py-12 sm:py-16">
<div className="max-w-5xl mx-auto">

{/* ── Page Header ──────────────────────────────────────────────── */}
<header className="mb-12 border-b-[4px] border-black pb-8">
<p className="text-xs font-black uppercase tracking-[0.22em] text-gray-500 mb-3">
Settings & Connections
</p>
<h1 className="text-4xl sm:text-6xl font-black uppercase tracking-tighter leading-none mb-4">
Account Center
</h1>
<p className="text-sm font-bold uppercase tracking-widest text-gray-600 max-w-2xl leading-relaxed">
Manage your authentication methods, connected platforms, and account preferences. All connections unlock richer analytics and AI insights.
</p>
</header>

{/* ── User identity quick-view ─────────────────────────────────── */}
{user && (
<div className="flex items-center gap-5 mb-12 p-6 border-[4px] border-black bg-black text-white">
{user.profile?.avatar ? (
<img
src={user.profile.avatar}
alt={user.name}
className="w-16 h-16 border-[3px] border-white object-cover flex-shrink-0"
/>
) : (
<div className="w-16 h-16 border-[3px] border-white bg-white text-black flex items-center justify-center font-black text-2xl flex-shrink-0">
{(user.name || "U")[0].toUpperCase()}
{/* ── Success banner ────────────────────────────────────────── */}
{banner && (
<div className="mb-8 border-[4px] border-black bg-black text-white px-6 py-4 flex items-center justify-between gap-4">
<p className="font-black uppercase tracking-widest text-sm">{banner}</p>
<button
onClick={() => setBanner("")}
className="text-white font-black text-lg leading-none hover:text-gray-300"
aria-label="Dismiss"
>
βœ•
</button>
</div>
)}

{/* ── User identity quick-view ─────────────────────────────────── */}
{user && (
<div className="flex items-center gap-5 mb-12 p-6 border-[4px] border-black bg-black text-white">
{user.profile?.avatar ? (
<img
src={user.profile.avatar}
alt={user.name}
className="w-16 h-16 border-[3px] border-white object-cover flex-shrink-0"
/>
) : (
<div className="w-16 h-16 border-[3px] border-white bg-white text-black flex items-center justify-center font-black text-2xl flex-shrink-0">
{(user.name || "U")[0].toUpperCase()}
</div>
)}
<div>
<p className="text-xs font-black uppercase tracking-widest text-gray-400">Logged in as</p>
<p className="text-2xl sm:text-3xl font-black uppercase tracking-tighter leading-none">
{user.name}
</p>
<p className="text-xs font-bold uppercase tracking-widest text-gray-400 mt-1">
{user.email} Β· {user.authProvider === "github" ? "GitHub Auth" : "Email Auth"}
</p>
</div>
)}
<div>
<p className="text-xs font-black uppercase tracking-widest text-gray-400">Logged in as</p>
<p className="text-2xl sm:text-3xl font-black uppercase tracking-tighter leading-none">
{user.name}
</p>
<p className="text-xs font-bold uppercase tracking-widest text-gray-400 mt-1">
{user.email} Β· {user.authProvider === "github" ? "GitHub Auth" : "Email Auth"}
</p>
</div>
)}

{/* ── Card Grid ────────────────────────────────────────────────── */}
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
<AuthMethodCard user={user} />
<GitHubCard user={user} />
<CodeforcesCard user={user} />
<DangerZone onLogout={logout} />
</div>
)}

{/* ── Card Grid ────────────────────────────────────────────────── */}
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
<AuthMethodCard user={user} />
<GitHubCard user={user} />
<CodeforcesCard user={user} />
<DangerZone onLogout={logout} />
</div>

</div>
</div>
</main>
);
}
27 changes: 15 additions & 12 deletions frontend/src/pages/AlgoVersePage.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,8 @@ export default function AlgoVersePage() {
};

return (
<main>
<title>AlgoVerse - CodeLens</title>
<div className="w-full min-h-screen bg-white">
{/* Hero Section */}
<section className="w-full border-b-4 border-black px-4 sm:px-6 md:px-8 py-16 sm:py-20 md:py-24">
Expand Down Expand Up @@ -148,7 +150,7 @@ export default function AlgoVersePage() {
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
className="w-full border-4 border-black px-6 py-4 font-bold text-base uppercase tracking-wide placeholder:text-black placeholder:opacity-40 focus:outline-none focus:shadow-[4px_4px_0_0_rgba(0,0,0,1)] transition-shadow"
/>
/>
</div>
{/* Filter Button */}
<button className="px-8 py-4 border-4 border-black bg-black text-white font-black uppercase tracking-widest hover:bg-white hover:text-black transition-colors">
Expand All @@ -167,13 +169,13 @@ export default function AlgoVersePage() {
<div className="grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-4 xl:grid-cols-6 gap-4">
{algorithmCategories.map((category) => (
<button
key={category.id}
onClick={() => setSelectedCategory(category.id)}
className={`border-4 border-black p-4 sm:p-6 transition-all duration-150 hover:-translate-x-1 hover:-translate-y-1 hover:shadow-[6px_6px_0_0_rgba(0,0,0,1)] ${
selectedCategory === category.id
? "bg-black text-white shadow-[6px_6px_0_0_rgba(0,0,0,1)]"
key={category.id}
onClick={() => setSelectedCategory(category.id)}
className={`border-4 border-black p-4 sm:p-6 transition-all duration-150 hover:-translate-x-1 hover:-translate-y-1 hover:shadow-[6px_6px_0_0_rgba(0,0,0,1)] ${
selectedCategory === category.id
? "bg-black text-white shadow-[6px_6px_0_0_rgba(0,0,0,1)]"
: "bg-white text-black"
}`}
}`}
>
<div className="text-3xl font-black mb-3">{category.icon}</div>
<div className="text-xs sm:text-sm font-black uppercase tracking-tight leading-tight mb-2">
Expand Down Expand Up @@ -201,15 +203,15 @@ export default function AlgoVersePage() {
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{featuredAlgorithms.map((algo) => (
<div
key={algo.id}
className="border-4 border-black bg-white p-6 hover:shadow-[12px_12px_0_0_rgba(0,0,0,1)] hover:-translate-x-2 hover:-translate-y-2 transition-all cursor-pointer group"
key={algo.id}
className="border-4 border-black bg-white p-6 hover:shadow-[12px_12px_0_0_rgba(0,0,0,1)] hover:-translate-x-2 hover:-translate-y-2 transition-all cursor-pointer group"
>
<div className="flex items-start justify-between mb-4">
<div
className={`border-2 px-3 py-1 text-xs font-black uppercase tracking-wide ${getDifficultyColor(
algo.difficulty
)}`}
>
>
{algo.difficulty}
</div>
<div className="text-xs font-black uppercase tracking-widest text-gray-500">
Expand Down Expand Up @@ -239,8 +241,8 @@ export default function AlgoVersePage() {
<div className="flex flex-wrap gap-2 pt-4 border-t-2 border-black">
{algo.tags.map((tag, idx) => (
<span
key={idx}
className="text-[10px] font-black uppercase tracking-wide border-2 border-black px-2 py-1 bg-gray-50"
key={idx}
className="text-[10px] font-black uppercase tracking-wide border-2 border-black px-2 py-1 bg-gray-50"
>
{tag}
</span>
Expand Down Expand Up @@ -281,5 +283,6 @@ export default function AlgoVersePage() {
</div>
</section>
</div>
</main>
);
}
Loading