Skip to content
Merged
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
67 changes: 29 additions & 38 deletions src/app/profile/page.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import { Shield, Star, Trophy, Swords, Pencil } from "lucide-react";
import { Shield, Star, Trophy, Swords } from "lucide-react";
import { createClient as createServerClient } from "../../lib/supabase/server";
import { redirect } from "next/navigation";
import { Cinzel } from "next/font/google";
import EditClass from "../../components/edit-class";
import BackToDashBoardLink from "../../components/back-to-dashboard-link";

//font for words
const cinzel = Cinzel({
Expand All @@ -19,6 +21,24 @@ export default async function ProfilePage() {
redirect("/login");
}

// Fetch user profile from database
const { data: profile, error: profileError } = await supabase
.from('profiles')
.select('class')
.eq('id', user.id)
.maybeSingle(); // Use maybeSingle() instead of single() to handle case where profile doesn't exist

// Map enum values to display names
const classDisplayMap: Record<string, string> = {
'warrior': 'Python Warrior',
'mage': 'Java Mage',
'rogue': 'C++ Rogue',
};

// Get class from database, default to null (will show as empty or default)
const userClassEnum = profile?.class as string | null;
const userClass = userClassEnum ? classDisplayMap[userClassEnum] || userClassEnum : null;

const username = user.email?.split('@')[0] || 'User';

const joinedDate = user.created_at
Expand Down Expand Up @@ -48,6 +68,9 @@ export default async function ProfilePage() {

{/* Content */}
<div className={`relative ${cinzel.className}`} style={{ zIndex: 1 }}>
{/* Back Button */}
<BackToDashBoardLink />

{/* Header */}
<div className="text-center mb-8">
<h1 className="text-4xl font-bold mb-2" style={{ color: "#be9661" }}>
Expand Down Expand Up @@ -197,19 +220,6 @@ export default async function ProfilePage() {
border: "0.5px solid rgba(190, 150, 97, 0.3)",
}}
>
{/* Edit Profile Button */}
<button
className="absolute top-6 right-6 px-4 py-2 rounded-lg flex items-center gap-2 border"
style={{
borderColor: "rgba(190, 150, 97, 0.3)",
color: "#E0E0E0",
backgroundColor: "transparent",
}}
>
<Pencil size={16} style={{ color: "#be9661" }} />
<span>Edit Profile</span>
</button>

{/* Username */}
<h2 className="text-3xl font-bold mb-1" style={{ color: "#be9661" }}>
{username}
Expand All @@ -236,7 +246,7 @@ export default async function ProfilePage() {
Level
</span>
<span className="text-2xl font-bold" style={{ color: "#be9661" }}>
42
0
</span>
</div>

Expand All @@ -255,7 +265,7 @@ export default async function ProfilePage() {
XP
</span>
<span className="text-2xl font-bold" style={{ color: "#be9661" }}>
12,847
0
</span>
</div>

Expand All @@ -274,32 +284,14 @@ export default async function ProfilePage() {
Quests
</span>
<span className="text-2xl font-bold" style={{ color: "#be9661" }}>
127
0/8
</span>
</div>
</div>

{/* Detailed Info */}
{/* Class */}
<div className="flex items-start gap-3 mb-4 relative">
<div
className="absolute left-0 top-0 bottom-0 rounded"
style={{
backgroundColor: "#be9661",
width: "4px",
marginTop: "-2px",
marginBottom: "-2px",
}}
/>
<div className="pl-3">
<span className="text-sm block mb-1" style={{ color: "#A0A0A0" }}>
Class
</span>
<span className="text-base block" style={{ color: "#E0E0E0" }}>
Python Warrior
</span>
</div>
</div>
<EditClass currentClass={userClass || 'Not set'} currentClassEnum={userClassEnum} userId={user.id} />

{/* Guild */}
<div className="flex items-start gap-3 mb-4 relative">
Expand Down Expand Up @@ -369,5 +361,4 @@ export default async function ProfilePage() {
</div>
</main>
);
}

}
11 changes: 8 additions & 3 deletions src/components/back-to-dashboard-link.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,15 @@ export default function BackToDashBoardLink() {
return (
<Link
href="/dashboard"
className="mb-4 inline-block text-indigo-600 hover:text-indigo-800 transition hover:scale-105 group"
className="mb-6 inline-flex items-center gap-2 px-4 py-2 rounded-lg transition-opacity hover:opacity-80"
style={{
backgroundColor: "#232331",
color: "#be9661",
border: "0.5px solid rgba(190, 150, 97, 0.3)",
}}
>
<ArrowLeft className="inline-block w-5 h-5 mr-1" />
<span className="text-sm font-medium">Back to Dashboard</span>
<ArrowLeft size={18} />
<span>Back to Dashboard</span>
</Link>
);
}
177 changes: 177 additions & 0 deletions src/components/edit-class.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
"use client";

import { useState, useEffect } from "react";
import { createClient } from "../lib/supabase/client";
import { Pencil, Check, X } from "lucide-react";

// Map enum values to display names
const CLASS_OPTIONS = [
{ value: 'warrior', label: 'Python Warrior', comingSoon: false },
{ value: 'mage', label: 'Java Mage', comingSoon: true },
{ value: 'rogue', label: 'C++ Rogue', comingSoon: true },
] as const;

interface EditClassProps {
currentClass: string;
currentClassEnum: string | null;
userId: string;
}

export default function EditClass({ currentClass, currentClassEnum, userId }: EditClassProps) {
const [isEditing, setIsEditing] = useState(false);
// Default to 'warrior' (Python Warrior) which is available
const [selectedClassEnum, setSelectedClassEnum] = useState<string>(currentClassEnum || 'warrior');
const [isSaving, setIsSaving] = useState(false);
const supabase = createClient();

useEffect(() => {
// If current class is coming soon or null, default to warrior (Python Warrior)
const currentOption = CLASS_OPTIONS.find(opt => opt.value === currentClassEnum);
if (!currentClassEnum || currentOption?.comingSoon) {
setSelectedClassEnum('warrior');
} else {
setSelectedClassEnum(currentClassEnum);
}
}, [currentClassEnum]);

const handleSave = async () => {
setIsSaving(true);
try {
// Use upsert to insert if profile doesn't exist, or update if it does
const { data, error } = await supabase
.from('profiles')
.upsert({
id: userId,
class: selectedClassEnum
}, {
onConflict: 'id'
})
.select();

if (error) {
console.error("Supabase error:", error);
throw error;
}

console.log("Class saved successfully:", data);
setIsEditing(false);
// Refresh the page to show updated class
window.location.reload();
} catch (error) {
console.error("Error updating class:", error);
alert(`Failed to update class: ${error instanceof Error ? error.message : 'Unknown error'}`);
} finally {
setIsSaving(false);
}
};

const handleCancel = () => {
// Reset to current class or default to warrior if coming soon
const currentOption = CLASS_OPTIONS.find(opt => opt.value === currentClassEnum);
if (!currentClassEnum || currentOption?.comingSoon) {
setSelectedClassEnum('warrior');
} else {
setSelectedClassEnum(currentClassEnum);
}
setIsEditing(false);
};

if (isEditing) {
return (
<div className="flex items-start gap-3 mb-4 relative">
<div
className="absolute left-0 top-0 bottom-0 rounded"
style={{
backgroundColor: "#be9661",
width: "4px",
marginTop: "-2px",
marginBottom: "-2px",
}}
/>
<div className="pl-3 flex-1">
<span className="text-sm block mb-2" style={{ color: "#A0A0A0" }}>
Class
</span>
<select
value={selectedClassEnum}
onChange={(e) => setSelectedClassEnum(e.target.value)}
className="w-full px-3 py-2 rounded-lg mb-2"
style={{
backgroundColor: "#232331",
color: "#E0E0E0",
border: "0.5px solid rgba(190, 150, 97, 0.3)",
}}
>
{CLASS_OPTIONS.map((option) => (
<option key={option.value} value={option.value}>
{option.label}{option.comingSoon ? ' - Coming Soon' : ''}
</option>
))}
</select>
<div className="flex gap-2">
<button
onClick={handleSave}
disabled={isSaving}
className="px-3 py-1 rounded-lg flex items-center gap-1"
style={{
backgroundColor: "#be9661",
color: "#191922",
border: "none",
}}
>
<Check size={14} />
<span>{isSaving ? "Saving..." : "Save"}</span>
</button>
<button
onClick={handleCancel}
disabled={isSaving}
className="px-3 py-1 rounded-lg flex items-center gap-1"
style={{
backgroundColor: "#232331",
color: "#E0E0E0",
border: "0.5px solid rgba(190, 150, 97, 0.3)",
}}
>
<X size={14} />
<span>Cancel</span>
</button>
</div>
</div>
</div>
);
}

return (
<div className="flex items-start gap-3 mb-4 relative">
<div
className="absolute left-0 top-0 bottom-0 rounded"
style={{
backgroundColor: "#be9661",
width: "4px",
marginTop: "-2px",
marginBottom: "-2px",
}}
/>
<div className="pl-3 flex-1">
<div className="flex items-center justify-between">
<div>
<span className="text-sm block mb-1" style={{ color: "#A0A0A0" }}>
Class
</span>
<span className="text-base block" style={{ color: "#E0E0E0" }}>
{currentClass || 'Not set'}
</span>
</div>
<button
onClick={() => setIsEditing(true)}
className="p-1 rounded transition-opacity hover:opacity-80"
style={{ color: "#be9661" }}
title="Edit class"
>
<Pencil size={16} />
</button>
</div>
</div>
</div>
);
}
7 changes: 7 additions & 0 deletions src/lib/types/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,10 @@ export type QuizData = {
id: string;
questions: Question[];
};

export type Profile = {
id: string;
class: string;
created_at: string;
updated_at: string;
};