diff --git a/client/App.tsx b/client/App.tsx index 2bacc9c0..8445abcd 100644 --- a/client/App.tsx +++ b/client/App.tsx @@ -119,16 +119,22 @@ const App = () => ( } /> {/* Service routes */} - - - - } /> - - - - } /> + + + + } + /> + + + + } + /> } /> } /> ; + allowedRealms?: Array< + "game_developer" | "client" | "community_member" | "customer" | "staff" + >; allowedRoles?: string[]; children: React.ReactElement; } -export default function RequireAccess({ allowedRealms, allowedRoles, children }: RequireAccessProps) { +export default function RequireAccess({ + allowedRealms, + allowedRoles, + children, +}: RequireAccessProps) { const { user, profile, roles } = useAuth(); const location = useLocation(); - const realmOk = !allowedRealms || allowedRealms.includes((profile as any)?.user_type); - const rolesOk = !allowedRoles || (Array.isArray(roles) && roles.some(r => allowedRoles.includes(r.toLowerCase()))); + const realmOk = + !allowedRealms || allowedRealms.includes((profile as any)?.user_type); + const rolesOk = + !allowedRoles || + (Array.isArray(roles) && + roles.some((r) => allowedRoles.includes(r.toLowerCase()))); - if (!user) return ; + if (!user) + return ( + + ); if (!realmOk || !rolesOk) return ; return children; diff --git a/client/components/admin/AdminMentorshipManager.tsx b/client/components/admin/AdminMentorshipManager.tsx index 40a66777..5479d513 100644 --- a/client/components/admin/AdminMentorshipManager.tsx +++ b/client/components/admin/AdminMentorshipManager.tsx @@ -12,7 +12,13 @@ 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 { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select"; import { Switch } from "@/components/ui/switch"; import { aethexToast } from "@/lib/aethex-toast"; import { cn } from "@/lib/utils"; @@ -49,11 +55,27 @@ interface MentorshipRequestRow { 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; + 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; +const statusOptions = [ + "all", + "pending", + "accepted", + "rejected", + "cancelled", +] as const; type StatusFilter = (typeof statusOptions)[number]; @@ -88,7 +110,10 @@ export default function AdminMentorshipManager() { 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) }); + aethexToast.error({ + title: "Failed to load mentors", + description: String(e?.message || e), + }); setMentors([]); } finally { setLoadingMentors(false); @@ -101,12 +126,17 @@ export default function AdminMentorshipManager() { const params = new URLSearchParams(); params.set("limit", "100"); if (statusFilter !== "all") params.set("status", statusFilter); - const resp = await fetch(`/api/mentorship/requests/all?${params.toString()}`); + const resp = await fetch( + `/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) }); + aethexToast.error({ + title: "Failed to load requests", + description: String(e?.message || e), + }); setRequests([]); } finally { setLoadingRequests(false); @@ -146,20 +176,35 @@ export default function AdminMentorshipManager() { 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, + hourly_rate: + typeof merged.hourly_rate === "number" ? merged.hourly_rate : null, }; const resp = await fetch("/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")); + 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 }); + 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) }); + aethexToast.error({ + title: "Save failed", + description: String(e?.message || e), + }); } }; @@ -168,7 +213,12 @@ export default function AdminMentorshipManager() { 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(" ")] + const haystack = [ + up.full_name, + up.username, + m.bio, + (m.expertise || []).join(" "), + ] .filter(Boolean) .join(" ") .toLowerCase(); @@ -184,19 +234,40 @@ export default function AdminMentorshipManager() { Mentors directory - Search, filter, and update mentor availability. + + Search, filter, and update mentor availability. +
- setMentorQ(e.target.value)} /> + setMentorQ(e.target.value)} + />
- - + +
-
@@ -230,7 +301,13 @@ export default function AdminMentorshipManager() { Add tag {expertiseFilter.length > 0 && ( - + )}
@@ -243,9 +320,13 @@ export default function AdminMentorshipManager() {
{loadingMentors ? ( -
Loading mentors…
+
+ Loading mentors… +
) : filteredMentors.length === 0 ? ( -
No mentors found.
+
+ No mentors found. +
) : (
@@ -259,15 +340,34 @@ export default function AdminMentorshipManager() {
-
{up.full_name || up.username || m.user_id}
- {merged.available ? "available" : "unavailable"} +
+ {up.full_name || up.username || m.user_id} +
+ + {merged.available ? "available" : "unavailable"} + +
+
+ {up.username ? `@${up.username}` : null}
-
{up.username ? `@${up.username}` : null}
- +
setDraft(m.user_id, { hourly_rate: e.target.value === "" ? null : Number(e.target.value) })} + value={ + typeof merged.hourly_rate === "number" + ? merged.hourly_rate + : (merged.hourly_rate ?? "") + } + onChange={(e) => + 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, { available: v }) + } + /> + + {merged.available + ? "Accepting requests" + : "Not accepting"} +
- + setDraft(m.user_id, { expertise: e.target.value.split(",").map((s) => s.trim()).filter(Boolean) })} + value={(Array.isArray(merged.expertise) + ? merged.expertise + : [] + ).join(", ")} + onChange={(e) => + setDraft(m.user_id, { + expertise: e.target.value + .split(",") + .map((s) => s.trim()) + .filter(Boolean), + }) + } />
@@ -302,14 +434,22 @@ export default function AdminMentorshipManager() {