diff --git a/client/lib/supabase-devconnect.ts b/client/lib/supabase-devconnect.ts index a887982d..75dce940 100644 --- a/client/lib/supabase-devconnect.ts +++ b/client/lib/supabase-devconnect.ts @@ -7,6 +7,10 @@ export const hasDevConnect = Boolean(devUrl && devAnon); export const devconnect: SupabaseClient | null = hasDevConnect ? createClient(devUrl!, devAnon!, { - auth: { autoRefreshToken: false, persistSession: false, detectSessionInUrl: false }, + auth: { + autoRefreshToken: false, + persistSession: false, + detectSessionInUrl: false, + }, }) : null; diff --git a/client/pages/Community.tsx b/client/pages/Community.tsx index 1b051339..2df6664e 100644 --- a/client/pages/Community.tsx +++ b/client/pages/Community.tsx @@ -896,8 +896,7 @@ export default function Community() { { id: "directory", title: "Browse Creators & Studios", - description: - "Discover non‑AeThex developers and independent studios.", + description: "Discover non‑AeThex developers and independent studios.", href: "/directory", label: "Open directory", }, diff --git a/client/pages/Directory.tsx b/client/pages/Directory.tsx index b8597bde..1aef5b22 100644 --- a/client/pages/Directory.tsx +++ b/client/pages/Directory.tsx @@ -1,28 +1,66 @@ import Layout from "@/components/Layout"; import { Badge } from "@/components/ui/badge"; -import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; +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 { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select"; import { supabase } from "@/lib/supabase"; import { devconnect } from "@/lib/supabase-devconnect"; 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"; + 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 source = devconnect ? "DevConnect" : "AeThex"; - type BasicDev = { id: string; name: string; avatar_url?: string | null; location?: string | null; user_type?: string | null; experience_level?: string | null; tags?: string[] | null; verified?: boolean; updated_at?: string | null; total_xp?: number | null }; + type BasicDev = { + id: string; + name: string; + avatar_url?: string | null; + location?: string | null; + user_type?: string | null; + experience_level?: string | null; + tags?: string[] | null; + verified?: boolean; + updated_at?: string | null; + total_xp?: number | null; + }; const [devs, setDevs] = useState([]); type StudioMember = { id: string; name: string; avatar_url?: string | null }; - type Studio = { id: string; name: string; description?: string | null; type?: string | null; is_recruiting?: boolean | null; recruiting_roles?: string[] | null; tags?: string[] | null; slug?: string | null; visibility?: string | null; members_count?: number; members?: StudioMember[] }; + type Studio = { + id: string; + name: string; + description?: string | null; + type?: string | null; + is_recruiting?: boolean | null; + recruiting_roles?: string[] | null; + tags?: string[] | null; + slug?: string | null; + visibility?: string | null; + members_count?: number; + members?: StudioMember[]; + }; const [studios, setStudios] = useState([]); const [skillFilter, setSkillFilter] = useState("all"); const [regionFilter, setRegionFilter] = useState("all"); @@ -39,7 +77,12 @@ export default function Directory() { user_type: u.user_type || u.role || null, experience_level: u.experience_level || u.seniority || null, tags: u.tags || u.skills || null, - verified: Boolean(u.is_verified || (u.subscription && String(u.subscription).toLowerCase().includes("pro")) || (u.badges && String(u.badges).toLowerCase().includes("verified"))), + verified: Boolean( + u.is_verified || + (u.subscription && + String(u.subscription).toLowerCase().includes("pro")) || + (u.badges && String(u.badges).toLowerCase().includes("verified")), + ), updated_at: u.updated_at || null, total_xp: u.total_xp || u.xp || null, }); @@ -64,7 +107,7 @@ export default function Directory() { id: String(r.id), name: r.name, description: r.description || null, - type: r.type || (r.visibility || null), + type: r.type || r.visibility || null, is_recruiting: r.is_recruiting ?? null, recruiting_roles: r.recruiting_roles ?? null, tags: r.tags ?? null, @@ -83,7 +126,7 @@ export default function Directory() { .select( client === devconnect ? "id,name,description,type,is_recruiting,recruiting_roles,tags,slug,created_at, collective_members:collective_members(count)" - : "id,name,description,visibility,created_at, team_memberships:team_memberships(count)" + : "id,name,description,visibility,created_at, team_memberships:team_memberships(count)", ) .limit(200) .then(({ data, error }) => { @@ -91,7 +134,9 @@ export default function Directory() { else if (client !== supabase) { supabase .from("teams" as any) - .select("id,name,description,visibility,created_at, team_memberships:team_memberships(count)") + .select( + "id,name,description,visibility,created_at, team_memberships:team_memberships(count)", + ) .limit(200) .then(({ data: d2 }) => setStudios((d2 || []).map(mapStudio))); } @@ -110,9 +155,12 @@ export default function Directory() { (data || []).forEach((row: any) => { const cid = String(row.collective_id); if (!byCollective[cid]) byCollective[cid] = []; - if (byCollective[cid].length < 5) byCollective[cid].push(String(row.profile_id)); + if (byCollective[cid].length < 5) + byCollective[cid].push(String(row.profile_id)); }); - const profileIds = Array.from(new Set(Object.values(byCollective).flat())); + const profileIds = Array.from( + new Set(Object.values(byCollective).flat()), + ); if (profileIds.length) { const { data: profs } = await devconnect ?.from("profiles" as any) @@ -120,9 +168,20 @@ export default function Directory() { .in("id", profileIds); const map: Record = {}; (profs || []).forEach((p: any) => { - map[String(p.id)] = { id: String(p.id), name: p.display_name || "Member", avatar_url: p.avatar_url || null }; + map[String(p.id)] = { + id: String(p.id), + name: p.display_name || "Member", + avatar_url: p.avatar_url || null, + }; }); - setStudios((prev) => prev.map((s) => ({ ...s, members: (byCollective[s.id] || []).map((pid) => map[pid]).filter(Boolean) }))); + setStudios((prev) => + prev.map((s) => ({ + ...s, + members: (byCollective[s.id] || []) + .map((pid) => map[pid]) + .filter(Boolean), + })), + ); } }) .catch(() => {}); @@ -142,15 +201,28 @@ export default function Directory() { }); if (skillFilter !== "all") { - list = list.filter((u) => (u.tags || []).map(String).map((s) => s.toLowerCase()).includes(skillFilter.toLowerCase()) || (u.user_type || "").toLowerCase() === skillFilter.toLowerCase()); + list = list.filter( + (u) => + (u.tags || []) + .map(String) + .map((s) => s.toLowerCase()) + .includes(skillFilter.toLowerCase()) || + (u.user_type || "").toLowerCase() === skillFilter.toLowerCase(), + ); } if (regionFilter !== "all") { - list = list.filter((u) => (u.location || "").toLowerCase().includes(regionFilter.toLowerCase())); + list = list.filter((u) => + (u.location || "").toLowerCase().includes(regionFilter.toLowerCase()), + ); } if (sortMode === "active") { list = [...list].sort((a, b) => (b.total_xp || 0) - (a.total_xp || 0)); } else if (sortMode === "recent") { - list = [...list].sort((a, b) => new Date(b.updated_at || 0).getTime() - new Date(a.updated_at || 0).getTime()); + list = [...list].sort( + (a, b) => + new Date(b.updated_at || 0).getTime() - + new Date(a.updated_at || 0).getTime(), + ); } return list; }, [devs, query, hideAeThex, skillFilter, regionFilter, sortMode]); @@ -173,34 +245,63 @@ export default function Directory() {
- Directory -

Creators & Studios

-

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

+ + Directory + +

+ Creators & Studios +

+

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

Source: - {source} + + {source} +
- setQuery(e.target.value)} className="md:col-span-2" /> + setQuery(e.target.value)} + className="md:col-span-2" + /> @@ -210,10 +311,18 @@ export default function Directory() { All regions - {[...new Set(devs.map((d) => (d.location || "").split(",").pop()?.trim()).filter(Boolean))] + {[ + ...new Set( + devs + .map((d) => (d.location || "").split(",").pop()?.trim()) + .filter(Boolean), + ), + ] .slice(0, 30) .map((r) => ( - {String(r)} + + {String(r)} + ))} @@ -239,29 +348,52 @@ export default function Directory() {
- Showing {filteredDevs.length} creators from {source} + Showing {filteredDevs.length} creators from{" "} + {source}
{filteredDevs.map((u) => ( - + - + {initials(u.name)}
{u.name || "Developer"} {u.verified && ( - Verified + + Verified + )}
-
{u.location || "Global"}
+
+ {u.location || "Global"} +
- {u.user_type && {u.user_type}} - {u.experience_level && {u.experience_level}} + {u.user_type && ( + {u.user_type} + )} + {u.experience_level && ( + + {u.experience_level} + + )} {(u.tags || []).slice(0, 3).map((t) => ( - {String(t)} + + {String(t)} + ))}
@@ -273,30 +405,48 @@ export default function Directory() {
- Showing {filteredStudios.length} studios from {source} + Showing {filteredStudios.length} studios from{" "} + {source}
{filteredStudios.map((t) => ( - +
{t.name} {t.is_recruiting && ( - Recruiting + + Recruiting + )}
{(t.type || t.visibility) && ( - {t.type || t.visibility} + + {t.type || t.visibility} + )}
-

{t.description || ""}

+

+ {t.description || ""} +

{t.members && t.members.length > 0 && (
- {t.members.slice(0,5).map((m) => ( - - - {initials(m.name)} + {t.members.slice(0, 5).map((m) => ( + + + + {initials(m.name)} + ))}
@@ -306,20 +456,35 @@ export default function Directory() { {typeof t.members_count === "number" && ( {t.members_count} members )} - {(t.recruiting_roles && t.recruiting_roles.length > 0) && ( - Roles: {t.recruiting_roles!.join(", ")} - )} + {t.recruiting_roles && + t.recruiting_roles.length > 0 && ( + + Roles: {t.recruiting_roles!.join(", ")} + + )}
{t.slug && ( )}
- {(t.tags && t.tags.length > 0) && ( + {t.tags && t.tags.length > 0 && (
{t.tags!.map((tag) => ( - {tag} + + {tag} + ))}
)}