diff --git a/src/app/globals.css b/src/app/globals.css
index efe06b6..afc34f5 100644
--- a/src/app/globals.css
+++ b/src/app/globals.css
@@ -314,5 +314,6 @@
}
.cm-editor {
+ width: 100%;
height: 100%;
}
diff --git a/src/app/leaderboards/components/LeaderboardRow.tsx b/src/app/leaderboards/components/LeaderboardRow.tsx
index 884bc1b..8458976 100644
--- a/src/app/leaderboards/components/LeaderboardRow.tsx
+++ b/src/app/leaderboards/components/LeaderboardRow.tsx
@@ -1,33 +1,30 @@
-import { LeaderboardRank } from "@/modules/leaderboard/types";
+import Rank from "@/components/app/Rank";
import { LinkIcon, Trophy } from "lucide-react";
import Link from "next/link";
+interface GlobalLeaderboardUser {
+ rank: number;
+ user_id: string;
+ username: string;
+ wins: number;
+ podiums: number;
+ problems_completed: number;
+}
+
const LeaderboardRow = ({
index = 0,
run,
}: {
index?: number;
- run: LeaderboardRank;
+ run: GlobalLeaderboardUser;
}) => {
- const getRankStyle = (rank: number) => {
- if (run.rank === 1) return "rank-gold";
- if (run.rank === 2) return "rank-silver";
- if (run.rank === 3) return "rank-bronze";
- return "bg-secondary text-muted-foreground";
- };
-
- const getRankGlow = (rank: number) => {
- if (run.rank === 1) return "glow-gold";
- if (run.rank === 2) return "glow-silver";
- if (run.rank === 3) return "glow-bronze";
- return "";
- };
-
const isTopThree = run.rank <= 3;
return (
{/* Rank */}
|
-
- {isTopThree ? : run.rank}
-
+
|
{/* Username */}
{run.username}
|
- {/* Time */}
-
-
- {run.pb}
-
+ {/* Wins */}
+ |
+ {run.wins}
|
- {/* Category */}
-
-
- {run.category}
-
+ {/* Podiums */}
+ |
+ {run.podiums}
|
- {/* Assisted */}
-
-
- {run.assisted ? "Assisted" : "Unassisted"}
-
+ {/* Problems Completed */}
+ |
+ {run.problems_completed}
|
- {/* Runs */}
- {run.runsCount} |
-
- {/* Problem */}
+ {/* Profile Link */}
-
- {run.problem}
+
+ View Profile
|
diff --git a/src/app/leaderboards/page.tsx b/src/app/leaderboards/page.tsx
index 8487913..51e704a 100644
--- a/src/app/leaderboards/page.tsx
+++ b/src/app/leaderboards/page.tsx
@@ -3,8 +3,7 @@
import { Trophy, Calendar, Globe, TrendingUp } from "lucide-react";
import { useState } from "react";
-import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
-import { Button } from "@/components/ui/button";
+import { Card } from "@/components/ui/card";
import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs";
import {
Table,
@@ -13,7 +12,6 @@ import {
TableHeader,
TableRow,
} from "@/components/ui/table";
-import { Badge } from "@/components/ui/badge";
import {
Pagination,
PaginationContent,
@@ -30,11 +28,10 @@ import {
getGlobalLeaderboard,
getWeeklyLeaderboard,
} from "@/modules/leaderboard/actions";
-import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group";
export default function Leaderboards() {
- const [global, globalLoaded, globalError] = useAction(getGlobalLeaderboard);
- const [weekly, weeklyLoaded, weeklyError] = useAction(getWeeklyLeaderboard);
+ const [global] = useAction(getGlobalLeaderboard);
+ const [weekly] = useAction(getWeeklyLeaderboard);
const [activeTab, setActiveTab] = useState<"global" | "weekly">("global");
const data = (activeTab === "global" ? global : weekly) || [];
@@ -95,26 +92,6 @@ export default function Leaderboards() {
-
-
-
-
- any%
-
-
- 100%
-
-
-
-
-
- Assisted
-
-
- Unassisted
-
-
-
{/* Table */}
@@ -122,28 +99,38 @@ export default function Leaderboards() {
-
- Rank
+ {/* Rank */}
+
+ Rank
-
- Runner
+
+ {/* Runner / Username */}
+ Runner
+
+ {/* Wins */}
+
+ Wins
-
- Time
+
+ {/* Podiums */}
+
+ Podiums
-
- Category
+
+ {/* Problems Completed */}
+
+ Problems Completed
-
- Assisted
+
+ {/* User ID (optional) */}
+
+ User ID
- Runs
- Problem
{data.map((entry, i) => (
-
+
))}
diff --git a/src/app/problems/components/ProblemCard.tsx b/src/app/problems/components/ProblemCard.tsx
index 1e7f9b6..2a9f5b8 100644
--- a/src/app/problems/components/ProblemCard.tsx
+++ b/src/app/problems/components/ProblemCard.tsx
@@ -1,23 +1,19 @@
import { Button } from "@/components/ui/button";
-import { Clock, Zap, Users } from "lucide-react";
-
-interface ProblemCardProps {
- name: string;
- difficulty: "easy" | "medium" | "hard";
- tags: string[];
- bestTime?: string;
- attempts: number;
- index?: number;
-}
+import { cn } from "@/lib/utils";
+import { Problem } from "@/modules/problems/types";
+import { Clock, Zap, Users, Code } from "lucide-react";
const ProblemCard = ({
- name,
- difficulty,
- tags,
- bestTime,
- attempts,
+ problem,
index = 0,
-}: ProblemCardProps) => {
+ bestTime,
+ attempts = 0,
+}: {
+ problem: Problem;
+ index?: number;
+ bestTime?: string;
+ attempts?: number;
+}) => {
const difficultyStyles = {
easy: "diff-easy",
medium: "diff-medium",
@@ -46,32 +42,19 @@ const ProblemCard = ({
{/* Header */}
- {name}
+ {problem.title}
- {difficultyLabels[difficulty]}
+ {difficultyLabels[problem.difficulty]}
- {/* Tags */}
-
- {tags.map((tag) => (
-
- {tag}
-
- ))}
-
-
- {/* Spacer */}
-
-
- {/* Stats + Action */}
-
+ {/* Tags / Meta */}
+
{bestTime && (
@@ -85,12 +68,22 @@ const ProblemCard = ({
- {/* Quick run button */}
-
-
-
+ {problem.language && (
+
+ {problem.language}
+
+ )}
+
+
+ {/* Description */}
+
+ {problem.description}
+
+
+
+
diff --git a/src/app/problems/page.tsx b/src/app/problems/page.tsx
index 02cb1ce..4eacb13 100644
--- a/src/app/problems/page.tsx
+++ b/src/app/problems/page.tsx
@@ -14,6 +14,8 @@ import { getProblems } from "@/modules/problems/actions";
export default function Problems() {
const [problems, problemsLoaded, problemsError] = useAction(getProblems);
+ console.log(problems);
+
const [viewMode, setViewMode] = useState<"grid" | "list">("grid");
const difficulties = ["All", "Easy", "Medium", "Hard"];
@@ -84,7 +86,7 @@ export default function Problems() {
>
{problems &&
problems.map((problem, i) => (
-
+
))}
diff --git a/src/app/profile/page.tsx b/src/app/profile/page.tsx
index 1b4411f..562878f 100644
--- a/src/app/profile/page.tsx
+++ b/src/app/profile/page.tsx
@@ -8,6 +8,7 @@ import { Button } from "@/components/ui/button";
import { Separator } from "@/components/ui/separator";
import ProfileStatCard from "@/app/profile/components/ProfileStatCard";
+import Rank from "@/components/app/Rank";
export default function Profile() {
const user = {
@@ -34,8 +35,8 @@ export default function Profile() {
{user.username[0].toUpperCase()}
-
diff --git a/src/components/app/ProfileCard.tsx b/src/app/run/components/ProfileCard.tsx
similarity index 51%
rename from src/components/app/ProfileCard.tsx
rename to src/app/run/components/ProfileCard.tsx
index b6e878a..d8a6f42 100644
--- a/src/components/app/ProfileCard.tsx
+++ b/src/app/run/components/ProfileCard.tsx
@@ -1,5 +1,6 @@
-import { Badge } from "../ui/badge";
-import { Card, CardContent } from "../ui/card";
+import Rank from "@/components/app/Rank";
+import { Badge } from "@/components/ui/badge";
+import { Card, CardContent } from "@/components/ui/card";
export default function ProfileCard({
user,
@@ -10,26 +11,24 @@ export default function ProfileCard({
-
+
{/* Avatar */}
-
+

-
{/* User Info */}
-
- {user.username}
-
- World Record Holder
+ {user.username}
+ WR Holder
diff --git a/src/app/run/components/RunTimer.tsx b/src/app/run/components/RunTimer.tsx
index 693256f..4f0748d 100644
--- a/src/app/run/components/RunTimer.tsx
+++ b/src/app/run/components/RunTimer.tsx
@@ -20,8 +20,9 @@ export default function RunTimer({ runInfo }: { runInfo: LiveRun }) {
return (
-
- {time}
+
+ {time}
+ {runInfo.category}
);
diff --git a/src/app/run/page.tsx b/src/app/run/page.tsx
index 3962589..32312af 100644
--- a/src/app/run/page.tsx
+++ b/src/app/run/page.tsx
@@ -9,7 +9,7 @@ import Editor from "./components/Editor";
import useAction from "@/hook/use-action";
import { getLiveRun } from "@/modules/live-run/actions";
import ProblemCard from "../problems/components/ProblemCard";
-import ProfileCard from "@/components/app/ProfileCard";
+import ProfileCard from "./components/ProfileCard";
export default function Run() {
const [runInfo] = useAction(getLiveRun);
@@ -21,9 +21,20 @@ export default function Run() {
];
return (
-
+
+ {/* Main column */}
-
+ {/* Top cards */}
+
@@ -32,22 +43,26 @@ export default function Run() {
{runInfo &&
}
-
{/* Editor */}
-
+
- {/* File tree */}
-
+ {/* File tree – hide on small screens */}
+
{/* Code */}
-
+
-
@@ -78,7 +93,7 @@ export default function Run() {
{/* Timer & Chat */}
-
diff --git a/src/app/runs/components/LiveRunNow.tsx b/src/app/runs/components/LiveRunRow.tsx
similarity index 100%
rename from src/app/runs/components/LiveRunNow.tsx
rename to src/app/runs/components/LiveRunRow.tsx
diff --git a/src/app/runs/page.tsx b/src/app/runs/page.tsx
index 9ceebb6..6af1023 100644
--- a/src/app/runs/page.tsx
+++ b/src/app/runs/page.tsx
@@ -14,7 +14,7 @@ import {
TableCell,
} from "@/components/ui/table";
import { cn } from "@/lib/utils";
-import LiveRunRow from "./components/LiveRunNow";
+import LiveRunRow from "./components/LiveRunRow";
import StatCard from "@/components/app/StatCard";
import useAction from "@/hook/use-action";
import { getLiveRuns } from "@/modules/live-run/actions";
diff --git a/src/components/app/Rank.tsx b/src/components/app/Rank.tsx
new file mode 100644
index 0000000..0727cad
--- /dev/null
+++ b/src/components/app/Rank.tsx
@@ -0,0 +1,29 @@
+import { Trophy } from "lucide-react";
+
+export default function Rank({ rank }: { rank: number }) {
+ const getRankStyle = (rank: number) => {
+ if (rank === 1) return "rank-gold";
+ if (rank === 2) return "rank-silver";
+ if (rank === 3) return "rank-bronze";
+ return "bg-secondary text-muted-foreground";
+ };
+
+ const getRankGlow = (rank: number) => {
+ if (rank === 1) return "glow-gold";
+ if (rank === 2) return "glow-silver";
+ if (rank === 3) return "glow-bronze";
+ return "";
+ };
+
+ const isTopThree = rank <= 3;
+
+ return (
+
+ {isTopThree ? : rank}
+
+ );
+}
diff --git a/src/modules/leaderboard/actions.ts b/src/modules/leaderboard/actions.ts
index c62bab4..4e395f6 100644
--- a/src/modules/leaderboard/actions.ts
+++ b/src/modules/leaderboard/actions.ts
@@ -1,194 +1,17 @@
"use server";
+import { createClient } from "@/lib/supabase/server";
import * as Core from "./core";
-import { LeaderboardRank } from "./types"; // updated interface
+import { LeaderboardUser } from "./types"; // updated interface
-export async function getGlobalLeaderboard(): Promise
{
- return [
- {
- rank: 1,
- username: "speedmaster_x",
- pb: "2:34.21",
- runsCount: 2341,
- problem: "Fix broken login flow",
- category: "any%",
- assisted: false,
- },
- {
- rank: 2,
- username: "algo_ninja",
- pb: "2:38.54",
- runsCount: 1876,
- problem: "Refactor legacy API endpoints",
- category: "any%",
- assisted: false,
- },
- {
- rank: 3,
- username: "clockwork",
- pb: "2:41.02",
- runsCount: 3102,
- problem: "Optimize image upload pipeline",
- category: "any%",
- assisted: false,
- },
- {
- rank: 4,
- username: "fastfingers99",
- pb: "2:45.33",
- runsCount: 987,
- problem: "Build a mini chat server",
- category: "any%",
- assisted: false,
- },
- {
- rank: 5,
- username: "recursion_queen",
- pb: "2:47.89",
- runsCount: 1543,
- problem: "Fix CSS layout bug in dashboard",
- category: "any%",
- assisted: false,
- },
- {
- rank: 6,
- username: "bitshifter",
- pb: "2:52.11",
- runsCount: 2109,
- problem: "Implement JWT authentication",
- category: "any%",
- assisted: false,
- },
- {
- rank: 7,
- username: "nullpointer",
- pb: "2:54.67",
- runsCount: 876,
- problem: "Create REST endpoint for user data",
- category: "any%",
- assisted: false,
- },
- {
- rank: 8,
- username: "stackattack",
- pb: "2:58.23",
- runsCount: 1234,
- problem: "Fix off-by-one bug in sorting function",
- category: "any%",
- assisted: false,
- },
- {
- rank: 9,
- username: "looplegend",
- pb: "3:01.45",
- runsCount: 654,
- problem: "Optimize SQL query for reports",
- category: "any%",
- assisted: false,
- },
- {
- rank: 10,
- username: "coderunner",
- pb: "3:05.78",
- runsCount: 1098,
- problem: "Add file upload validation",
- category: "any%",
- assisted: false,
- },
- ];
+export async function getGlobalLeaderboard(): Promise {
+ const supabase = await createClient();
+
+ return await Core.getGlobalLeaderboard(supabase);
}
-export async function getWeeklyLeaderboard(): Promise {
- return [
- {
- rank: 1,
- username: "weekly_warrior",
- pb: "2:51.33",
- runsCount: 156,
- problem: "Build notifications API",
- category: "any%",
- assisted: false,
- },
- {
- rank: 2,
- username: "speedmaster_x",
- pb: "2:53.12",
- runsCount: 89,
- problem: "Build notifications API",
- category: "any%",
- assisted: false,
- },
- {
- rank: 3,
- username: "newbie_grinder",
- pb: "2:58.67",
- runsCount: 234,
- problem: "Build notifications API",
- category: "any%",
- assisted: false,
- },
- {
- rank: 4,
- username: "algo_ninja",
- pb: "3:02.45",
- runsCount: 67,
- problem: "Build notifications API",
- category: "any%",
- assisted: false,
- },
- {
- rank: 5,
- username: "fresh_runner",
- pb: "3:05.89",
- runsCount: 45,
- problem: "Build notifications API",
- category: "any%",
- assisted: false,
- },
- {
- rank: 6,
- username: "practice_makes",
- pb: "3:08.12",
- runsCount: 112,
- problem: "Build notifications API",
- category: "any%",
- assisted: false,
- },
- {
- rank: 7,
- username: "splitmaster",
- pb: "3:12.34",
- runsCount: 78,
- problem: "Build notifications API",
- category: "any%",
- assisted: false,
- },
- {
- rank: 8,
- username: "grindtime",
- pb: "3:15.56",
- runsCount: 92,
- problem: "Build notifications API",
- category: "any%",
- assisted: false,
- },
- {
- rank: 9,
- username: "pb_hunter",
- pb: "3:18.90",
- runsCount: 56,
- problem: "Build notifications API",
- category: "any%",
- assisted: false,
- },
- {
- rank: 10,
- username: "reset_king",
- pb: "3:22.11",
- runsCount: 187,
- problem: "Build notifications API",
- category: "any%",
- assisted: false,
- },
- ];
+export async function getWeeklyLeaderboard(): Promise {
+ const supabase = await createClient();
+
+ return await Core.getWeeklyLeaderboard(supabase);
}
diff --git a/src/modules/leaderboard/core.ts b/src/modules/leaderboard/core.ts
index a59f71d..d981b6c 100644
--- a/src/modules/leaderboard/core.ts
+++ b/src/modules/leaderboard/core.ts
@@ -1,3 +1,21 @@
"use server";
+import { SupabaseClient } from "@supabase/supabase-js";
import "server-only";
+import { LeaderboardUser } from "./types";
+
+export async function getGlobalLeaderboard(
+ supabase: SupabaseClient,
+): Promise {
+ const { data, error } = await supabase.rpc("get_global_leaderboard");
+ if (error) throw error;
+ return data;
+}
+
+export async function getWeeklyLeaderboard(
+ supabase: SupabaseClient,
+): Promise {
+ const { data, error } = await supabase.rpc("get_weekly_leaderboard");
+ if (error) throw error;
+ return data;
+}
diff --git a/src/modules/leaderboard/types.ts b/src/modules/leaderboard/types.ts
index 0f1cc51..c5edd3e 100644
--- a/src/modules/leaderboard/types.ts
+++ b/src/modules/leaderboard/types.ts
@@ -1,11 +1,8 @@
-export type Leaderboard = LeaderboardRank[];
-
-export interface LeaderboardRank {
+export interface LeaderboardUser {
rank: number;
+ user_id: string;
username: string;
- pb: string;
- runsCount: number;
- problem: string;
- category: "any%" | "100%";
- assisted: boolean;
+ wins: number;
+ podiums: number;
+ problems_completed: number;
}
diff --git a/src/modules/problems/actions.ts b/src/modules/problems/actions.ts
index 0d587ef..a549c80 100644
--- a/src/modules/problems/actions.ts
+++ b/src/modules/problems/actions.ts
@@ -4,90 +4,5 @@ import * as Core from "./core";
import { Problem } from "./types";
export async function getProblems(): Promise {
- return [
- {
- name: "Two Sum Dash",
- difficulty: "easy" as const,
- tags: ["arrays", "hash-map"],
- bestTime: "0:42",
- attempts: 12453,
- },
- {
- name: "Binary Sprint",
- difficulty: "easy" as const,
- tags: ["binary-search"],
- bestTime: "0:38",
- attempts: 8921,
- },
- {
- name: "Linked List Rush",
- difficulty: "medium" as const,
- tags: ["linked-list", "pointers"],
- bestTime: "1:24",
- attempts: 6234,
- },
- {
- name: "Tree Traverse Blitz",
- difficulty: "medium" as const,
- tags: ["trees", "recursion"],
- bestTime: "2:01",
- attempts: 5102,
- },
- {
- name: "DP Gauntlet",
- difficulty: "hard" as const,
- tags: ["dynamic-programming"],
- bestTime: "4:32",
- attempts: 2341,
- },
- {
- name: "Graph Navigator",
- difficulty: "hard" as const,
- tags: ["graphs", "bfs", "dfs"],
- bestTime: "3:45",
- attempts: 3456,
- },
- {
- name: "String Scramble",
- difficulty: "easy" as const,
- tags: ["strings", "manipulation"],
- bestTime: "0:55",
- attempts: 9876,
- },
- {
- name: "Stack Overflow",
- difficulty: "medium" as const,
- tags: ["stacks", "queues"],
- bestTime: "1:42",
- attempts: 4567,
- },
- {
- name: "Heap Hero",
- difficulty: "hard" as const,
- tags: ["heaps", "priority-queue"],
- bestTime: "3:21",
- attempts: 1890,
- },
- {
- name: "Sliding Window Slam",
- difficulty: "medium" as const,
- tags: ["sliding-window", "arrays"],
- bestTime: "1:56",
- attempts: 5678,
- },
- {
- name: "Trie Triumph",
- difficulty: "hard" as const,
- tags: ["trie", "strings"],
- bestTime: "4:12",
- attempts: 1234,
- },
- {
- name: "Backtrack Blaster",
- difficulty: "hard" as const,
- tags: ["backtracking", "recursion"],
- bestTime: "5:01",
- attempts: 987,
- },
- ];
+ return await Core.getProblems();
}
diff --git a/src/modules/problems/core.ts b/src/modules/problems/core.ts
index a59f71d..0189644 100644
--- a/src/modules/problems/core.ts
+++ b/src/modules/problems/core.ts
@@ -1,3 +1,46 @@
"use server";
import "server-only";
+import { ProblemDefinition } from "./types";
+
+const problems: ProblemDefinition[] = [
+ {
+ id: "fix-auth-callback",
+ title: "Fix the Broken Auth Callback",
+ description:
+ "Users can log in with OAuth, but sessions aren't persisted. Fix it.",
+ language: "typescript",
+ difficulty: "easy",
+ validate100: () => true,
+ validateAny: () => true,
+ },
+ {
+ id: "fix-prod-build",
+ title: "Patch a Failing Production Build",
+ description:
+ "Production build fails while development works. Fix the build.",
+ language: "javascript",
+ difficulty: "easy",
+ validate100: () => true,
+ validateAny: () => true,
+ },
+ {
+ id: "add-user-profile-endpoint",
+ title: "Implement Missing API Endpoint",
+ description: "The frontend expects `/api/me`. Implement it correctly.",
+ language: "go",
+ difficulty: "easy",
+ validate100: () => true,
+ validateAny: () => true,
+ },
+];
+
+export async function getProblems() {
+ return problems.map((def) => ({
+ id: def.id,
+ title: def.title,
+ description: def.description,
+ difficulty: def.difficulty,
+ language: def.language,
+ }));
+}
diff --git a/src/modules/problems/types.ts b/src/modules/problems/types.ts
index 6610746..50478c1 100644
--- a/src/modules/problems/types.ts
+++ b/src/modules/problems/types.ts
@@ -1,7 +1,24 @@
+export type ProblemValidator = (submission: any) => boolean;
+
export interface Problem {
- name: string;
+ id: string;
+ title: string;
+ description: string;
difficulty: "easy" | "medium" | "hard";
- tags: string[];
- bestTime?: string;
- attempts: number;
+ language:
+ | "rust"
+ | "go"
+ | "java"
+ | "javascript"
+ | "typescript"
+ | "python"
+ | null;
+}
+
+export interface ProblemDefinition extends Problem {
+ /* Any% validator */
+ validateAny: ProblemValidator;
+ /* 100% validator */
+ validate100: ProblemValidator;
+ metadata?: Record; // optional extra info like time limits, refs
}