From f4e0cfb3f460ec736c7e3175455ce227e216c35b Mon Sep 17 00:00:00 2001 From: Leo dev Date: Thu, 5 Feb 2026 13:05:43 +0100 Subject: [PATCH 1/9] Backend structure #5 --- src/modules/{run/run.actions.ts => leaderboard/actions.ts} | 0 src/modules/{run/run.service.ts => leaderboard/core.ts} | 0 src/modules/{run/run.types.ts => leaderboard/types.ts} | 0 src/modules/{user/user.actions.ts => live-run/actions.ts} | 0 src/modules/{user/user.service.ts => live-run/core.ts} | 0 src/modules/{user/user.types.ts => live-run/types.ts} | 0 src/modules/user/actions.ts | 0 src/modules/user/core.ts | 0 src/modules/user/types.ts | 0 9 files changed, 0 insertions(+), 0 deletions(-) rename src/modules/{run/run.actions.ts => leaderboard/actions.ts} (100%) rename src/modules/{run/run.service.ts => leaderboard/core.ts} (100%) rename src/modules/{run/run.types.ts => leaderboard/types.ts} (100%) rename src/modules/{user/user.actions.ts => live-run/actions.ts} (100%) rename src/modules/{user/user.service.ts => live-run/core.ts} (100%) rename src/modules/{user/user.types.ts => live-run/types.ts} (100%) create mode 100644 src/modules/user/actions.ts create mode 100644 src/modules/user/core.ts create mode 100644 src/modules/user/types.ts diff --git a/src/modules/run/run.actions.ts b/src/modules/leaderboard/actions.ts similarity index 100% rename from src/modules/run/run.actions.ts rename to src/modules/leaderboard/actions.ts diff --git a/src/modules/run/run.service.ts b/src/modules/leaderboard/core.ts similarity index 100% rename from src/modules/run/run.service.ts rename to src/modules/leaderboard/core.ts diff --git a/src/modules/run/run.types.ts b/src/modules/leaderboard/types.ts similarity index 100% rename from src/modules/run/run.types.ts rename to src/modules/leaderboard/types.ts diff --git a/src/modules/user/user.actions.ts b/src/modules/live-run/actions.ts similarity index 100% rename from src/modules/user/user.actions.ts rename to src/modules/live-run/actions.ts diff --git a/src/modules/user/user.service.ts b/src/modules/live-run/core.ts similarity index 100% rename from src/modules/user/user.service.ts rename to src/modules/live-run/core.ts diff --git a/src/modules/user/user.types.ts b/src/modules/live-run/types.ts similarity index 100% rename from src/modules/user/user.types.ts rename to src/modules/live-run/types.ts diff --git a/src/modules/user/actions.ts b/src/modules/user/actions.ts new file mode 100644 index 0000000..e69de29 diff --git a/src/modules/user/core.ts b/src/modules/user/core.ts new file mode 100644 index 0000000..e69de29 diff --git a/src/modules/user/types.ts b/src/modules/user/types.ts new file mode 100644 index 0000000..e69de29 From 42a98ad85941bc681719bdfc23327c099c4e1522 Mon Sep 17 00:00:00 2001 From: Leo dev Date: Thu, 5 Feb 2026 13:09:36 +0100 Subject: [PATCH 2/9] Backend structure #5 --- src/modules/leaderboard/actions.ts | 4 ++++ src/modules/leaderboard/core.ts | 3 +++ src/modules/live-run/actions.ts | 1 + src/modules/live-run/core.ts | 3 +++ src/modules/user/actions.ts | 1 + src/modules/user/core.ts | 3 +++ 6 files changed, 15 insertions(+) diff --git a/src/modules/leaderboard/actions.ts b/src/modules/leaderboard/actions.ts index e69de29..95b3ae1 100644 --- a/src/modules/leaderboard/actions.ts +++ b/src/modules/leaderboard/actions.ts @@ -0,0 +1,4 @@ +"use server"; + + +export function getGlobalLeaderboard() {} \ No newline at end of file diff --git a/src/modules/leaderboard/core.ts b/src/modules/leaderboard/core.ts index e69de29..a59f71d 100644 --- a/src/modules/leaderboard/core.ts +++ b/src/modules/leaderboard/core.ts @@ -0,0 +1,3 @@ +"use server"; + +import "server-only"; diff --git a/src/modules/live-run/actions.ts b/src/modules/live-run/actions.ts index e69de29..908fe79 100644 --- a/src/modules/live-run/actions.ts +++ b/src/modules/live-run/actions.ts @@ -0,0 +1 @@ +"use server"; diff --git a/src/modules/live-run/core.ts b/src/modules/live-run/core.ts index e69de29..a59f71d 100644 --- a/src/modules/live-run/core.ts +++ b/src/modules/live-run/core.ts @@ -0,0 +1,3 @@ +"use server"; + +import "server-only"; diff --git a/src/modules/user/actions.ts b/src/modules/user/actions.ts index e69de29..908fe79 100644 --- a/src/modules/user/actions.ts +++ b/src/modules/user/actions.ts @@ -0,0 +1 @@ +"use server"; diff --git a/src/modules/user/core.ts b/src/modules/user/core.ts index e69de29..a59f71d 100644 --- a/src/modules/user/core.ts +++ b/src/modules/user/core.ts @@ -0,0 +1,3 @@ +"use server"; + +import "server-only"; From b6d137b1d0ba5f7248197c79e6248c7a2039545b Mon Sep 17 00:00:00 2001 From: Leo dev Date: Thu, 5 Feb 2026 13:12:18 +0100 Subject: [PATCH 3/9] Backend structure #5 --- src/modules/leaderboard/actions.ts | 3 ++- src/modules/live-run/actions.ts | 2 ++ src/modules/user/actions.ts | 2 ++ 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/modules/leaderboard/actions.ts b/src/modules/leaderboard/actions.ts index 95b3ae1..f1179b4 100644 --- a/src/modules/leaderboard/actions.ts +++ b/src/modules/leaderboard/actions.ts @@ -1,4 +1,5 @@ "use server"; +import * as Core from "./core"; -export function getGlobalLeaderboard() {} \ No newline at end of file +export function getGlobalLeaderboard() {} diff --git a/src/modules/live-run/actions.ts b/src/modules/live-run/actions.ts index 908fe79..59d1635 100644 --- a/src/modules/live-run/actions.ts +++ b/src/modules/live-run/actions.ts @@ -1 +1,3 @@ "use server"; + +import * as Core from "./core"; diff --git a/src/modules/user/actions.ts b/src/modules/user/actions.ts index 908fe79..59d1635 100644 --- a/src/modules/user/actions.ts +++ b/src/modules/user/actions.ts @@ -1 +1,3 @@ "use server"; + +import * as Core from "./core"; From 07ea424c3ee0b387ca78ec4ecff104578b259497 Mon Sep 17 00:00:00 2001 From: Leo dev Date: Thu, 5 Feb 2026 13:26:04 +0100 Subject: [PATCH 4/9] Use action --- src/app/leaderboards/page.tsx | 157 ++--------------------------- src/hook/use-action.ts | 19 ++++ src/modules/leaderboard/actions.ts | 151 ++++++++++++++++++++++++++- src/modules/leaderboard/types.ts | 9 ++ 4 files changed, 187 insertions(+), 149 deletions(-) create mode 100644 src/hook/use-action.ts diff --git a/src/app/leaderboards/page.tsx b/src/app/leaderboards/page.tsx index fd28720..74f43d1 100644 --- a/src/app/leaderboards/page.tsx +++ b/src/app/leaderboards/page.tsx @@ -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 (
diff --git a/src/hook/use-action.ts b/src/hook/use-action.ts new file mode 100644 index 0000000..0f7468a --- /dev/null +++ b/src/hook/use-action.ts @@ -0,0 +1,19 @@ +import { useEffect, useState } from "react"; + +export default function useAction( + action: () => Promise, + dependencies = [], +): [T | null, boolean, Error | null] { + const [value, setValue] = useState(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]; +} diff --git a/src/modules/leaderboard/actions.ts b/src/modules/leaderboard/actions.ts index f1179b4..34f7f0a 100644 --- a/src/modules/leaderboard/actions.ts +++ b/src/modules/leaderboard/actions.ts @@ -1,5 +1,154 @@ "use server"; import * as Core from "./core"; +import { Leaderboard } from "./types"; -export function getGlobalLeaderboard() {} +export async function getGlobalLeaderboard(): Promise { + 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 { + 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%", + }, + ]; +} diff --git a/src/modules/leaderboard/types.ts b/src/modules/leaderboard/types.ts index e69de29..4f25bf1 100644 --- a/src/modules/leaderboard/types.ts +++ b/src/modules/leaderboard/types.ts @@ -0,0 +1,9 @@ +export type Leaderboard = LeaderboardRank[]; + +export interface LeaderboardRank { + rank: number; + username: string; + pb: string; + runsCount: number; + category: string; +} From 7046857e0b86ddd940b1f1c94c2b89cd49c505fa Mon Sep 17 00:00:00 2001 From: Leo dev Date: Thu, 5 Feb 2026 13:36:25 +0100 Subject: [PATCH 5/9] Problems #5 --- src/app/problems/page.tsx | 100 +++----------------------------- src/modules/problems/actions.ts | 93 +++++++++++++++++++++++++++++ src/modules/problems/core.ts | 3 + src/modules/problems/types.ts | 7 +++ 4 files changed, 111 insertions(+), 92 deletions(-) create mode 100644 src/modules/problems/actions.ts create mode 100644 src/modules/problems/core.ts create mode 100644 src/modules/problems/types.ts diff --git a/src/app/problems/page.tsx b/src/app/problems/page.tsx index 35b15cd..1992627 100644 --- a/src/app/problems/page.tsx +++ b/src/app/problems/page.tsx @@ -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,100 +87,12 @@ export default function Problems() { : "grid-cols-1" }`} > - {problems.map((problem, i) => ( - - ))} + {problems && + problems.map((problem, i) => ( + + ))}
); } - -/* ---------------- 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, - }, -]; diff --git a/src/modules/problems/actions.ts b/src/modules/problems/actions.ts new file mode 100644 index 0000000..0d587ef --- /dev/null +++ b/src/modules/problems/actions.ts @@ -0,0 +1,93 @@ +"use server"; + +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, + }, + ]; +} diff --git a/src/modules/problems/core.ts b/src/modules/problems/core.ts new file mode 100644 index 0000000..a59f71d --- /dev/null +++ b/src/modules/problems/core.ts @@ -0,0 +1,3 @@ +"use server"; + +import "server-only"; diff --git a/src/modules/problems/types.ts b/src/modules/problems/types.ts new file mode 100644 index 0000000..6610746 --- /dev/null +++ b/src/modules/problems/types.ts @@ -0,0 +1,7 @@ +export interface Problem { + name: string; + difficulty: "easy" | "medium" | "hard"; + tags: string[]; + bestTime?: string; + attempts: number; +} From a5214925a851c632174a99249349bbe53680392d Mon Sep 17 00:00:00 2001 From: Leo dev Date: Thu, 5 Feb 2026 13:40:04 +0100 Subject: [PATCH 6/9] live runs #5 --- src/app/runs/page.tsx | 49 ++++----------------------------- src/modules/live-run/actions.ts | 42 ++++++++++++++++++++++++++++ src/modules/live-run/types.ts | 9 ++++++ 3 files changed, 56 insertions(+), 44 deletions(-) diff --git a/src/app/runs/page.tsx b/src/app/runs/page.tsx index 7ba7a3c..35a1c37 100644 --- a/src/app/runs/page.tsx +++ b/src/app/runs/page.tsx @@ -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; diff --git a/src/modules/live-run/actions.ts b/src/modules/live-run/actions.ts index 59d1635..221d8eb 100644 --- a/src/modules/live-run/actions.ts +++ b/src/modules/live-run/actions.ts @@ -1,3 +1,45 @@ "use server"; import * as Core from "./core"; +import { Run } from "./types"; + +export async function getLiveRuns(): Promise { + 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", + }, + ]; +} diff --git a/src/modules/live-run/types.ts b/src/modules/live-run/types.ts index e69de29..b29b054 100644 --- a/src/modules/live-run/types.ts +++ b/src/modules/live-run/types.ts @@ -0,0 +1,9 @@ +export interface Run { + id: number; + username: string; + problem: string; + category: string; + time: string; + pace: string; + status: string; +} From 0e3663714e9c28a49f430ca879e7b08c86f329ab Mon Sep 17 00:00:00 2001 From: Leo dev Date: Thu, 5 Feb 2026 14:08:31 +0100 Subject: [PATCH 7/9] Supabase implementation --- package-lock.json | 154 ++++++++++++++++++++++++++++++++- package.json | 2 + src/lib/supabase.ts | 26 ++++++ src/modules/account/actions.ts | 3 + src/modules/account/core.ts | 3 + src/modules/account/types.ts | 3 + 6 files changed, 189 insertions(+), 2 deletions(-) create mode 100644 src/lib/supabase.ts create mode 100644 src/modules/account/actions.ts create mode 100644 src/modules/account/core.ts create mode 100644 src/modules/account/types.ts diff --git a/package-lock.json b/package-lock.json index 0240847..ea2cd87 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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 + } + } } } } diff --git a/package.json b/package.json index db0abb3..e721d45 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/src/lib/supabase.ts b/src/lib/supabase.ts new file mode 100644 index 0000000..ae00085 --- /dev/null +++ b/src/lib/supabase.ts @@ -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); + }, + }, + }, + ); +} diff --git a/src/modules/account/actions.ts b/src/modules/account/actions.ts new file mode 100644 index 0000000..59d1635 --- /dev/null +++ b/src/modules/account/actions.ts @@ -0,0 +1,3 @@ +"use server"; + +import * as Core from "./core"; diff --git a/src/modules/account/core.ts b/src/modules/account/core.ts new file mode 100644 index 0000000..a59f71d --- /dev/null +++ b/src/modules/account/core.ts @@ -0,0 +1,3 @@ +"use server"; + +import "server-only"; diff --git a/src/modules/account/types.ts b/src/modules/account/types.ts new file mode 100644 index 0000000..b914e3e --- /dev/null +++ b/src/modules/account/types.ts @@ -0,0 +1,3 @@ +export interface Account { + +} From 231e9d57c8feebc5e3ac6a649accf71b91e53a1e Mon Sep 17 00:00:00 2001 From: Leo dev Date: Thu, 5 Feb 2026 15:29:16 +0100 Subject: [PATCH 8/9] Sign in with github implementation --- src/modules/account/actions.ts | 23 ++++++++++++ src/modules/account/core.ts | 65 ++++++++++++++++++++++++++++++++++ src/modules/account/types.ts | 3 -- src/modules/user/types.ts | 6 ++++ 4 files changed, 94 insertions(+), 3 deletions(-) diff --git a/src/modules/account/actions.ts b/src/modules/account/actions.ts index 59d1635..a2e7820 100644 --- a/src/modules/account/actions.ts +++ b/src/modules/account/actions.ts @@ -1,3 +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(); +} diff --git a/src/modules/account/core.ts b/src/modules/account/core.ts index a59f71d..7edcfc7 100644 --- a/src/modules/account/core.ts +++ b/src/modules/account/core.ts @@ -1,3 +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 { + // 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 { + 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]; +} diff --git a/src/modules/account/types.ts b/src/modules/account/types.ts index b914e3e..e69de29 100644 --- a/src/modules/account/types.ts +++ b/src/modules/account/types.ts @@ -1,3 +0,0 @@ -export interface Account { - -} diff --git a/src/modules/user/types.ts b/src/modules/user/types.ts index e69de29..4e35545 100644 --- a/src/modules/user/types.ts +++ b/src/modules/user/types.ts @@ -0,0 +1,6 @@ +export interface UserProfile { + id: string; + username: string; + full_name: string; + avatar_url: string; +} From e0f023f1fbfcf83a263bfc1a3f374532fe1a5943 Mon Sep 17 00:00:00 2001 From: Leo dev Date: Thu, 5 Feb 2026 15:31:21 +0100 Subject: [PATCH 9/9] Sign in with github frontend --- src/components/auth/ContinueWith.tsx | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/components/auth/ContinueWith.tsx b/src/components/auth/ContinueWith.tsx index 9e94783..d3c85d1 100644 --- a/src/components/auth/ContinueWith.tsx +++ b/src/components/auth/ContinueWith.tsx @@ -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 ( <>
@@ -16,11 +24,15 @@ export default function ContinueWith() {
- -