diff --git a/api/achievements/activate.ts b/api/achievements/activate.ts index b483f811..e52c6500 100644 --- a/api/achievements/activate.ts +++ b/api/achievements/activate.ts @@ -48,10 +48,7 @@ const CORE_ACHIEVEMENTS = [ const DEFAULT_TARGET_EMAIL = "mrpiglr@gmail.com"; const DEFAULT_TARGET_USERNAME = "mrpiglr"; -export default async function handler( - req: VercelRequest, - res: VercelResponse, -) { +export default async function handler(req: VercelRequest, res: VercelResponse) { if (req.method !== "POST") { return res.status(405).json({ error: "Method not allowed" }); } @@ -68,20 +65,18 @@ export default async function handler( // Ensure core achievements exist const achievementResults = await Promise.all( CORE_ACHIEVEMENTS.map(async (achievement) => { - const { error } = await admin - .from("achievements") - .upsert( - { - id: achievement.id, - name: achievement.name, - description: achievement.description, - icon: achievement.icon, - badge_color: achievement.badgeColor, - xp_reward: achievement.xpReward, - created_at: nowIso, - }, - { onConflict: "id" }, - ); + const { error } = await admin.from("achievements").upsert( + { + id: achievement.id, + name: achievement.name, + description: achievement.description, + icon: achievement.icon, + badge_color: achievement.badgeColor, + xp_reward: achievement.xpReward, + created_at: nowIso, + }, + { onConflict: "id" }, + ); if (error) { throw error; @@ -92,14 +87,8 @@ export default async function handler( // Normalise profile progression defaults await Promise.all([ - admin - .from("user_profiles") - .update({ level: 1 }) - .is("level", null), - admin - .from("user_profiles") - .update({ total_xp: 0 }) - .is("total_xp", null), + admin.from("user_profiles").update({ level: 1 }).is("level", null), + admin.from("user_profiles").update({ total_xp: 0 }).is("total_xp", null), admin .from("user_profiles") .update({ user_type: "game_developer" }) @@ -108,7 +97,9 @@ export default async function handler( // Locate target user const normalizedEmail = (targetEmail || DEFAULT_TARGET_EMAIL).toLowerCase(); - const normalizedUsername = (targetUsername || DEFAULT_TARGET_USERNAME).toLowerCase(); + const normalizedUsername = ( + targetUsername || DEFAULT_TARGET_USERNAME + ).toLowerCase(); let targetUserId: string | null = null; @@ -171,7 +162,9 @@ export default async function handler( throw existingError; } - const existingIds = new Set((existingRows ?? []).map((row: any) => row.achievement_id)); + const existingIds = new Set( + (existingRows ?? []).map((row: any) => row.achievement_id), + ); for (const achievement of CORE_ACHIEVEMENTS) { if (existingIds.has(achievement.id)) { diff --git a/client/App.tsx b/client/App.tsx index 381a74b1..5977f666 100644 --- a/client/App.tsx +++ b/client/App.tsx @@ -67,12 +67,18 @@ const App = () => ( } /> } /> - } /> + } + /> } /> - } /> + } + /> } />
- {isLegendary ? "Legendary Status" : `Progress to Level ${level + 1}`} + {isLegendary + ? "Legendary Status" + : `Progress to Level ${level + 1}`} - {isLegendary ? "MAX" : `${(progressToNextLevel || 0).toFixed(0)}%`} + {isLegendary + ? "MAX" + : `${(progressToNextLevel || 0).toFixed(0)}%`}
diff --git a/client/lib/aethex-database-adapter.ts b/client/lib/aethex-database-adapter.ts index 2573a0ac..5309c114 100644 --- a/client/lib/aethex-database-adapter.ts +++ b/client/lib/aethex-database-adapter.ts @@ -34,7 +34,9 @@ const ensureSupabase = () => { const MS_PER_DAY = 1000 * 60 * 60 * 24; const startOfUTC = (date: Date) => - new Date(Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate())); + new Date( + Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate()), + ); const isoDate = (date: Date) => date.toISOString().slice(0, 10); @@ -50,14 +52,14 @@ const normalizeProfile = ( ): AethexUserProfile => ({ ...(row as AethexUserProfile), email: email ?? (row as any)?.email, - username: - (row as any)?.username ?? email?.split("@")[0] ?? "user", + username: (row as any)?.username ?? email?.split("@")[0] ?? "user", onboarded: true, role: (row as any)?.role ?? "developer", loyalty_points: (row as any)?.loyalty_points ?? 0, current_streak: (row as any)?.current_streak ?? 0, longest_streak: - (row as any)?.longest_streak ?? Math.max((row as any)?.current_streak ?? 0, 0), + (row as any)?.longest_streak ?? + Math.max((row as any)?.current_streak ?? 0, 0), last_streak_at: (row as any)?.last_streak_at ?? null, }); @@ -302,16 +304,15 @@ export const aethexUserService = { return normalizeProfile(data); }, - async getProfileByUsername(username: string): Promise { + async getProfileByUsername( + username: string, + ): Promise { const normalized = username?.trim(); if (!normalized) return null; ensureSupabase(); - const { - data, - error, - } = await supabase + const { data, error } = await supabase .from("user_profiles") .select("*") .eq("username", normalized) @@ -332,10 +333,7 @@ export const aethexUserService = { return normalizeProfile(data); } - const { - data: fallback, - error: fallbackError, - } = await supabase + const { data: fallback, error: fallbackError } = await supabase .from("user_profiles") .select("*") .ilike("username", normalized) @@ -379,13 +377,11 @@ export const aethexUserService = { } return ((data as any[]) || []).map((row) => - normalizeProfile( - { - ...(row as AethexUserProfile), - user_type: (row as any).user_type || "game_developer", - experience_level: (row as any).experience_level || "beginner", - }, - ), + normalizeProfile({ + ...(row as AethexUserProfile), + user_type: (row as any).user_type || "game_developer", + experience_level: (row as any).experience_level || "beginner", + }), ); }, @@ -693,7 +689,10 @@ export const aethexAchievementService = { } }, - async updateUserXPAndLevel(userId: string, xpGained: number | null = null): Promise { + async updateUserXPAndLevel( + userId: string, + xpGained: number | null = null, + ): Promise { ensureSupabase(); const { data: profile, error } = await supabase @@ -719,7 +718,8 @@ export const aethexAchievementService = { const updates: Record = {}; if ("total_xp" in currentProfile) updates.total_xp = newTotalXP; if ("level" in currentProfile) updates.level = newLevel; - if ("loyalty_points" in currentProfile) updates.loyalty_points = newLoyaltyPoints; + if ("loyalty_points" in currentProfile) + updates.loyalty_points = newLoyaltyPoints; if (Object.keys(updates).length > 0) { const { error: updateError } = await supabase @@ -748,11 +748,16 @@ export const aethexAchievementService = { return; } } catch (error) { - console.warn("Edge function award failed, attempting direct Supabase insert", error); + console.warn( + "Edge function award failed, attempting direct Supabase insert", + error, + ); } const achievements = await this.getAllAchievements(); - const byName = new Map(achievements.map((item) => [item.name, item.id] as const)); + const byName = new Map( + achievements.map((item) => [item.name, item.id] as const), + ); const names = ["Welcome to AeThex", "AeThex Explorer"]; for (const name of names) { diff --git a/client/lib/onboarding.integration.spec.ts b/client/lib/onboarding.integration.spec.ts index dc9649e5..b5abeb9f 100644 --- a/client/lib/onboarding.integration.spec.ts +++ b/client/lib/onboarding.integration.spec.ts @@ -36,7 +36,8 @@ const fetchMock = fetch as unknown as Mock; vi.mock("@/lib/supabase", () => { const userProfiles = new Map(); - const userAchievements: Array<{ user_id: string; achievement_id: string }> = []; + const userAchievements: Array<{ user_id: string; achievement_id: string }> = + []; const achievementsCatalog = [ { @@ -59,8 +60,12 @@ vi.mock("@/lib/supabase", () => { }, ]; - const achievementsById = new Map(achievementsCatalog.map((item) => [item.id, item] as const)); - const achievementsByName = new Map(achievementsCatalog.map((item) => [item.name, item] as const)); + const achievementsById = new Map( + achievementsCatalog.map((item) => [item.id, item] as const), + ); + const achievementsByName = new Map( + achievementsCatalog.map((item) => [item.name, item] as const), + ); const profileDefaults = (id: string) => ({ id, @@ -201,7 +206,8 @@ vi.mock("@/lib/supabase", () => { for (const entry of entries) { const exists = userAchievements.some( (item) => - item.user_id === entry.user_id && item.achievement_id === entry.achievement_id, + item.user_id === entry.user_id && + item.achievement_id === entry.achievement_id, ); if (exists) { error = { code: "23505" }; @@ -215,10 +221,12 @@ vi.mock("@/lib/supabase", () => { return { eq(_column: string, userId: string) { return { - data: userAchievements.map((entry) => ({ - ...entry, - achievements: achievementsById.get(entry.achievement_id), - })).filter((entry) => entry.user_id === userId), + data: userAchievements + .map((entry) => ({ + ...entry, + achievements: achievementsById.get(entry.achievement_id), + })) + .filter((entry) => entry.user_id === userId), error: null, }; }, @@ -251,9 +259,11 @@ vi.mock("@/lib/supabase", () => { }), }, from(table: string) { - return tableMap[table] ?? { - select: () => ({ data: [], error: null }), - }; + return ( + tableMap[table] ?? { + select: () => ({ data: [], error: null }), + } + ); }, channel: () => ({ on: () => ({}), diff --git a/client/pages/Community.tsx b/client/pages/Community.tsx index 50a1bede..91c86d1c 100644 --- a/client/pages/Community.tsx +++ b/client/pages/Community.tsx @@ -24,7 +24,13 @@ import LoadingScreen from "@/components/LoadingScreen"; import { aethexToast } from "@/lib/aethex-toast"; import { cn } from "@/lib/utils"; import { Link } from "react-router-dom"; -import { useCallback, useEffect, useRef, useState, type FormEvent } from "react"; +import { + useCallback, + useEffect, + useRef, + useState, + type FormEvent, +} from "react"; import { AlertTriangle, ArrowDown, @@ -285,14 +291,17 @@ function EventCard({ }; const statusStyles: Record = { - "Registration Open": "bg-gradient-to-r from-emerald-500/20 to-aethex-500/30 text-emerald-200 border border-emerald-400/40", + "Registration Open": + "bg-gradient-to-r from-emerald-500/20 to-aethex-500/30 text-emerald-200 border border-emerald-400/40", Recurring: "bg-blue-500/10 text-blue-200 border border-blue-400/40", Upcoming: "bg-orange-500/10 text-orange-200 border border-orange-400/40", Waitlist: "bg-amber-500/10 text-amber-200 border border-amber-400/40", }; const buttonLabel = isRegistered ? "Manage Registration" : "Register"; - const submitLabel = isRegistered ? "Update Registration" : "Confirm Registration"; + const submitLabel = isRegistered + ? "Update Registration" + : "Confirm Registration"; return (
-

{event.title}

- +

+ {event.title} +

+ {event.status} {isRegistered && ( @@ -347,7 +363,9 @@ function EventCard({ @@ -367,7 +385,10 @@ function EventCard({ id={`name-${event.id}`} value={form.name} onChange={(eventChange) => - setForm((prev) => ({ ...prev, name: eventChange.target.value })) + setForm((prev) => ({ + ...prev, + name: eventChange.target.value, + })) } placeholder="Enter your name" required @@ -380,14 +401,19 @@ function EventCard({ type="email" value={form.email} onChange={(eventChange) => - setForm((prev) => ({ ...prev, email: eventChange.target.value })) + setForm((prev) => ({ + ...prev, + email: eventChange.target.value, + })) } placeholder="you@example.com" required />
- +
- +