diff --git a/client/lib/aethex-database-adapter.ts b/client/lib/aethex-database-adapter.ts index b1793bba..d3cd5bbc 100644 --- a/client/lib/aethex-database-adapter.ts +++ b/client/lib/aethex-database-adapter.ts @@ -768,6 +768,10 @@ export const aethexProjectService = { // Achievement Services (Supabase only) export const aethexAchievementService = { async getAllAchievements(): Promise { + if (!isSupabaseConfigured) { + return fallbackAchievementCatalog.map((achievement) => ({ ...achievement })); + } + const { data, error } = await supabase .from("achievements") .select("*") @@ -781,6 +785,14 @@ export const aethexAchievementService = { }, async getUserAchievements(userId: string): Promise { + if (!isSupabaseConfigured) { + const unlocked = fallbackUserAchievements.get(userId); + if (!unlocked) return []; + return Array.from(unlocked) + .map((achievementId) => fallbackAchievementsById.get(achievementId)) + .filter((value): value is AethexAchievement => Boolean(value)); + } + const { data, error } = await supabase .from("user_achievements") .select( @@ -801,6 +813,23 @@ export const aethexAchievementService = { }, async awardAchievement(userId: string, achievementId: string): Promise { + if (!isSupabaseConfigured) { + const achievement = + fallbackAchievementsById.get(achievementId) || + fallbackAchievementsByName.get(achievementId); + if (!achievement) { + return; + } + + const current = fallbackUserAchievements.get(userId) ?? new Set(); + if (!current.has(achievement.id)) { + current.add(achievement.id); + fallbackUserAchievements.set(userId, current); + await this.updateUserXPAndLevel(userId, achievement.xp_reward ?? 0); + } + return; + } + const { data: achievement, error: fetchError } = await supabase .from("achievements") .select("id, xp_reward") @@ -826,6 +855,25 @@ export const aethexAchievementService = { }, async updateUserXPAndLevel(userId: string, xpGained: number | null = null): Promise { + if (!isSupabaseConfigured) { + const xpDelta = xpGained ?? 0; + const currentProfile = (await mockAuth.getUserProfile(userId)) as any; + const currentXP = currentProfile?.total_xp ?? 0; + const newTotalXP = currentXP + xpDelta; + const newLevel = Math.floor(newTotalXP / 1000) + 1; + const newLoyaltyPoints = (currentProfile?.loyalty_points ?? 0) + xpDelta; + + await mockAuth.updateProfile( + userId, + { + total_xp: newTotalXP, + level: newLevel, + loyalty_points: newLoyaltyPoints, + } as any, + ); + return; + } + const { data: profile, error } = await supabase .from("user_profiles") .select("total_xp, level, loyalty_points") @@ -861,6 +909,23 @@ export const aethexAchievementService = { }, async checkAndAwardOnboardingAchievement(userId: string): Promise { + const awardDirectly = async () => { + const names = ["Welcome to AeThex", "AeThex Explorer"]; + const achievements = await this.getAllAchievements(); + const byName = new Map(achievements.map((item) => [item.name, item.id] as const)); + + for (const name of names) { + const id = byName.get(name); + if (!id) continue; + await this.awardAchievement(userId, id); + } + }; + + if (!isSupabaseConfigured) { + await awardDirectly(); + return; + } + try { const resp = await fetch(`/api/achievements/award`, { method: "POST", @@ -878,15 +943,7 @@ export const aethexAchievementService = { 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 names = ["Welcome to AeThex", "AeThex Explorer"]; - - for (const name of names) { - const id = byName.get(name); - if (!id) continue; - await this.awardAchievement(userId, id); - } + await awardDirectly(); }, async checkAndAwardProjectAchievements(userId: string): Promise {