Merge pull request #6 from orus-dev/supabase

Supabase and backend structure #5
This commit is contained in:
Klesti Selimaj
2026-02-05 15:39:48 +01:00
committed by GitHub
28 changed files with 667 additions and 288 deletions
+152 -2
View File
@@ -35,6 +35,8 @@
"@radix-ui/react-toggle": "^1.1.9",
"@radix-ui/react-toggle-group": "^1.1.10",
"@radix-ui/react-tooltip": "^1.2.7",
"@supabase/ssr": "^0.8.0",
"@supabase/supabase-js": "^2.94.1",
"@uiw/codemirror-theme-vscode": "^4.25.4",
"@uiw/react-codemirror": "^4.25.4",
"class-variance-authority": "^0.7.1",
@@ -2744,6 +2746,98 @@
"integrity": "sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==",
"license": "MIT"
},
"node_modules/@supabase/auth-js": {
"version": "2.94.1",
"resolved": "https://registry.npmjs.org/@supabase/auth-js/-/auth-js-2.94.1.tgz",
"integrity": "sha512-Wt/SdmAtNNiqrcBbPlzWojLcE1bQ9OYb8PTaYF6QccFX5JeXZI0sZ01MLNE+E83UK6cK0lw4YznX0D2g08UQng==",
"license": "MIT",
"dependencies": {
"tslib": "2.8.1"
},
"engines": {
"node": ">=20.0.0"
}
},
"node_modules/@supabase/functions-js": {
"version": "2.94.1",
"resolved": "https://registry.npmjs.org/@supabase/functions-js/-/functions-js-2.94.1.tgz",
"integrity": "sha512-A7Bx0gnclDNZ4m8+mnO2IEEzMxtUSg7cpPEBF6Ek1LpjIQkC7vvoidiV/RuntnKX43IiVcWV1f2FsAppMagEmQ==",
"license": "MIT",
"dependencies": {
"tslib": "2.8.1"
},
"engines": {
"node": ">=20.0.0"
}
},
"node_modules/@supabase/postgrest-js": {
"version": "2.94.1",
"resolved": "https://registry.npmjs.org/@supabase/postgrest-js/-/postgrest-js-2.94.1.tgz",
"integrity": "sha512-N6MTghjHnMZddT48rAj8dIFgedCU97cc1ahQM74Tc+DF4UH7y2+iEfdYV3unJsylpaiWlu92Fy8Lj14Jbrmxog==",
"license": "MIT",
"dependencies": {
"tslib": "2.8.1"
},
"engines": {
"node": ">=20.0.0"
}
},
"node_modules/@supabase/realtime-js": {
"version": "2.94.1",
"resolved": "https://registry.npmjs.org/@supabase/realtime-js/-/realtime-js-2.94.1.tgz",
"integrity": "sha512-Wq8olpCAGmN4y2DH2kUdlcakdzNHRCde72BFS8zK5ub46bBeSUoE9DqrfeNFWKaF2gCE/cmK8aTUTorZD9jdtQ==",
"license": "MIT",
"dependencies": {
"@types/phoenix": "^1.6.6",
"@types/ws": "^8.18.1",
"tslib": "2.8.1",
"ws": "^8.18.2"
},
"engines": {
"node": ">=20.0.0"
}
},
"node_modules/@supabase/ssr": {
"version": "0.8.0",
"resolved": "https://registry.npmjs.org/@supabase/ssr/-/ssr-0.8.0.tgz",
"integrity": "sha512-/PKk8kNFSs8QvvJ2vOww1mF5/c5W8y42duYtXvkOSe+yZKRgTTZywYG2l41pjhNomqESZCpZtXuWmYjFRMV+dw==",
"license": "MIT",
"dependencies": {
"cookie": "^1.0.2"
},
"peerDependencies": {
"@supabase/supabase-js": "^2.76.1"
}
},
"node_modules/@supabase/storage-js": {
"version": "2.94.1",
"resolved": "https://registry.npmjs.org/@supabase/storage-js/-/storage-js-2.94.1.tgz",
"integrity": "sha512-/Mi18LGyrugPwtfqETfAqEGcBQotY/7IMsTGYgEFdqr8cQq280BVQWjN2wI9KibWtshPp0Ryvil5Uzd5YfM7kA==",
"license": "MIT",
"dependencies": {
"iceberg-js": "^0.8.1",
"tslib": "2.8.1"
},
"engines": {
"node": ">=20.0.0"
}
},
"node_modules/@supabase/supabase-js": {
"version": "2.94.1",
"resolved": "https://registry.npmjs.org/@supabase/supabase-js/-/supabase-js-2.94.1.tgz",
"integrity": "sha512-87vOY8n3WHB3m+a/KeySj07djOQVuRA5qgX5E7db1eDkaZ1of5M+3t/tv6eYYy4BfqxuHMZuCe5uVrO/oyvoow==",
"license": "MIT",
"dependencies": {
"@supabase/auth-js": "2.94.1",
"@supabase/functions-js": "2.94.1",
"@supabase/postgrest-js": "2.94.1",
"@supabase/realtime-js": "2.94.1",
"@supabase/storage-js": "2.94.1"
},
"engines": {
"node": ">=20.0.0"
}
},
"node_modules/@swc/helpers": {
"version": "0.5.15",
"resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz",
@@ -3091,12 +3185,17 @@
"version": "20.19.30",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.30.tgz",
"integrity": "sha512-WJtwWJu7UdlvzEAUm484QNg5eAoq5QR08KDNx7g45Usrs2NtOPiX8ugDqmKdXkyL03rBqU5dYNYVQetEpBHq2g==",
"dev": true,
"license": "MIT",
"dependencies": {
"undici-types": "~6.21.0"
}
},
"node_modules/@types/phoenix": {
"version": "1.6.7",
"resolved": "https://registry.npmjs.org/@types/phoenix/-/phoenix-1.6.7.tgz",
"integrity": "sha512-oN9ive//QSBkf19rfDv45M7eZPi0eEXylht2OLEXicu5b4KoQ1OzXIw+xDSGWxSxe1JmepRR/ZH283vsu518/Q==",
"license": "MIT"
},
"node_modules/@types/react": {
"version": "19.2.10",
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.10.tgz",
@@ -3117,6 +3216,15 @@
"@types/react": "^19.2.0"
}
},
"node_modules/@types/ws": {
"version": "8.18.1",
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz",
"integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==",
"license": "MIT",
"dependencies": {
"@types/node": "*"
}
},
"node_modules/@uiw/codemirror-extensions-basic-setup": {
"version": "4.25.4",
"resolved": "https://registry.npmjs.org/@uiw/codemirror-extensions-basic-setup/-/codemirror-extensions-basic-setup-4.25.4.tgz",
@@ -3300,6 +3408,19 @@
"@codemirror/view": "^6.0.0"
}
},
"node_modules/cookie": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz",
"integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==",
"license": "MIT",
"engines": {
"node": ">=18"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/express"
}
},
"node_modules/crelt": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.6.tgz",
@@ -3554,6 +3675,15 @@
"dev": true,
"license": "ISC"
},
"node_modules/iceberg-js": {
"version": "0.8.1",
"resolved": "https://registry.npmjs.org/iceberg-js/-/iceberg-js-0.8.1.tgz",
"integrity": "sha512-1dhVQZXhcHje7798IVM+xoo/1ZdVfzOMIc8/rgVSijRK38EDqOJoGula9N/8ZI5RD8QTxNQtK/Gozpr+qUqRRA==",
"license": "MIT",
"engines": {
"node": ">=20.0.0"
}
},
"node_modules/internmap": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz",
@@ -4613,7 +4743,6 @@
"version": "6.21.0",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
"integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
"dev": true,
"license": "MIT"
},
"node_modules/use-callback-ref": {
@@ -4695,6 +4824,27 @@
"resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz",
"integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==",
"license": "MIT"
},
"node_modules/ws": {
"version": "8.19.0",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.19.0.tgz",
"integrity": "sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==",
"license": "MIT",
"engines": {
"node": ">=10.0.0"
},
"peerDependencies": {
"bufferutil": "^4.0.1",
"utf-8-validate": ">=5.0.2"
},
"peerDependenciesMeta": {
"bufferutil": {
"optional": true
},
"utf-8-validate": {
"optional": true
}
}
}
}
}
+2
View File
@@ -35,6 +35,8 @@
"@radix-ui/react-toggle": "^1.1.9",
"@radix-ui/react-toggle-group": "^1.1.10",
"@radix-ui/react-tooltip": "^1.2.7",
"@supabase/ssr": "^0.8.0",
"@supabase/supabase-js": "^2.94.1",
"@uiw/codemirror-theme-vscode": "^4.25.4",
"@uiw/react-codemirror": "^4.25.4",
"class-variance-authority": "^0.7.1",
+9 -148
View File
@@ -25,158 +25,19 @@ import {
import LeaderboardRow from "@/app/leaderboards/components/LeaderboardRow";
import StatCard from "@/components/app/StatCard";
const globalLeaderboard = [
{
rank: 1,
username: "speedmaster_x",
pb: "2:34.21",
runsCount: 2341,
category: "Any%",
},
{
rank: 2,
username: "algo_ninja",
pb: "2:38.54",
runsCount: 1876,
category: "Any%",
},
{
rank: 3,
username: "clockwork",
pb: "2:41.02",
runsCount: 3102,
category: "Any%",
},
{
rank: 4,
username: "fastfingers99",
pb: "2:45.33",
runsCount: 987,
category: "Any%",
},
{
rank: 5,
username: "recursion_queen",
pb: "2:47.89",
runsCount: 1543,
category: "Any%",
},
{
rank: 6,
username: "bitshifter",
pb: "2:52.11",
runsCount: 2109,
category: "Any%",
},
{
rank: 7,
username: "nullpointer",
pb: "2:54.67",
runsCount: 876,
category: "Any%",
},
{
rank: 8,
username: "stackattack",
pb: "2:58.23",
runsCount: 1234,
category: "Any%",
},
{
rank: 9,
username: "looplegend",
pb: "3:01.45",
runsCount: 654,
category: "Any%",
},
{
rank: 10,
username: "coderunner",
pb: "3:05.78",
runsCount: 1098,
category: "Any%",
},
];
const weeklyLeaderboard = [
{
rank: 1,
username: "weekly_warrior",
pb: "2:51.33",
runsCount: 156,
category: "Any%",
},
{
rank: 2,
username: "speedmaster_x",
pb: "2:53.12",
runsCount: 89,
category: "Any%",
},
{
rank: 3,
username: "newbie_grinder",
pb: "2:58.67",
runsCount: 234,
category: "Any%",
},
{
rank: 4,
username: "algo_ninja",
pb: "3:02.45",
runsCount: 67,
category: "Any%",
},
{
rank: 5,
username: "fresh_runner",
pb: "3:05.89",
runsCount: 45,
category: "Any%",
},
{
rank: 6,
username: "practice_makes",
pb: "3:08.12",
runsCount: 112,
category: "Any%",
},
{
rank: 7,
username: "splitmaster",
pb: "3:12.34",
runsCount: 78,
category: "Any%",
},
{
rank: 8,
username: "grindtime",
pb: "3:15.56",
runsCount: 92,
category: "Any%",
},
{
rank: 9,
username: "pb_hunter",
pb: "3:18.90",
runsCount: 56,
category: "Any%",
},
{
rank: 10,
username: "reset_king",
pb: "3:22.11",
runsCount: 187,
category: "Any%",
},
];
import useAction from "@/hook/use-action";
import {
getGlobalLeaderboard,
getWeeklyLeaderboard,
} from "@/modules/leaderboard/actions";
export default function Leaderboards() {
const [global, globalLoaded, globalError] = useAction(getGlobalLeaderboard);
const [weekly, weeklyLoaded, weeklyError] = useAction(getWeeklyLeaderboard);
const [activeTab, setActiveTab] = useState<"global" | "weekly">("global");
const categories = ["Any%", "100%", "Easy Only", "No DP", "Blind"];
const data = activeTab === "global" ? globalLeaderboard : weeklyLeaderboard;
const data = (activeTab === "global" ? global : weekly) || [];
return (
<div className="min-h-screen pt-24 pb-16">
+6 -90
View File
@@ -8,8 +8,12 @@ import { Input } from "@/components/ui/input";
import { Button } from "@/components/ui/button";
import { Badge } from "@/components/ui/badge";
import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group";
import useAction from "@/hook/use-action";
import { getProblems } from "@/modules/problems/actions";
export default function Problems() {
const [problems, problemsLoaded, problemsError] = useAction(getProblems);
const [viewMode, setViewMode] = useState<"grid" | "list">("grid");
const categories = ["All", "Arrays", "Trees", "Graphs", "DP", "Strings"];
@@ -83,7 +87,8 @@ export default function Problems() {
: "grid-cols-1"
}`}
>
{problems.map((problem, i) => (
{problems &&
problems.map((problem, i) => (
<ProblemCard key={problem.name} {...problem} index={i} />
))}
</div>
@@ -91,92 +96,3 @@ export default function Problems() {
</div>
);
}
/* ---------------- data ---------------- */
const problems = [
{
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,
},
];
+5 -44
View File
@@ -16,53 +16,14 @@ import {
import { cn } from "@/lib/utils";
import LiveRunRow from "./components/LiveRunNow";
import StatCard from "@/components/app/StatCard";
/* ---------------- mock data ---------------- */
const liveRuns = [
{
id: 1,
username: "speedmaster_x",
problem: "Two Sum Dash",
category: "Any%",
time: "1:52.34",
pace: "-0:03",
status: "pb",
},
{
id: 2,
username: "algo_ninja",
problem: "Binary Sprint",
category: "Any%",
time: "2:04.11",
pace: "+0:05",
status: "normal",
},
{
id: 3,
username: "stackattack",
problem: "String Scramble",
category: "Easy",
time: "0:48.92",
pace: "-0:01",
status: "pb",
},
{
id: 4,
username: "reset_king",
problem: "Linked List Rush",
category: "Any%",
time: "1:31.77",
pace: "+0:12",
status: "danger",
},
];
/* ---------------- page ---------------- */
import useAction from "@/hook/use-action";
import { getLiveRuns } from "@/modules/live-run/actions";
export default function LiveRuns() {
const [liveRuns, liveRunsLoaded, liveRunsError] = useAction(getLiveRuns);
// Sort top runners (status === 'pb') to the top
const sortedRuns = [...liveRuns].sort((a, b) => {
const sortedRuns = [...(liveRuns || [])].sort((a, b) => {
if (a.status === "pb" && b.status !== "pb") return -1;
if (a.status !== "pb" && b.status === "pb") return 1;
return 0;
+14 -2
View File
@@ -1,7 +1,15 @@
"use client";
import { Github, Mail } from "lucide-react";
import { Button } from "../ui/button";
import { signInWithGitHub } from "@/modules/account/actions";
export default function ContinueWith() {
const continueWithGithub = () => {
signInWithGitHub();
};
const continueWithGoogle = () => {};
return (
<>
<div className="relative">
@@ -16,11 +24,15 @@ export default function ContinueWith() {
</div>
<div className="grid grid-cols-2 gap-3 animate-fade-in stagger-4">
<Button variant="outline">
<Button variant="outline" onClick={continueWithGithub}>
<Github className="h-4 w-4" />
GitHub
</Button>
<Button variant="outline" className="animate-fade-in stagger-5">
<Button
variant="outline"
className="animate-fade-in stagger-5"
onClick={continueWithGoogle}
>
<Mail className="h-4 w-4" />
Google
</Button>
+19
View File
@@ -0,0 +1,19 @@
import { useEffect, useState } from "react";
export default function useAction<T>(
action: () => Promise<T>,
dependencies = [],
): [T | null, boolean, Error | null] {
const [value, setValue] = useState<T | null>(null);
const [loaded, setLoaded] = useState(false);
const [error, setError] = useState(null);
useEffect(() => {
action()
.then(setValue)
.catch(setError)
.finally(() => setLoaded(true));
}, dependencies);
return [value, loaded, error];
}
+26
View File
@@ -0,0 +1,26 @@
import "server-only";
import { createServerClient, type CookieOptions } from "@supabase/ssr";
import { cookies } from "next/headers";
export async function createClient() {
const cookieStore = await cookies();
return createServerClient(
process.env.SUPABASE_URL!,
process.env.SUPABASE_SECRET_KEY!,
{
cookies: {
get(name: string) {
return cookieStore.get(name)?.value;
},
set(name: string, value: string, options: CookieOptions) {
cookieStore.set(name, value, options);
},
remove(name: string, options: CookieOptions) {
cookieStore.set(name, "", options);
},
},
},
);
}
+26
View File
@@ -0,0 +1,26 @@
"use server";
import * as Core from "./core";
import { createClient } from "@/lib/supabase";
import { redirect } from "next/navigation";
export async function signInWithGitHub() {
const supabase = await createClient();
const { data, error } = await supabase.auth.signInWithOAuth({
provider: "github",
});
if (error) {
console.error(error);
return redirect("/login?msg=Trouble");
}
return redirect(data.url);
}
export async function signOut() {
const supabase = await createClient();
const { error } = await supabase.auth.signOut();
}
+68
View File
@@ -0,0 +1,68 @@
"use server";
import { SupabaseClient, User } from "@supabase/supabase-js";
import "server-only";
import { UserProfile } from "../user/types";
export async function getProfile(
supabase: SupabaseClient,
user: User,
): Promise<UserProfile> {
// Try to fetch existing profile
const { data: profile, error } = await supabase
.from("profiles")
.select("*")
.eq("id", user.id)
.single();
if (error && error.code !== "PGRST116") {
// PGRST116 = "No rows found" is fine
throw error;
}
// If no profile, create one
if (!profile) {
return getGithubProfile(supabase, user);
}
return profile;
}
export async function getGithubProfile(
supabase: SupabaseClient,
user: User,
): Promise<UserProfile> {
const { data: newProfile, error: insertError } = await supabase
.from("profiles")
.insert({
id: user.id,
username: user.user_metadata.login || null,
full_name: user.user_metadata.name || null,
avatar_url: user.user_metadata.avatar_url || null,
})
.select()
.single();
if (insertError) throw insertError;
return newProfile;
}
export async function getSession(
supabase: SupabaseClient,
): Promise<[User, UserProfile]> {
const {
data: { session },
error: sessionError,
} = await supabase.auth.getSession();
if (sessionError || !session?.user) {
throw new Error("No authenticated user found");
}
const user = session.user;
const profile = await getProfile(supabase, user);
return [user, profile];
}
+154
View File
@@ -0,0 +1,154 @@
"use server";
import * as Core from "./core";
import { Leaderboard } from "./types";
export async function getGlobalLeaderboard(): Promise<Leaderboard> {
return [
{
rank: 1,
username: "speedmaster_x",
pb: "2:34.21",
runsCount: 2341,
category: "Any%",
},
{
rank: 2,
username: "algo_ninja",
pb: "2:38.54",
runsCount: 1876,
category: "Any%",
},
{
rank: 3,
username: "clockwork",
pb: "2:41.02",
runsCount: 3102,
category: "Any%",
},
{
rank: 4,
username: "fastfingers99",
pb: "2:45.33",
runsCount: 987,
category: "Any%",
},
{
rank: 5,
username: "recursion_queen",
pb: "2:47.89",
runsCount: 1543,
category: "Any%",
},
{
rank: 6,
username: "bitshifter",
pb: "2:52.11",
runsCount: 2109,
category: "Any%",
},
{
rank: 7,
username: "nullpointer",
pb: "2:54.67",
runsCount: 876,
category: "Any%",
},
{
rank: 8,
username: "stackattack",
pb: "2:58.23",
runsCount: 1234,
category: "Any%",
},
{
rank: 9,
username: "looplegend",
pb: "3:01.45",
runsCount: 654,
category: "Any%",
},
{
rank: 10,
username: "coderunner",
pb: "3:05.78",
runsCount: 1098,
category: "Any%",
},
];
}
export async function getWeeklyLeaderboard(): Promise<Leaderboard> {
return [
{
rank: 1,
username: "weekly_warrior",
pb: "2:51.33",
runsCount: 156,
category: "Any%",
},
{
rank: 2,
username: "speedmaster_x",
pb: "2:53.12",
runsCount: 89,
category: "Any%",
},
{
rank: 3,
username: "newbie_grinder",
pb: "2:58.67",
runsCount: 234,
category: "Any%",
},
{
rank: 4,
username: "algo_ninja",
pb: "3:02.45",
runsCount: 67,
category: "Any%",
},
{
rank: 5,
username: "fresh_runner",
pb: "3:05.89",
runsCount: 45,
category: "Any%",
},
{
rank: 6,
username: "practice_makes",
pb: "3:08.12",
runsCount: 112,
category: "Any%",
},
{
rank: 7,
username: "splitmaster",
pb: "3:12.34",
runsCount: 78,
category: "Any%",
},
{
rank: 8,
username: "grindtime",
pb: "3:15.56",
runsCount: 92,
category: "Any%",
},
{
rank: 9,
username: "pb_hunter",
pb: "3:18.90",
runsCount: 56,
category: "Any%",
},
{
rank: 10,
username: "reset_king",
pb: "3:22.11",
runsCount: 187,
category: "Any%",
},
];
}
+3
View File
@@ -0,0 +1,3 @@
"use server";
import "server-only";
+9
View File
@@ -0,0 +1,9 @@
export type Leaderboard = LeaderboardRank[];
export interface LeaderboardRank {
rank: number;
username: string;
pb: string;
runsCount: number;
category: string;
}
+45
View File
@@ -0,0 +1,45 @@
"use server";
import * as Core from "./core";
import { Run } from "./types";
export async function getLiveRuns(): Promise<Run[]> {
return [
{
id: 1,
username: "speedmaster_x",
problem: "Two Sum Dash",
category: "Any%",
time: "1:52.34",
pace: "-0:03",
status: "pb",
},
{
id: 2,
username: "algo_ninja",
problem: "Binary Sprint",
category: "Any%",
time: "2:04.11",
pace: "+0:05",
status: "normal",
},
{
id: 3,
username: "stackattack",
problem: "String Scramble",
category: "Easy",
time: "0:48.92",
pace: "-0:01",
status: "pb",
},
{
id: 4,
username: "reset_king",
problem: "Linked List Rush",
category: "Any%",
time: "1:31.77",
pace: "+0:12",
status: "danger",
},
];
}
+3
View File
@@ -0,0 +1,3 @@
"use server";
import "server-only";
+9
View File
@@ -0,0 +1,9 @@
export interface Run {
id: number;
username: string;
problem: string;
category: string;
time: string;
pace: string;
status: string;
}
+93
View File
@@ -0,0 +1,93 @@
"use server";
import * as Core from "./core";
import { Problem } from "./types";
export async function getProblems(): Promise<Problem[]> {
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,
},
];
}
+3
View File
@@ -0,0 +1,3 @@
"use server";
import "server-only";
+7
View File
@@ -0,0 +1,7 @@
export interface Problem {
name: string;
difficulty: "easy" | "medium" | "hard";
tags: string[];
bestTime?: string;
attempts: number;
}
View File
View File
+3
View File
@@ -0,0 +1,3 @@
"use server";
import * as Core from "./core";
+3
View File
@@ -0,0 +1,3 @@
"use server";
import "server-only";
+6
View File
@@ -0,0 +1,6 @@
export interface UserProfile {
id: string;
username: string;
full_name: string;
avatar_url: string;
}
View File
View File
View File