diff --git a/api/community/seed-demo.ts b/api/community/seed-demo.ts index 7f0e1e7c..812597d7 100644 --- a/api/community/seed-demo.ts +++ b/api/community/seed-demo.ts @@ -124,8 +124,14 @@ const DEMO_POSTS: DemoPost[] = [ ]; const FOLLOW_PAIRS: Array<{ followerEmail: string; followingEmail: string }> = [ - { followerEmail: "mrpiglr+demo@aethex.dev", followingEmail: "updates@aethex.dev" }, - { followerEmail: "mrpiglr+demo@aethex.dev", followingEmail: "labs@aethex.dev" }, + { + followerEmail: "mrpiglr+demo@aethex.dev", + followingEmail: "updates@aethex.dev", + }, + { + followerEmail: "mrpiglr+demo@aethex.dev", + followingEmail: "labs@aethex.dev", + }, { followerEmail: "labs@aethex.dev", followingEmail: "updates@aethex.dev" }, ]; @@ -140,8 +146,8 @@ export default async function handler(req: VercelRequest, res: VercelResponse) { const seededUsers: any[] = []; for (const demoUser of DEMO_USERS) { - const { data: searchResult, error: searchError } = await admin.auth.admin - .listUsers({ email: demoUser.email }); + const { data: searchResult, error: searchError } = + await admin.auth.admin.listUsers({ email: demoUser.email }); if (searchError) throw searchError; let authUser = searchResult.users?.[0]; @@ -226,7 +232,9 @@ export default async function handler(req: VercelRequest, res: VercelResponse) { continue; } - const createdAt = new Date(now - post.hoursAgo * 3600 * 1000).toISOString(); + const createdAt = new Date( + now - post.hoursAgo * 3600 * 1000, + ).toISOString(); const { data: insertedPost, error: insertPostError } = await admin .from("community_posts") .insert({ @@ -257,18 +265,20 @@ export default async function handler(req: VercelRequest, res: VercelResponse) { seededPosts.push(insertedPost); } - const followRows = FOLLOW_PAIRS.flatMap(({ followerEmail, followingEmail }) => { - const followerId = userMap.get(followerEmail); - const followingId = userMap.get(followingEmail); - if (!followerId || !followingId) return []; - return [ - { - follower_id: followerId, - following_id: followingId, - created_at: new Date().toISOString(), - }, - ]; - }); + const followRows = FOLLOW_PAIRS.flatMap( + ({ followerEmail, followingEmail }) => { + const followerId = userMap.get(followerEmail); + const followingId = userMap.get(followingEmail); + if (!followerId || !followingId) return []; + return [ + { + follower_id: followerId, + following_id: followingId, + created_at: new Date().toISOString(), + }, + ]; + }, + ); if (followRows.length) { const { error: followError } = await admin @@ -288,9 +298,7 @@ export default async function handler(req: VercelRequest, res: VercelResponse) { ); const sanitizedPosts = Array.from( new Map( - seededPosts - .filter(Boolean) - .map((post: any) => [post.id, post]), + seededPosts.filter(Boolean).map((post: any) => [post.id, post]), ).values(), ); diff --git a/client/components/Layout.tsx b/client/components/Layout.tsx index 160a4f19..3188a1fe 100644 --- a/client/components/Layout.tsx +++ b/client/components/Layout.tsx @@ -308,9 +308,9 @@ export default function CodeLayout({ children }: LayoutProps) { - - My Profile - + + My Profile + diff --git a/client/components/admin/AdminMemberManager.tsx b/client/components/admin/AdminMemberManager.tsx index 039f1676..0043b4e7 100644 --- a/client/components/admin/AdminMemberManager.tsx +++ b/client/components/admin/AdminMemberManager.tsx @@ -256,7 +256,8 @@ const AdminMemberManager = ({ } 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 (!err) + return "Supabase rejected the update. Review payload and RLS policies."; if (typeof err === "string") return err; if (err.message) return err.message; try { diff --git a/client/lib/aethex-database-adapter.ts b/client/lib/aethex-database-adapter.ts index 87e031b2..ef6af20a 100644 --- a/client/lib/aethex-database-adapter.ts +++ b/client/lib/aethex-database-adapter.ts @@ -382,7 +382,10 @@ export const aethexUserService = { let err = anyResp.error; if (err) { const message = String(err?.message || err); - if (message.includes("relationship") || message.includes("schema cache")) { + if ( + message.includes("relationship") || + message.includes("schema cache") + ) { // Fallback: fetch profiles, then batch-fetch achievements and map xp rewards const { data: profilesOnly, error: profilesErr } = await supabase .from("user_profiles") @@ -399,7 +402,9 @@ export const aethexUserService = { throw new Error(profilesErr?.message || String(profilesErr)); } - const ids = Array.isArray(profilesOnly) ? profilesOnly.map((p: any) => p.id).filter(Boolean) : []; + const ids = Array.isArray(profilesOnly) + ? profilesOnly.map((p: any) => p.id).filter(Boolean) + : []; let uaRows: any[] = []; if (ids.length) { @@ -414,7 +419,9 @@ export const aethexUserService = { uaRows = Array.isArray(uaData) ? uaData : []; } - const achievementIds = Array.from(new Set(uaRows.map((r) => r.achievement_id).filter(Boolean))); + const achievementIds = Array.from( + new Set(uaRows.map((r) => r.achievement_id).filter(Boolean)), + ); let achievementMap: Record = {}; if (achievementIds.length) { const { data: achData, error: achErr } = await supabase @@ -433,7 +440,11 @@ export const aethexUserService = { const enrichedProfiles = (profilesOnly || []).map((p: any) => { const userAchievements = uaRows .filter((r) => r.user_id === p.id) - .map((r) => ({ achievements: { xp_reward: achievementMap[r.achievement_id] || 0 } })); + .map((r) => ({ + achievements: { + xp_reward: achievementMap[r.achievement_id] || 0, + }, + })); return { ...p, user_achievements: userAchievements }; }); diff --git a/client/pages/Admin.tsx b/client/pages/Admin.tsx index 380f3d2a..58394b08 100644 --- a/client/pages/Admin.tsx +++ b/client/pages/Admin.tsx @@ -86,8 +86,9 @@ export default function Admin() { specialties: ["Simulation", "AI/ML", "Economy"], }, ]); - const [projectApplications, setProjectApplications] = - useState([]); + const [projectApplications, setProjectApplications] = useState< + ProjectApplication[] + >([]); const [projectApplicationsLoading, setProjectApplicationsLoading] = useState(false); type OpportunityApplication = { @@ -103,8 +104,9 @@ export default function Admin() { submitted_at?: string | null; message?: string | null; }; - const [opportunityApplications, setOpportunityApplications] = - useState([]); + const [opportunityApplications, setOpportunityApplications] = useState< + OpportunityApplication[] + >([]); const [opportunityApplicationsLoading, setOpportunityApplicationsLoading] = useState(false); const [selectedMemberId, setSelectedMemberId] = useState(null); @@ -311,7 +313,8 @@ export default function Admin() { value: opportunityApplicationsLoading ? "…" : opportunityApplications.length.toString(), - description: "Contributor & career submissions captured via Opportunities.", + description: + "Contributor & career submissions captured via Opportunities.", trend: opportunityApplicationsLoading ? "Syncing applicant data…" : `${opportunityApplications.filter((app) => (app.status ?? "new").toLowerCase() === "new").length} awaiting review`, @@ -1016,7 +1019,10 @@ export default function Admin() {
{projectApplications.slice(0, 6).map((app) => (
@@ -1043,7 +1049,8 @@ export default function Admin() {
) : (

- No project applications on file. Encourage partners to apply via briefs. + No project applications on file. Encourage partners to + apply via briefs.

)} @@ -1059,7 +1066,8 @@ export default function Admin() { Opportunity applications
- View contributor and career submissions captured on the Opportunities page. + View contributor and career submissions captured on the + Opportunities page. @@ -1092,7 +1100,10 @@ export default function Admin() {
{opportunityApplications.slice(0, 6).map((app) => (
@@ -1165,7 +1176,8 @@ export default function Admin() {
) : (

- No opportunity applications yet. Share the Opportunities page to grow the pipeline. + No opportunity applications yet. Share the Opportunities + page to grow the pipeline.

)} diff --git a/client/pages/DevelopersDirectory.tsx b/client/pages/DevelopersDirectory.tsx index bf8230fe..df9fc96e 100644 --- a/client/pages/DevelopersDirectory.tsx +++ b/client/pages/DevelopersDirectory.tsx @@ -109,19 +109,21 @@ interface DeveloperCardProps { const DeveloperCard = ({ profile }: DeveloperCardProps) => { const realmStyle = realmBadgeStyles[profile.user_type] || "bg-aethex-500 text-white"; - const fallbackBanner = realmBannerFallbacks[profile.user_type] || + const fallbackBanner = + realmBannerFallbacks[profile.user_type] || "from-slate-900 via-slate-800 to-slate-900"; const isGodMode = (profile.level ?? 1) >= 100; const passportHref = profile.username ? `/passport/${profile.username}` : `/passport/${profile.id}`; const name = profile.full_name || profile.username || "AeThex Explorer"; - const initials = name - .split(" ") - .filter(Boolean) - .map((segment) => segment[0]?.toUpperCase()) - .join("") - .slice(0, 2) || "AE"; + const initials = + name + .split(" ") + .filter(Boolean) + .map((segment) => segment[0]?.toUpperCase()) + .join("") + .slice(0, 2) || "AE"; const totalXp = Math.max(0, Math.floor(Number(profile.total_xp ?? 0))); const levelValue = Math.max(1, Math.floor(Number(profile.level ?? 1))); const loyaltyPoints = Math.max( @@ -225,9 +227,7 @@ const DeveloperCard = ({ profile }: DeveloperCardProps) => { {profile.bio && ( -

- {profile.bio} -

+

{profile.bio}

)}
diff --git a/client/pages/Opportunities.tsx b/client/pages/Opportunities.tsx index 79875597..17790326 100644 --- a/client/pages/Opportunities.tsx +++ b/client/pages/Opportunities.tsx @@ -11,7 +11,13 @@ import { import { Checkbox } from "@/components/ui/checkbox"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; -import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { Textarea } from "@/components/ui/textarea"; import { useAethexToast } from "@/hooks/use-aethex-toast"; @@ -208,7 +214,8 @@ const Opportunities = () => { useEffect(() => { setContributorForm((prev) => ({ ...prev, - fullName: prev.fullName || profile?.full_name || user?.email?.split("@")[0] || "", + fullName: + prev.fullName || profile?.full_name || user?.email?.split("@")[0] || "", email: prev.email || user?.email || "", location: prev.location || profile?.location || "", portfolioUrl: prev.portfolioUrl || profileWebsite || "", @@ -216,7 +223,8 @@ const Opportunities = () => { setCareerForm((prev) => ({ ...prev, - fullName: prev.fullName || profile?.full_name || user?.email?.split("@")[0] || "", + fullName: + prev.fullName || profile?.full_name || user?.email?.split("@")[0] || "", email: prev.email || user?.email || "", location: prev.location || profile?.location || "", portfolioUrl: prev.portfolioUrl || profileWebsite || "", @@ -236,23 +244,36 @@ const Opportunities = () => { }; const submitApplication = async ( - payload: Omit & { type: "contributor" | "career" }, + payload: Omit & { + type: "contributor" | "career"; + }, ) => { await aethexApplicationService.submitApplication(payload); }; - const handleContributorSubmit = async (event: React.FormEvent) => { + const handleContributorSubmit = async ( + event: React.FormEvent, + ) => { event.preventDefault(); if (!contributorForm.fullName.trim()) { - toast.error({ title: "Name required", description: "Please tell us who you are." }); + toast.error({ + title: "Name required", + description: "Please tell us who you are.", + }); return; } if (!validateEmail(contributorForm.email)) { - toast.error({ title: "Valid email required", description: "Share an email so we can follow up." }); + toast.error({ + title: "Valid email required", + description: "Share an email so we can follow up.", + }); return; } if (!contributorForm.primarySkill) { - toast.error({ title: "Select your primary skill", description: "Choose the area where you create the most impact." }); + toast.error({ + title: "Select your primary skill", + description: "Choose the area where you create the most impact.", + }); return; } setSubmittingContributor(true); @@ -270,10 +291,12 @@ const Opportunities = () => { }); toast.success({ title: "Application received", - description: "Thank you! Our contributor success team will reach out shortly.", + description: + "Thank you! Our contributor success team will reach out shortly.", }); setContributorForm({ - fullName: profile?.full_name || contributorForm.email.split("@")[0] || "", + fullName: + profile?.full_name || contributorForm.email.split("@")[0] || "", email: contributorForm.email, location: profile?.location || "", primarySkill: "", @@ -285,17 +308,23 @@ const Opportunities = () => { } catch (error: any) { toast.error({ title: "Submission failed", - description: error?.message || "We could not submit your contributor application.", + description: + error?.message || "We could not submit your contributor application.", }); } finally { setSubmittingContributor(false); } }; - const handleCareerSubmit = async (event: React.FormEvent) => { + const handleCareerSubmit = async ( + event: React.FormEvent, + ) => { event.preventDefault(); if (!careerForm.fullName.trim()) { - toast.error({ title: "Name required", description: "Please add your full name." }); + toast.error({ + title: "Name required", + description: "Please add your full name.", + }); return; } if (!validateEmail(careerForm.email)) { @@ -327,7 +356,8 @@ const Opportunities = () => { }); toast.success({ title: "Thanks for applying", - description: "Our talent team will review your experience and reach out soon.", + description: + "Our talent team will review your experience and reach out soon.", }); setCareerForm({ fullName: profile?.full_name || careerForm.email.split("@")[0] || "", @@ -342,7 +372,8 @@ const Opportunities = () => { } catch (error: any) { toast.error({ title: "Submission failed", - description: error?.message || "We could not submit your career application.", + description: + error?.message || "We could not submit your career application.", }); } finally { setSubmittingCareer(false); @@ -356,20 +387,35 @@ const Opportunities = () => {
- + Build the future at AeThex

Contribute. Collaborate. Craft the next era of AeThex.

- Whether you are a community contributor or exploring full-time roles, this is your gateway to ship meaningfully with the AeThex team. We unite game makers, storytellers, engineers, and strategists around bold ideas. + Whether you are a community contributor or exploring full-time + roles, this is your gateway to ship meaningfully with the AeThex + team. We unite game makers, storytellers, engineers, and + strategists around bold ideas.

@@ -410,7 +466,8 @@ const Opportunities = () => { What makes AeThex different? - Cross-functional teams, high-trust culture, and shipped outcomes over vanity metrics. + Cross-functional teams, high-trust culture, and shipped + outcomes over vanity metrics. @@ -419,16 +476,20 @@ const Opportunities = () => {

Hybrid squads

- Contributors and full-time teammates collaborate inside the same rituals, tooling, and roadmap. + Contributors and full-time teammates collaborate inside + the same rituals, tooling, and roadmap.

-

Impact-first onboarding

+

+ Impact-first onboarding +

- Ship something real during your first sprint with a dedicated mentor or squad lead. + Ship something real during your first sprint with a + dedicated mentor or squad lead.

@@ -437,7 +498,8 @@ const Opportunities = () => {

Transparent growth

- Clear expectations, async updates, and opportunities to move from contributor to core team. + Clear expectations, async updates, and opportunities to + move from contributor to core team.

@@ -446,18 +508,26 @@ const Opportunities = () => {
-
+
- + Explore opportunities

Contributor paths & immediate hiring needs

- Choose how you want to collaborate with AeThex. We empower flexible contributor engagements alongside full-time roles across labs, platform, and community. + Choose how you want to collaborate with AeThex. We empower + flexible contributor engagements alongside full-time roles + across labs, platform, and community.

@@ -466,7 +536,8 @@ const Opportunities = () => {

Rolling reviews

- Contributor cohorts are evaluated weekly. Urgent hiring roles receive priority outreach within 72 hours. + Contributor cohorts are evaluated weekly. Urgent hiring + roles receive priority outreach within 72 hours.

@@ -480,15 +551,25 @@ const Opportunities = () => { Core Contributors - Shape feature roadmaps, ship code, design experiences, or lead community programs. + Shape feature roadmaps, ship code, design experiences, or + lead community programs.

Typical commitment: 5-15 hrs / week

    -
  • Join a squad shipping AeThex platform, docs, or live services
  • -
  • Collaborate directly with PMs, producers, and staff engineers
  • -
  • Earn AeThex recognition, mentorship, and prioritized hiring pathways
  • +
  • + Join a squad shipping AeThex platform, docs, or live + services +
  • +
  • + Collaborate directly with PMs, producers, and staff + engineers +
  • +
  • + Earn AeThex recognition, mentorship, and prioritized + hiring pathways +
@@ -499,15 +580,24 @@ const Opportunities = () => { Fellows & Apprentices - Guided programs for emerging builders to gain portfolio-ready experience. + Guided programs for emerging builders to gain + portfolio-ready experience.

Typical commitment: 10-20 hrs / week for 12 weeks

    -
  • Structured mentorship with clear milestones and feedback
  • -
  • Contribute to lab prototypes, live events, or curriculum builds
  • -
  • Ideal for rising professionals breaking into games or platform engineering
  • +
  • + Structured mentorship with clear milestones and feedback +
  • +
  • + Contribute to lab prototypes, live events, or curriculum + builds +
  • +
  • + Ideal for rising professionals breaking into games or + platform engineering +
@@ -518,14 +608,24 @@ const Opportunities = () => { Full-time roles - Join AeThex as a core teammate with benefits, equity pathways, and ownership of initiatives. + Join AeThex as a core teammate with benefits, equity + pathways, and ownership of initiatives. -

Immediate needs across engineering, design, product, and community.

+

+ Immediate needs across engineering, design, product, and + community. +

    -
  • Global-first, remote-friendly culture with async collaboration
  • -
  • Cross-functional squads aligned to measurable player and creator outcomes
  • +
  • + Global-first, remote-friendly culture with async + collaboration +
  • +
  • + Cross-functional squads aligned to measurable player and + creator outcomes +
  • Opportunities to incubate new AeThex Labs ventures
@@ -533,23 +633,37 @@ const Opportunities = () => {
{openOpportunities.map((role) => ( - +
- {role.title} + + {role.title} + {role.type}
- + Priority
-

{role.summary}

+

+ {role.summary} +

{role.tags.map((tag) => ( - + {tag} ))} @@ -565,39 +679,59 @@ const Opportunities = () => {
- Contributor journey + + Contributor journey + Careers at AeThex - Contributor application + + Contributor application + - Ideal for open-source, part-time, or fellowship-style collaborations. + Ideal for open-source, part-time, or fellowship-style + collaborations. -
+
@@ -631,7 +765,9 @@ const Opportunities = () => {
- + {
- + { className="flex cursor-pointer items-start gap-3 rounded-lg border border-border/50 bg-background/40 p-3 text-sm transition hover:border-aethex-400/60" > toggleContributorInterest(interest)} + checked={contributorForm.interests.includes( + interest, + )} + onCheckedChange={() => + toggleContributorInterest(interest) + } className="mt-0.5" /> {interest} @@ -727,7 +869,10 @@ const Opportunities = () => {
- +