diff --git a/client/components/admin/AdminAchievementManager.tsx b/client/components/admin/AdminAchievementManager.tsx new file mode 100644 index 00000000..a4b46df5 --- /dev/null +++ b/client/components/admin/AdminAchievementManager.tsx @@ -0,0 +1,307 @@ +import { useCallback, useEffect, useMemo, useState } from "react"; +import { + Card, + CardContent, + CardDescription, + CardHeader, + CardTitle, +} from "@/components/ui/card"; +import { Badge } from "@/components/ui/badge"; +import { Button } from "@/components/ui/button"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select"; +import { ScrollArea } from "@/components/ui/scroll-area"; +import { aethexToast } from "@/lib/aethex-toast"; +import { + aethexAchievementService, + type AethexAchievement, + type AethexUserProfile, +} from "@/lib/aethex-database-adapter"; +import { formatDistanceToNowStrict } from "date-fns"; +import { Award, Gift, Loader2, Sparkles } from "lucide-react"; + +interface AdminAchievementManagerProps { + targetUser: AethexUserProfile | null; +} + +const AdminAchievementManager = ({ + targetUser, +}: AdminAchievementManagerProps) => { + const [achievements, setAchievements] = useState([]); + const [userAchievements, setUserAchievements] = useState([]); + const [selectedAchievementId, setSelectedAchievementId] = useState(""); + const [loadingList, setLoadingList] = useState(false); + const [loadingUserAchievements, setLoadingUserAchievements] = useState(false); + const [awarding, setAwarding] = useState(false); + const [activatingRewards, setActivatingRewards] = useState(false); + + const loadAchievements = useCallback(async () => { + setLoadingList(true); + try { + const list = await aethexAchievementService.getAllAchievements(); + setAchievements(list); + } catch (error) { + console.warn("Failed to load achievements", error); + setAchievements([]); + } finally { + setLoadingList(false); + } + }, []); + + const loadUserAchievements = useCallback( + async (userId: string) => { + setLoadingUserAchievements(true); + try { + const list = await aethexAchievementService.getUserAchievements(userId); + setUserAchievements(list); + } catch (error) { + console.warn("Failed to load user achievements", error); + setUserAchievements([]); + } finally { + setLoadingUserAchievements(false); + } + }, + [], + ); + + useEffect(() => { + loadAchievements().catch(() => undefined); + }, [loadAchievements]); + + useEffect(() => { + if (targetUser?.id) { + loadUserAchievements(targetUser.id).catch(() => undefined); + } else { + setUserAchievements([]); + } + }, [targetUser?.id, loadUserAchievements]); + + const selectedAchievement = useMemo( + () => achievements.find((achievement) => achievement.id === selectedAchievementId) ?? null, + [achievements, selectedAchievementId], + ); + + const awardAchievement = async () => { + if (!targetUser?.id || !selectedAchievementId) { + aethexToast.error({ + title: "Select achievement", + description: "Choose an achievement and member before awarding.", + }); + return; + } + setAwarding(true); + try { + await aethexAchievementService.awardAchievement( + targetUser.id, + selectedAchievementId, + ); + aethexToast.success({ + title: "Achievement awarded", + description: `${selectedAchievement?.name ?? "Achievement"} granted to ${targetUser.full_name ?? targetUser.email ?? "member"}.`, + }); + await loadUserAchievements(targetUser.id); + } catch (error: any) { + console.error("Failed to award achievement", error); + aethexToast.error({ + title: "Award failed", + description: error?.message || "Supabase rejected the award operation.", + }); + } finally { + setAwarding(false); + } + }; + + const activateRewards = async () => { + if (!targetUser) { + aethexToast.error({ + title: "Select member", + description: "Choose a member before running rewards automation.", + }); + return; + } + setActivatingRewards(true); + try { + const result = await aethexAchievementService.activateCommunityRewards({ + email: targetUser.email ?? undefined, + username: targetUser.username ?? undefined, + }); + if (!result) { + aethexToast.error({ + title: "Activation failed", + description: "No rewards were activated. Check server logs for details.", + }); + } else { + const awarded = result.awardedAchievementIds?.length ?? 0; + aethexToast.success({ + title: "Rewards activated", + description: + awarded > 0 + ? `${awarded} achievement${awarded === 1 ? "" : "s"} added for ${targetUser.full_name ?? targetUser.email ?? "member"}.` + : "Rewards automation completed with no new awards.", + }); + if (targetUser.id) { + await loadUserAchievements(targetUser.id); + } + } + } catch (error: any) { + console.error("Failed to activate rewards", error); + aethexToast.error({ + title: "Automation failed", + description: error?.message || "Could not trigger rewards function.", + }); + } finally { + setActivatingRewards(false); + } + }; + + return ( + + + + + Achievement control + + + Grant rewards, run automations, and inspect achievement history. + + + +
+
+

Target member

+ {targetUser ? ( +
+

+ {targetUser.full_name ?? targetUser.username ?? targetUser.email ?? "Unknown"} +

+

+ {targetUser.email ?? "No email on record"} +

+
+ ) : ( +

+ Select a member from the directory to enable rewards management. +

+ )} +
+
+

Choose achievement

+ +
+
+ +
+ + +
+ +
+
+

Achievement history

+ {loadingUserAchievements ? ( + + Loading… + + ) : null} +
+ {targetUser ? ( + + {userAchievements.length ? ( +
    + {userAchievements.map((achievement) => ( +
  • +
    +

    {achievement.name}

    + {achievement.description ? ( +

    + {achievement.description} +

    + ) : null} +
    + + {achievement.xp_reward ?? 0} XP + +
  • + ))} +
+ ) : ( +

+ No achievements recorded yet. +

+ )} +
+ ) : ( +

+ Select a member to view and manage their achievements. +

+ )} +
+ + {selectedAchievement ? ( +
+

+ {selectedAchievement.name} +

+ {selectedAchievement.description ? ( +

{selectedAchievement.description}

+ ) : null} +
+ {selectedAchievement.xp_reward ?? 0} XP + ID: {selectedAchievement.id} + + Created {formatDistanceToNowStrict(new Date(selectedAchievement.created_at), { + addSuffix: true, + })} + +
+
+ ) : null} +
+
+ ); +}; + +export default AdminAchievementManager;