Skip to content

Commit 3e68b24

Browse files
committed
Feat: Progress-tracking + other updates
1 parent 0a6313a commit 3e68b24

File tree

4 files changed

+138
-36
lines changed

4 files changed

+138
-36
lines changed

src/app/dashboard/page.tsx

Lines changed: 66 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import { redirect } from "next/navigation";
2+
import { revalidatePath } from "next/cache";
23
import Link from "next/link";
34
import { createClient as createServerClient } from "../../lib/supabase/server";
45
import { Cinzel } from "next/font/google";
5-
import checkUserCompletedQuizzes from "@/lib/checkUserCompletedQuizzes";
66
import { ArrowBigLeft, ArrowBigDown, ArrowBigRight } from "lucide-react";
77

88
const cinzel = Cinzel({
@@ -17,6 +17,36 @@ async function logout() {
1717
redirect("/");
1818
}
1919

20+
async function resetQuestProgress() {
21+
"use server";
22+
const supabase = await createServerClient();
23+
const {
24+
data: { user },
25+
} = await supabase.auth.getUser();
26+
27+
if (!user) {
28+
return;
29+
}
30+
31+
// Delete all quest completions for this user
32+
// The trigger will automatically update quests_completed count to 0
33+
const { error: deleteError } = await supabase
34+
.from("quest_completions")
35+
.delete()
36+
.eq("user_id", user.id);
37+
38+
if (deleteError) {
39+
console.error("Error deleting quest completions:", deleteError);
40+
}
41+
42+
// Revalidate the dashboard and profile pages to refresh data
43+
revalidatePath("/dashboard");
44+
revalidatePath("/profile");
45+
46+
// Redirect to refresh the page
47+
redirect("/dashboard");
48+
}
49+
2050
export default async function DashboardPage() {
2151
const supabase = await createServerClient();
2252
const {
@@ -25,19 +55,30 @@ export default async function DashboardPage() {
2555

2656
if (!user) redirect("/login");
2757

28-
// Completed quizzes
29-
const completedQuizzes = await checkUserCompletedQuizzes();
30-
31-
const isHelloWorldComplete = completedQuizzes.has("hello-world");
32-
const isVariablesComplete = completedQuizzes.has("variables");
33-
const isUserInputComplete = completedQuizzes.has("user-input");
34-
const isConditionalsComplete = completedQuizzes.has("conditionals");
35-
const isLoopsComplete = completedQuizzes.has("loops");
36-
const isMathComplete = completedQuizzes.has("math");
37-
const isFunctionsComplete = completedQuizzes.has("functions");
38-
const isListsArraysComplete = completedQuizzes.has("lists-arrays");
39-
const isDictionaryComplete = completedQuizzes.has("dictionary");
40-
const isRecursionComplete = completedQuizzes.has("recursion");
58+
// Fetch individual quest completions from quest_completions table
59+
const { data: questCompletions, error: completionsError } = await supabase
60+
.from("quest_completions")
61+
.select("quest_id")
62+
.eq("user_id", user.id);
63+
64+
if (completionsError) {
65+
console.error(`Error fetching quest completions: ${completionsError.message}`);
66+
}
67+
68+
// Create a Set of completed quest IDs for quick lookup
69+
const completedQuestIds = new Set(questCompletions?.map((qc) => qc.quest_id) || []);
70+
71+
// Check individual quest completion status
72+
const isHelloWorldComplete = completedQuestIds.has("hello-world");
73+
const isVariablesComplete = completedQuestIds.has("variables");
74+
const isUserInputComplete = completedQuestIds.has("user-input");
75+
const isConditionalsComplete = completedQuestIds.has("conditionals");
76+
const isLoopsComplete = completedQuestIds.has("loops");
77+
const isMathComplete = completedQuestIds.has("math");
78+
const isFunctionsComplete = completedQuestIds.has("functions");
79+
const isListsArraysComplete = completedQuestIds.has("lists-arrays");
80+
const isDictionaryComplete = completedQuestIds.has("dictionary");
81+
const isRecursionComplete = completedQuestIds.has("recursion");
4182

4283
const celestialButtonClasses =
4384
"btn border-2 border-cyan-400 text-cyan-400 bg-transparent hover:bg-cyan-900/50 hover:border-cyan-200 hover:text-cyan-200 shadow-lg shadow-cyan-500/50 transition duration-300 ease-in-out w-full";
@@ -66,18 +107,23 @@ export default async function DashboardPage() {
66107
>
67108
<div className="flex justify-between items-start w-full">
68109
<div className="flex flex-col gap-4 p-0 w-fit">
69-
<h1 className="text-white text-5xl font-bold tracking-wider mb-4">Dashboard</h1>
70-
<div className="flex flex-col gap-4 w-32">
71-
<Link href="/" className={celestialButtonClasses}>
72-
<span>Home</span>
73-
</Link>
74-
</div>
110+
<h1 className="text-white text-6xl font-bold tracking-wider mb-4 drop-shadow-[0_0_15px_rgba(255,255,255,0.5)] hover:drop-shadow-[0_0_20px_rgba(255,255,255,0.7)] transition-all duration-300">
111+
Dashboard
112+
</h1>
75113
</div>
76114

77115
<div className="flex items-center gap-4 p-0 w-fit">
116+
<Link href="/" className={celestialButtonNoFullWidth}>
117+
<span>Home</span>
118+
</Link>
78119
<Link href="/profile" className={celestialButtonNoFullWidth}>
79120
<span>Profile</span>
80121
</Link>
122+
<form action={resetQuestProgress}>
123+
<button type="submit" className={celestialButtonNoFullWidth}>
124+
<span>Reset Quests</span>
125+
</button>
126+
</form>
81127
<form action={logout}>
82128
<button type="submit" className={celestialButtonNoFullWidth}>
83129
<span>Log out</span>

src/app/profile/page.tsx

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ export default async function ProfilePage() {
2424
// Fetch user profile from database
2525
const { data: profile } = await supabase
2626
.from('profiles')
27-
.select('class')
27+
.select('class, quests_completed')
2828
.eq('id', user.id)
2929
.maybeSingle(); // Use maybeSingle() instead of single() to handle case where profile doesn't exist
3030

@@ -49,6 +49,11 @@ export default async function ProfilePage() {
4949
})
5050
: 'Unknown';
5151

52+
// Calculate Python progress based on quest completion
53+
const totalQuests = 10;
54+
const completedQuests = profile?.quests_completed ?? 0;
55+
const pythonProgress = Math.round((completedQuests / totalQuests) * 100);
56+
5257
return (
5358
<main className="relative min-h-dvh p-8">
5459
{/* Background with blur */}
@@ -143,7 +148,7 @@ export default async function ProfilePage() {
143148
Python
144149
</span>
145150
<span className="text-base" style={{ color: "#E0E0E0" }}>
146-
85%
151+
{pythonProgress}%
147152
</span>
148153
</div>
149154
<div
@@ -153,7 +158,7 @@ export default async function ProfilePage() {
153158
<div
154159
className="h-full rounded-full"
155160
style={{
156-
width: "85%",
161+
width: `${pythonProgress}%`,
157162
background: "linear-gradient(to right, #be9661, #a7e4e7)",
158163
}}
159164
/>
@@ -167,7 +172,7 @@ export default async function ProfilePage() {
167172
Java
168173
</span>
169174
<span className="text-base" style={{ color: "#E0E0E0" }}>
170-
72%
175+
0%
171176
</span>
172177
</div>
173178
<div
@@ -177,7 +182,7 @@ export default async function ProfilePage() {
177182
<div
178183
className="h-full rounded-full"
179184
style={{
180-
width: "72%",
185+
width: "0%",
181186
background: "linear-gradient(to right, #a7e4e7, #be9661)",
182187
}}
183188
/>
@@ -191,7 +196,7 @@ export default async function ProfilePage() {
191196
C++
192197
</span>
193198
<span className="text-base" style={{ color: "#E0E0E0" }}>
194-
68%
199+
0%
195200
</span>
196201
</div>
197202
<div
@@ -201,7 +206,7 @@ export default async function ProfilePage() {
201206
<div
202207
className="h-full rounded-full"
203208
style={{
204-
width: "68%",
209+
width: "0%",
205210
background: "linear-gradient(135deg, #be9661, #a7e4e7, #be9661)",
206211
}}
207212
/>
@@ -284,7 +289,7 @@ export default async function ProfilePage() {
284289
Quests
285290
</span>
286291
<span className="text-2xl font-bold" style={{ color: "#be9661" }}>
287-
0/8
292+
{profile?.quests_completed ?? 0}/10
288293
</span>
289294
</div>
290295
</div>

src/components/tutorial-quiz.tsx

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -56,9 +56,8 @@ export default function Quiz({ quizData }: QuizProps) {
5656
setIsCorrect(null);
5757
};
5858

59-
// Updates database that user completed quiz, no score to keep things simple
60-
// TODO: Update the insert with a upsert, and keep track of the user's most recent score on quiz
61-
// Also another neat feature would be loading the quiz result instead of resetting the quiz each time
59+
// Updates database that user completed quiz
60+
// Inserts into quest_completions table (triggers will auto-update quests_completed count)
6261
const updateUserQuizProgress = async () => {
6362
const supabase = createClient();
6463

@@ -72,13 +71,20 @@ export default function Quiz({ quizData }: QuizProps) {
7271
return;
7372
}
7473

75-
const { error: insertError } = await supabase.from("user_quiz_progress").insert({
76-
user_id: user.id,
77-
quiz_id: quizData.id,
78-
});
74+
// Insert into quest_completions table
75+
// The unique constraint prevents duplicates, and the trigger auto-updates quests_completed count
76+
const { error: insertError } = await supabase
77+
.from("quest_completions")
78+
.insert({
79+
user_id: user.id,
80+
quest_id: quizData.id,
81+
});
7982

8083
if (insertError) {
81-
console.error(`Supabase insertion error: ${insertError}`);
84+
// If it's a duplicate key error, the quest was already completed - that's okay
85+
if (insertError.code !== "23505") {
86+
console.error(`Error inserting quest completion: ${insertError.message}`);
87+
}
8288
}
8389
};
8490

src/lib/updateQuestCount.ts

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import { createClient } from "./supabase/client";
2+
3+
/**
4+
* Updates the quests_completed count in the profiles table
5+
* by counting distinct quiz_ids completed by the user
6+
*/
7+
export async function updateQuestCount() {
8+
const supabase = createClient();
9+
10+
// Get user
11+
const {
12+
data: { user },
13+
} = await supabase.auth.getUser();
14+
15+
if (!user) {
16+
console.error("User not logged in, cannot update quest count.");
17+
return;
18+
}
19+
20+
// Count distinct quiz_ids for this user
21+
const { data: quizData, error: selectError } = await supabase
22+
.from("user_quiz_progress")
23+
.select("quiz_id")
24+
.eq("user_id", user.id);
25+
26+
if (selectError) {
27+
console.error(`Error fetching quiz progress: ${selectError.message}`);
28+
return;
29+
}
30+
31+
// Get unique quiz IDs
32+
const uniqueQuizIds = new Set(quizData?.map((row) => row.quiz_id) || []);
33+
const questsCompleted = uniqueQuizIds.size;
34+
35+
// Update the profiles table
36+
const { error: updateError } = await supabase
37+
.from("profiles")
38+
.update({ quests_completed: questsCompleted })
39+
.eq("id", user.id);
40+
41+
if (updateError) {
42+
console.error(`Error updating quest count: ${updateError.message}`);
43+
}
44+
}
45+

0 commit comments

Comments
 (0)