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 { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from "@/components/ui/table"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select"; import { ScrollArea } from "@/components/ui/scroll-area"; import { Textarea } from "@/components/ui/textarea"; import { aethexRoleService, aethexUserService, type AethexUserProfile, } from "@/lib/aethex-database-adapter"; import { aethexToast } from "@/lib/aethex-toast"; import { cn } from "@/lib/utils"; import { BadgeCheck, Loader2, Plus, RefreshCw, ShieldCheck, UserCog, } from "lucide-react"; const roleOptions = [ "owner", "admin", "founder", "moderator", "creator", "mentor", "staff", "member", ]; const experienceOptions = ["beginner", "intermediate", "advanced", "expert"]; const userTypeOptions = [ "game_developer", "client", "community_member", "customer", ]; interface AdminMemberManagerProps { profiles: AethexUserProfile[]; selectedId: string | null; onSelectedIdChange: (id: string) => void; onRefresh: () => Promise; ownerEmail: string; } interface ProfileDraft { full_name: string; location: string; bio: string; experience_level: string; user_type: string; level: string; total_xp: string; loyalty_points: string; } const buildProfileDraft = (profile: AethexUserProfile): ProfileDraft => ({ full_name: profile.full_name ?? "", location: profile.location ?? "", bio: profile.bio ?? "", experience_level: profile.experience_level ?? "beginner", user_type: profile.user_type ?? "game_developer", level: profile.level != null ? String(profile.level) : "", total_xp: profile.total_xp != null ? String(profile.total_xp) : "", loyalty_points: (profile as any).loyalty_points != null ? String((profile as any).loyalty_points) : "0", }); const ensureOwnerRoles = ( roles: string[], profile: AethexUserProfile | null, ownerEmail: string, ) => { if (!profile) return roles; if ((profile.email ?? "").toLowerCase() !== ownerEmail.toLowerCase()) { return roles; } const required = new Set(["owner", "admin", "founder", ...roles]); return Array.from(required); }; const normalizeRoles = (roles: string[]): string[] => { const normalized = roles .map((role) => role.trim().toLowerCase()) .filter((role) => role.length > 0); const unique = Array.from(new Set(normalized)); return unique.length ? unique : ["member"]; }; const AdminMemberManager = ({ profiles, selectedId, onSelectedIdChange, onRefresh, ownerEmail, }: AdminMemberManagerProps) => { const [roles, setRoles] = useState([]); const [loadingRoles, setLoadingRoles] = useState(false); const [savingRoles, setSavingRoles] = useState(false); const [savingProfile, setSavingProfile] = useState(false); const [customRole, setCustomRole] = useState(""); const [query, setQuery] = useState(""); const [profileDraft, setProfileDraft] = useState(null); const selectedProfile = useMemo( () => profiles && Array.isArray(profiles) ? (profiles.find((profile) => profile.id === selectedId) ?? null) : null, [profiles, selectedId], ); useEffect(() => { if (!selectedId && profiles.length) { onSelectedIdChange(profiles[0].id); } }, [profiles, selectedId, onSelectedIdChange]); const loadRoles = useCallback(async (id: string) => { setLoadingRoles(true); try { const fetched = await aethexRoleService.getUserRoles(id); setRoles(normalizeRoles(fetched)); } catch (error) { console.warn("Failed to load user roles", error); setRoles(["member"]); } finally { setLoadingRoles(false); } }, []); useEffect(() => { if (selectedProfile) { setProfileDraft(buildProfileDraft(selectedProfile)); loadRoles(selectedProfile.id).catch(() => undefined); } else { setProfileDraft(null); setRoles([]); } }, [selectedProfile, loadRoles]); const filteredProfiles = useMemo(() => { if (!profiles || !Array.isArray(profiles)) return []; const value = query.trim().toLowerCase(); if (!value) return profiles; return profiles.filter((profile) => { const haystack = [ profile.full_name, profile.username, profile.email, profile.bio, profile.location, profile.role, ] .filter(Boolean) .join(" ") .toLowerCase(); return haystack.includes(value); }); }, [profiles, query]); const handleRoleToggle = (role: string) => { setRoles((prev) => prev.includes(role.toLowerCase()) ? prev.filter((item) => item !== role.toLowerCase()) : [...prev, role.toLowerCase()], ); }; const addCustomRole = () => { const value = customRole.trim().toLowerCase(); if (!value) return; setRoles((prev) => (prev.includes(value) ? prev : [...prev, value])); setCustomRole(""); }; const saveRoles = async () => { if (!selectedProfile) return; setSavingRoles(true); try { const enforced = ensureOwnerRoles( normalizeRoles(roles), selectedProfile, ownerEmail, ); await aethexRoleService.setUserRoles(selectedProfile.id, enforced); setRoles(enforced); aethexToast.success({ title: "Roles updated", description: `${selectedProfile.full_name ?? selectedProfile.email ?? "Member"} now has ${enforced.join(", ")}.`, }); } catch (error: any) { console.error("Failed to set user roles", error); aethexToast.error({ title: "Role update failed", description: error?.message || "Unable to update roles. Check Supabase policies.", }); } finally { setSavingRoles(false); } }; const saveProfile = async () => { if (!selectedProfile || !profileDraft) return; setSavingProfile(true); try { const updates: Partial = { full_name: profileDraft.full_name.trim() || null, location: profileDraft.location.trim() || null, bio: profileDraft.bio.trim() || null, experience_level: profileDraft.experience_level as any, user_type: profileDraft.user_type as any, }; if (profileDraft.level.trim().length) { updates.level = Number(profileDraft.level) || 0; } if (profileDraft.total_xp.trim().length) { updates.total_xp = Number(profileDraft.total_xp) || 0; } if (profileDraft.loyalty_points.trim().length) { (updates as any).loyalty_points = Number(profileDraft.loyalty_points) || 0; } await aethexUserService.updateProfile(selectedProfile.id, updates); aethexToast.success({ title: "Profile updated", description: `${selectedProfile.full_name ?? selectedProfile.email ?? "Member"} profile saved.`, }); await onRefresh(); } catch (error: any) { console.error("Failed to update profile", error); const extractErrorMessage = (err: any) => { if (!err) return "Supabase rejected the update. Review payload and RLS policies."; if (typeof err === "string") return err; if (err.message) return err.message; try { return JSON.stringify(err); } catch (e) { return String(err); } }; aethexToast.error({ title: "Profile update failed", description: extractErrorMessage(error), }); } finally { setSavingProfile(false); } }; const resetDraft = () => { if (!selectedProfile) return; setProfileDraft(buildProfileDraft(selectedProfile)); }; return (
Directory Search and select members to administer.
setQuery(event.target.value)} className="bg-background/60" />
Name Email Role {filteredProfiles.map((profile) => { const active = profile.id === selectedId; return ( onSelectedIdChange(profile.id)} >
{profile.full_name || profile.username || "Unknown"} {profile.username}
{profile.email || "—"} {(profile.role || "member").toLowerCase()}
); })} {!filteredProfiles.length ? ( No members found. ) : null}
Member controls Update profile data, loyalty, and Supabase attributes. {selectedProfile && profileDraft ? (
setProfileDraft((draft) => draft ? { ...draft, full_name: event.target.value } : draft, ) } className="bg-background/60" />
setProfileDraft((draft) => draft ? { ...draft, location: event.target.value } : draft, ) } className="bg-background/60" />