diff --git a/client/pages/Directory.tsx b/client/pages/Directory.tsx new file mode 100644 index 00000000..f6baf3df --- /dev/null +++ b/client/pages/Directory.tsx @@ -0,0 +1,132 @@ +import Layout from "@/components/Layout"; +import { Badge } from "@/components/ui/badge"; +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; +import { Input } from "@/components/ui/input"; +import { Button } from "@/components/ui/button"; +import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"; +import { Tabs, TabsList, TabsTrigger, TabsContent } from "@/components/ui/tabs"; +import { supabase } from "@/lib/supabase"; +import { useEffect, useMemo, useState } from "react"; + +function initials(name?: string | null) { + const parts = (name || "").trim().split(/\s+/); + return ((parts[0] || "").charAt(0) + (parts[1] || "").charAt(0)).toUpperCase() || "A"; +} + +export default function Directory() { + const [query, setQuery] = useState(""); + const [hideAeThex, setHideAeThex] = useState(true); + const [devs, setDevs] = useState([]); + const [studios, setStudios] = useState([]); + + useEffect(() => { + supabase + .from("user_profiles" as any) + .select("id,full_name,username,avatar_url,location,user_type,experience_level,website_url,github_url,linkedin_url") + .limit(200) + .then(({ data }) => setDevs(data || [])); + + supabase + .from("teams" as any) + .select("id,name,description,visibility,created_at") + .limit(200) + .then(({ data }) => setStudios(data || [])); + }, []); + + const filteredDevs = useMemo(() => { + const q = query.trim().toLowerCase(); + return devs.filter((u) => { + if (hideAeThex && u.user_type === "staff") return false; + if (!q) return true; + return ( + (u.full_name || "").toLowerCase().includes(q) || + (u.username || "").toLowerCase().includes(q) || + (u.location || "").toLowerCase().includes(q) + ); + }); + }, [devs, query, hideAeThex]); + + const filteredStudios = useMemo(() => { + const q = query.trim().toLowerCase(); + return studios.filter((t) => { + if (!q) return true; + return ( + (t.name || "").toLowerCase().includes(q) || + (t.description || "").toLowerCase().includes(q) + ); + }); + }, [studios, query]); + + return ( + +
+
+
+
+ Directory +

Creators & Studios

+

Browse non‑AeThex creators and studios. Opt‑in visibility; public info only.

+
+
+ +
+
+
+ setQuery(e.target.value)} /> +
+
+ +
+ + + Developers + Studios + + + +
+ {filteredDevs.map((u) => ( + + + + + {initials(u.full_name || u.username)} + +
+
{u.full_name || u.username || "Developer"}
+
{u.location || "Global"}
+
+ {u.user_type} + {u.experience_level && {u.experience_level}} +
+
+
+
+ ))} +
+
+ + +
+ {filteredStudios.map((t) => ( + + + {t.name} + {t.visibility && {t.visibility}} + + +

{t.description || ""}

+
+
+ ))} +
+
+
+
+
+
+ ); +}