import { useCallback, useEffect, useMemo, useRef, 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 { Textarea } from "@/components/ui/textarea"; import { ScrollArea } from "@/components/ui/scroll-area"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select"; import { Switch } from "@/components/ui/switch"; // API Base URL for fetch requests const API_BASE = import.meta.env.VITE_API_BASE || ""; import { aethexToast } from "@/lib/aethex-toast"; import { cn } from "@/lib/utils"; import { Loader2, RefreshCw, Save, Users, GraduationCap, MessageSquareText, DollarSign, Tag, } from "lucide-react"; interface MentorRow { user_id: string; bio?: string | null; expertise?: string[]; available?: boolean; hourly_rate?: number | null; user_profiles?: { id?: string; full_name?: string | null; username?: string | null; avatar_url?: string | null; bio?: string | null; } | null; } interface MentorshipRequestRow { id: string; mentor_id: string; mentee_id: string; message?: string | null; status: "pending" | "accepted" | "rejected" | "cancelled"; created_at?: string | null; mentor?: { id?: string; full_name?: string | null; username?: string | null; avatar_url?: string | null; } | null; mentee?: { id?: string; full_name?: string | null; username?: string | null; avatar_url?: string | null; } | null; } const statusOptions = [ "all", "pending", "accepted", "rejected", "cancelled", ] as const; type StatusFilter = (typeof statusOptions)[number]; export default function AdminMentorshipManager() { const [mentors, setMentors] = useState([]); const [loadingMentors, setLoadingMentors] = useState(false); const [mentorQ, setMentorQ] = useState(""); const [expertiseInput, setExpertiseInput] = useState(""); const [expertiseFilter, setExpertiseFilter] = useState([]); const [availableOnly, setAvailableOnly] = useState(true); const [requests, setRequests] = useState([]); const [loadingRequests, setLoadingRequests] = useState(false); const [statusFilter, setStatusFilter] = useState("pending"); const draftsRef = useRef>>({}); const expertiseQueryParam = useMemo(() => { return expertiseFilter.length ? expertiseFilter.join(",") : ""; }, [expertiseFilter]); const loadMentors = useCallback(async () => { setLoadingMentors(true); try { const params = new URLSearchParams(); params.set("limit", "50"); params.set("available", String(availableOnly)); if (expertiseQueryParam) params.set("expertise", expertiseQueryParam); if (mentorQ.trim()) params.set("q", mentorQ.trim()); const resp = await fetch(`${API_BASE}/api/mentors?${params.toString()}`); if (!resp.ok) throw new Error(await resp.text().catch(() => "Failed")); const data = await resp.json(); setMentors(Array.isArray(data) ? data : []); } catch (e: any) { aethexToast.error({ title: "Failed to load mentors", description: String(e?.message || e), }); setMentors([]); } finally { setLoadingMentors(false); } }, [availableOnly, expertiseQueryParam, mentorQ]); const loadRequests = useCallback(async () => { setLoadingRequests(true); try { const params = new URLSearchParams(); params.set("limit", "100"); if (statusFilter !== "all") params.set("status", statusFilter); const resp = await fetch( `${API_BASE}/api/mentorship/requests/all?${params.toString()}`, ); if (!resp.ok) throw new Error(await resp.text().catch(() => "Failed")); const data = await resp.json(); setRequests(Array.isArray(data) ? data : []); } catch (e: any) { aethexToast.error({ title: "Failed to load requests", description: String(e?.message || e), }); setRequests([]); } finally { setLoadingRequests(false); } }, [statusFilter]); useEffect(() => { loadMentors().catch(() => undefined); // eslint-disable-next-line react-hooks/exhaustive-deps }, [availableOnly, expertiseQueryParam]); useEffect(() => { loadRequests().catch(() => undefined); }, [loadRequests]); const setDraft = (userId: string, patch: Partial) => { draftsRef.current[userId] = { ...draftsRef.current[userId], ...patch }; setMentors((prev) => prev.slice()); }; const getDraftedMentor = (m: MentorRow): MentorRow => { const draft = draftsRef.current[m.user_id] || {}; return { ...m, bio: draft.bio ?? m.bio, expertise: draft.expertise ?? m.expertise, available: draft.available ?? m.available, hourly_rate: draft.hourly_rate ?? m.hourly_rate, }; }; const saveMentor = async (m: MentorRow) => { const merged = getDraftedMentor(m); try { const payload = { user_id: merged.user_id, bio: merged.bio ?? null, expertise: Array.isArray(merged.expertise) ? merged.expertise : [], available: !!merged.available, hourly_rate: typeof merged.hourly_rate === "number" ? merged.hourly_rate : null, }; const resp = await fetch(`${API_BASE}/api/mentors/apply`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(payload), }); if (!resp.ok) throw new Error(await resp.text().catch(() => "Save failed")); const updated = await resp.json(); draftsRef.current[m.user_id] = {}; setMentors((prev) => prev.map((row) => row.user_id === m.user_id ? { ...row, ...updated } : row, ), ); aethexToast.success({ title: "Mentor saved", description: merged.user_profiles?.full_name || merged.user_profiles?.username || merged.user_id, }); } catch (e: any) { aethexToast.error({ title: "Save failed", description: String(e?.message || e), }); } }; const filteredMentors = useMemo(() => { const q = mentorQ.trim().toLowerCase(); if (!q) return mentors; return mentors.filter((m) => { const up = m.user_profiles || {}; const haystack = [ up.full_name, up.username, m.bio, (m.expertise || []).join(" "), ] .filter(Boolean) .join(" ") .toLowerCase(); return haystack.includes(q); }); }, [mentors, mentorQ]); return (
Mentors directory
Search, filter, and update mentor availability.
setMentorQ(e.target.value)} />
setExpertiseInput(e.target.value)} onKeyDown={(e) => { if (e.key === "Enter") { const value = expertiseInput.trim(); if (value && !expertiseFilter.includes(value)) { setExpertiseFilter([...expertiseFilter, value]); } setExpertiseInput(""); } }} /> {expertiseFilter.length > 0 && ( )}
{expertiseFilter.map((tag) => ( {tag} ))}
{loadingMentors ? (
Loading mentors…
) : filteredMentors.length === 0 ? (
No mentors found.
) : (
{filteredMentors.map((m) => { const draft = draftsRef.current[m.user_id] || {}; const merged = getDraftedMentor(m); const up = merged.user_profiles || {}; return (
{up.full_name || up.username || m.user_id}
{merged.available ? "available" : "unavailable"}
{up.username ? `@${up.username}` : null}
setDraft(m.user_id, { hourly_rate: e.target.value === "" ? null : Number(e.target.value), }) } />
setDraft(m.user_id, { available: v }) } /> {merged.available ? "Accepting requests" : "Not accepting"}
setDraft(m.user_id, { expertise: e.target.value .split(",") .map((s) => s.trim()) .filter(Boolean), }) } />