Prettier format pending files

This commit is contained in:
Builder.io 2025-10-14 04:35:52 +00:00
parent b573ff8070
commit bf4234348c
10 changed files with 495 additions and 198 deletions

View file

@ -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(),
);

View file

@ -308,9 +308,9 @@ export default function CodeLayout({ children }: LayoutProps) {
</DropdownMenuItem>
<DropdownMenuItem asChild>
<Link to="/profile" className="cursor-pointer">
<UserCircle className="mr-2 h-4 w-4" />
My Profile
</Link>
<UserCircle className="mr-2 h-4 w-4" />
My Profile
</Link>
</DropdownMenuItem>
<DropdownMenuItem asChild>
<Link to={passportHref} className="cursor-pointer">

View file

@ -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 {

View file

@ -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<string, number> = {};
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 };
});

View file

@ -86,8 +86,9 @@ export default function Admin() {
specialties: ["Simulation", "AI/ML", "Economy"],
},
]);
const [projectApplications, setProjectApplications] =
useState<ProjectApplication[]>([]);
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<OpportunityApplication[]>([]);
const [opportunityApplications, setOpportunityApplications] = useState<
OpportunityApplication[]
>([]);
const [opportunityApplicationsLoading, setOpportunityApplicationsLoading] =
useState(false);
const [selectedMemberId, setSelectedMemberId] = useState<string | null>(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() {
<div className="grid gap-2">
{projectApplications.slice(0, 6).map((app) => (
<div
key={app.id || `${app.applicant_email ?? "applicant"}-${app.projects?.id ?? "project"}`}
key={
app.id ||
`${app.applicant_email ?? "applicant"}-${app.projects?.id ?? "project"}`
}
className="space-y-1 rounded border border-border/30 bg-background/40 p-3"
>
<div className="flex flex-wrap items-center justify-between gap-2">
@ -1043,7 +1049,8 @@ export default function Admin() {
</div>
) : (
<p>
No project applications on file. Encourage partners to apply via briefs.
No project applications on file. Encourage partners to
apply via briefs.
</p>
)}
</CardContent>
@ -1059,7 +1066,8 @@ export default function Admin() {
<CardTitle>Opportunity applications</CardTitle>
</div>
<CardDescription>
View contributor and career submissions captured on the Opportunities page.
View contributor and career submissions captured on the
Opportunities page.
</CardDescription>
</CardHeader>
<CardContent className="space-y-3 text-sm text-muted-foreground">
@ -1092,7 +1100,10 @@ export default function Admin() {
<div className="grid gap-2">
{opportunityApplications.slice(0, 6).map((app) => (
<div
key={app.id || `${app.email ?? "candidate"}-${app.submitted_at ?? "time"}`}
key={
app.id ||
`${app.email ?? "candidate"}-${app.submitted_at ?? "time"}`
}
className="space-y-2 rounded border border-border/30 bg-background/40 p-3"
>
<div className="flex flex-wrap items-center justify-between gap-2">
@ -1165,7 +1176,8 @@ export default function Admin() {
</div>
) : (
<p>
No opportunity applications yet. Share the Opportunities page to grow the pipeline.
No opportunity applications yet. Share the Opportunities
page to grow the pipeline.
</p>
)}
</CardContent>

View file

@ -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) => {
</CardHeader>
<CardContent className="space-y-4 pt-0">
{profile.bio && (
<p className="text-sm text-slate-300 line-clamp-3">
{profile.bio}
</p>
<p className="text-sm text-slate-300 line-clamp-3">{profile.bio}</p>
)}
<div className="grid gap-3 sm:grid-cols-3">
<div className="rounded-lg border border-slate-800 bg-slate-900/70 p-3 text-slate-200">

View file

@ -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<AethexApplicationSubmission, "type"> & { type: "contributor" | "career" },
payload: Omit<AethexApplicationSubmission, "type"> & {
type: "contributor" | "career";
},
) => {
await aethexApplicationService.submitApplication(payload);
};
const handleContributorSubmit = async (event: React.FormEvent<HTMLFormElement>) => {
const handleContributorSubmit = async (
event: React.FormEvent<HTMLFormElement>,
) => {
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<HTMLFormElement>) => {
const handleCareerSubmit = async (
event: React.FormEvent<HTMLFormElement>,
) => {
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 = () => {
<section className="relative z-10 border-b border-border/30">
<div className="container mx-auto grid gap-12 px-4 py-20 lg:grid-cols-[1.1fr_0.9fr]">
<div className="space-y-6">
<Badge variant="outline" className="border-neon-blue/60 bg-neon-blue/10 text-neon-blue">
<Badge
variant="outline"
className="border-neon-blue/60 bg-neon-blue/10 text-neon-blue"
>
Build the future at AeThex
</Badge>
<h1 className="text-4xl font-bold tracking-tight text-white sm:text-5xl">
Contribute. Collaborate. Craft the next era of AeThex.
</h1>
<p className="max-w-2xl text-lg text-muted-foreground">
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.
</p>
<div className="flex flex-wrap gap-3">
<Button asChild size="lg" className="bg-gradient-to-r from-aethex-500 to-neon-blue hover:from-aethex-600 hover:to-neon-blue/90">
<Button
asChild
size="lg"
className="bg-gradient-to-r from-aethex-500 to-neon-blue hover:from-aethex-600 hover:to-neon-blue/90"
>
<a href="#apply">Start Application</a>
</Button>
<Button asChild variant="outline" size="lg" className="border-border/60 bg-background/40 backdrop-blur hover:bg-background/70">
<Button
asChild
variant="outline"
size="lg"
className="border-border/60 bg-background/40 backdrop-blur hover:bg-background/70"
>
<a href="#open-roles">
Browse open roles
<ArrowRight className="ml-2 h-4 w-4" />
@ -380,8 +426,12 @@ const Opportunities = () => {
<Card className="border-border/40 bg-background/50 backdrop-blur">
<CardHeader className="flex flex-row items-start justify-between space-y-0">
<div>
<CardTitle className="text-lg">Contributor network</CardTitle>
<CardDescription>Mentors, maintainers, and shipmates.</CardDescription>
<CardTitle className="text-lg">
Contributor network
</CardTitle>
<CardDescription>
Mentors, maintainers, and shipmates.
</CardDescription>
</div>
<Users className="h-8 w-8 text-aethex-400" />
</CardHeader>
@ -392,13 +442,19 @@ const Opportunities = () => {
<Card className="border-border/40 bg-background/50 backdrop-blur">
<CardHeader className="flex flex-row items-start justify-between space-y-0">
<div>
<CardTitle className="text-lg">Teams hiring now</CardTitle>
<CardDescription>Across Labs, Platform, and Community.</CardDescription>
<CardTitle className="text-lg">
Teams hiring now
</CardTitle>
<CardDescription>
Across Labs, Platform, and Community.
</CardDescription>
</div>
<Briefcase className="h-8 w-8 text-aethex-400" />
</CardHeader>
<CardContent>
<p className="text-2xl font-semibold text-white">12 squads</p>
<p className="text-2xl font-semibold text-white">
12 squads
</p>
</CardContent>
</Card>
</div>
@ -410,7 +466,8 @@ const Opportunities = () => {
What makes AeThex different?
</CardTitle>
<CardDescription>
Cross-functional teams, high-trust culture, and shipped outcomes over vanity metrics.
Cross-functional teams, high-trust culture, and shipped
outcomes over vanity metrics.
</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
@ -419,16 +476,20 @@ const Opportunities = () => {
<div>
<p className="font-medium text-white">Hybrid squads</p>
<p className="text-sm text-muted-foreground">
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.
</p>
</div>
</div>
<div className="flex items-start gap-3">
<CheckCircle2 className="mt-1 h-5 w-5 text-yellow-400" />
<div>
<p className="font-medium text-white">Impact-first onboarding</p>
<p className="font-medium text-white">
Impact-first onboarding
</p>
<p className="text-sm text-muted-foreground">
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.
</p>
</div>
</div>
@ -437,7 +498,8 @@ const Opportunities = () => {
<div>
<p className="font-medium text-white">Transparent growth</p>
<p className="text-sm text-muted-foreground">
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.
</p>
</div>
</div>
@ -446,18 +508,26 @@ const Opportunities = () => {
</div>
</section>
<section className="relative z-10 border-b border-border/30" id="open-roles">
<section
className="relative z-10 border-b border-border/30"
id="open-roles"
>
<div className="container mx-auto px-4 py-16">
<div className="mb-10 grid gap-4 md:grid-cols-[0.6fr_1fr] md:items-end">
<div>
<Badge variant="outline" className="border-purple-400/60 bg-purple-500/10 text-purple-200">
<Badge
variant="outline"
className="border-purple-400/60 bg-purple-500/10 text-purple-200"
>
Explore opportunities
</Badge>
<h2 className="mt-4 text-3xl font-semibold text-white">
Contributor paths & immediate hiring needs
</h2>
<p className="mt-2 text-muted-foreground">
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.
</p>
</div>
<div className="rounded-xl border border-border/40 bg-background/40 p-4 backdrop-blur">
@ -466,7 +536,8 @@ const Opportunities = () => {
<div>
<p className="font-medium text-white">Rolling reviews</p>
<p className="text-sm text-muted-foreground">
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.
</p>
</div>
</div>
@ -480,15 +551,25 @@ const Opportunities = () => {
Core Contributors
</CardTitle>
<CardDescription>
Shape feature roadmaps, ship code, design experiences, or lead community programs.
Shape feature roadmaps, ship code, design experiences, or
lead community programs.
</CardDescription>
</CardHeader>
<CardContent className="space-y-3 text-sm text-muted-foreground">
<p>Typical commitment: 5-15 hrs / week</p>
<ul className="list-disc space-y-2 pl-4">
<li>Join a squad shipping AeThex platform, docs, or live services</li>
<li>Collaborate directly with PMs, producers, and staff engineers</li>
<li>Earn AeThex recognition, mentorship, and prioritized hiring pathways</li>
<li>
Join a squad shipping AeThex platform, docs, or live
services
</li>
<li>
Collaborate directly with PMs, producers, and staff
engineers
</li>
<li>
Earn AeThex recognition, mentorship, and prioritized
hiring pathways
</li>
</ul>
</CardContent>
</Card>
@ -499,15 +580,24 @@ const Opportunities = () => {
Fellows & Apprentices
</CardTitle>
<CardDescription>
Guided programs for emerging builders to gain portfolio-ready experience.
Guided programs for emerging builders to gain
portfolio-ready experience.
</CardDescription>
</CardHeader>
<CardContent className="space-y-3 text-sm text-muted-foreground">
<p>Typical commitment: 10-20 hrs / week for 12 weeks</p>
<ul className="list-disc space-y-2 pl-4">
<li>Structured mentorship with clear milestones and feedback</li>
<li>Contribute to lab prototypes, live events, or curriculum builds</li>
<li>Ideal for rising professionals breaking into games or platform engineering</li>
<li>
Structured mentorship with clear milestones and feedback
</li>
<li>
Contribute to lab prototypes, live events, or curriculum
builds
</li>
<li>
Ideal for rising professionals breaking into games or
platform engineering
</li>
</ul>
</CardContent>
</Card>
@ -518,14 +608,24 @@ const Opportunities = () => {
Full-time roles
</CardTitle>
<CardDescription>
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.
</CardDescription>
</CardHeader>
<CardContent className="space-y-3 text-sm text-muted-foreground">
<p>Immediate needs across engineering, design, product, and community.</p>
<p>
Immediate needs across engineering, design, product, and
community.
</p>
<ul className="list-disc space-y-2 pl-4">
<li>Global-first, remote-friendly culture with async collaboration</li>
<li>Cross-functional squads aligned to measurable player and creator outcomes</li>
<li>
Global-first, remote-friendly culture with async
collaboration
</li>
<li>
Cross-functional squads aligned to measurable player and
creator outcomes
</li>
<li>Opportunities to incubate new AeThex Labs ventures</li>
</ul>
</CardContent>
@ -533,23 +633,37 @@ const Opportunities = () => {
</div>
<div className="mt-8 grid gap-6 lg:grid-cols-3">
{openOpportunities.map((role) => (
<Card key={role.title} className="border-border/40 bg-background/40 backdrop-blur">
<Card
key={role.title}
className="border-border/40 bg-background/40 backdrop-blur"
>
<CardHeader>
<div className="flex items-start justify-between gap-4">
<div>
<CardTitle className="text-white">{role.title}</CardTitle>
<CardTitle className="text-white">
{role.title}
</CardTitle>
<CardDescription>{role.type}</CardDescription>
</div>
<Badge variant="outline" className="border-aethex-400/60 text-aethex-200">
<Badge
variant="outline"
className="border-aethex-400/60 text-aethex-200"
>
Priority
</Badge>
</div>
</CardHeader>
<CardContent className="space-y-4">
<p className="text-sm text-muted-foreground">{role.summary}</p>
<p className="text-sm text-muted-foreground">
{role.summary}
</p>
<div className="flex flex-wrap gap-2">
{role.tags.map((tag) => (
<Badge key={tag} variant="outline" className="border-border/50 bg-background/60 text-xs uppercase tracking-wide">
<Badge
key={tag}
variant="outline"
className="border-border/50 bg-background/60 text-xs uppercase tracking-wide"
>
{tag}
</Badge>
))}
@ -565,39 +679,59 @@ const Opportunities = () => {
<div className="container mx-auto px-4 py-16">
<div className="mb-10 flex flex-col gap-4 md:flex-row md:items-end md:justify-between">
<div>
<Badge variant="outline" className="border-aethex-400/60 bg-aethex-500/10 text-aethex-200">
<Badge
variant="outline"
className="border-aethex-400/60 bg-aethex-500/10 text-aethex-200"
>
Apply now
</Badge>
<h2 className="mt-4 text-3xl font-semibold text-white">
Tell us how you want to build with AeThex
</h2>
<p className="mt-2 max-w-2xl text-muted-foreground">
Share a snapshot of your experience, interests, and availability. We calibrate opportunities weekly and match you with the best-fit squad or role.
Share a snapshot of your experience, interests, and
availability. We calibrate opportunities weekly and match you
with the best-fit squad or role.
</p>
</div>
<div className="rounded-xl border border-border/40 bg-background/40 p-4 text-sm text-muted-foreground backdrop-blur">
<p className="font-medium text-white">Need help?</p>
<p>
Email <a className="text-neon-blue underline" href="mailto:opportunities@aethex.com">opportunities@aethex.com</a> if you want to talk through the right track before applying.
Email{" "}
<a
className="text-neon-blue underline"
href="mailto:opportunities@aethex.com"
>
opportunities@aethex.com
</a>{" "}
if you want to talk through the right track before applying.
</p>
</div>
</div>
<Tabs defaultValue="contributor" className="w-full">
<TabsList className="bg-background/40">
<TabsTrigger value="contributor">Contributor journey</TabsTrigger>
<TabsTrigger value="contributor">
Contributor journey
</TabsTrigger>
<TabsTrigger value="career">Careers at AeThex</TabsTrigger>
</TabsList>
<TabsContent value="contributor" className="mt-6">
<Card className="border-border/40 bg-background/50 backdrop-blur">
<CardHeader>
<CardTitle className="text-white">Contributor application</CardTitle>
<CardTitle className="text-white">
Contributor application
</CardTitle>
<CardDescription>
Ideal for open-source, part-time, or fellowship-style collaborations.
Ideal for open-source, part-time, or fellowship-style
collaborations.
</CardDescription>
</CardHeader>
<CardContent>
<form className="grid gap-6" onSubmit={handleContributorSubmit}>
<form
className="grid gap-6"
onSubmit={handleContributorSubmit}
>
<div className="grid gap-4 md:grid-cols-2">
<div className="space-y-2">
<Label htmlFor="contributor-name">Full name</Label>
@ -631,7 +765,9 @@ const Opportunities = () => {
</div>
<div className="grid gap-4 md:grid-cols-2">
<div className="space-y-2">
<Label htmlFor="contributor-location">Location (optional)</Label>
<Label htmlFor="contributor-location">
Location (optional)
</Label>
<Input
id="contributor-location"
value={contributorForm.location}
@ -694,7 +830,9 @@ const Opportunities = () => {
</Select>
</div>
<div className="space-y-2">
<Label htmlFor="contributor-portfolio">Portfolio or profile link</Label>
<Label htmlFor="contributor-portfolio">
Portfolio or profile link
</Label>
<Input
id="contributor-portfolio"
value={contributorForm.portfolioUrl}
@ -717,8 +855,12 @@ 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"
>
<Checkbox
checked={contributorForm.interests.includes(interest)}
onCheckedChange={() => toggleContributorInterest(interest)}
checked={contributorForm.interests.includes(
interest,
)}
onCheckedChange={() =>
toggleContributorInterest(interest)
}
className="mt-0.5"
/>
<span>{interest}</span>
@ -727,7 +869,10 @@ const Opportunities = () => {
</div>
</div>
<div className="space-y-2">
<Label htmlFor="contributor-message">Tell us about a project you are proud of or what you want to build here</Label>
<Label htmlFor="contributor-message">
Tell us about a project you are proud of or what you
want to build here
</Label>
<Textarea
id="contributor-message"
value={contributorForm.message}
@ -741,7 +886,11 @@ const Opportunities = () => {
placeholder="Share context, links, or ideas you want to explore with AeThex."
/>
</div>
<Button type="submit" className="w-full sm:w-auto" disabled={submittingContributor}>
<Button
type="submit"
className="w-full sm:w-auto"
disabled={submittingContributor}
>
{submittingContributor ? (
<>
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
@ -758,9 +907,12 @@ const Opportunities = () => {
<TabsContent value="career" className="mt-6">
<Card className="border-border/40 bg-background/50 backdrop-blur">
<CardHeader>
<CardTitle className="text-white">Career application</CardTitle>
<CardTitle className="text-white">
Career application
</CardTitle>
<CardDescription>
Ideal for full-time or long-term contract positions within AeThex.
Ideal for full-time or long-term contract positions within
AeThex.
</CardDescription>
</CardHeader>
<CardContent>
@ -798,7 +950,9 @@ const Opportunities = () => {
</div>
<div className="grid gap-4 md:grid-cols-2">
<div className="space-y-2">
<Label htmlFor="career-location">Location (optional)</Label>
<Label htmlFor="career-location">
Location (optional)
</Label>
<Input
id="career-location"
value={careerForm.location}
@ -861,7 +1015,9 @@ const Opportunities = () => {
</Select>
</div>
<div className="space-y-2">
<Label htmlFor="career-portfolio">Portfolio or work samples</Label>
<Label htmlFor="career-portfolio">
Portfolio or work samples
</Label>
<Input
id="career-portfolio"
value={careerForm.portfolioUrl}
@ -876,7 +1032,9 @@ const Opportunities = () => {
</div>
</div>
<div className="space-y-2">
<Label htmlFor="career-resume">Resume, CV, or LinkedIn</Label>
<Label htmlFor="career-resume">
Resume, CV, or LinkedIn
</Label>
<Input
id="career-resume"
value={careerForm.resumeUrl}
@ -890,7 +1048,9 @@ const Opportunities = () => {
/>
</div>
<div className="space-y-2">
<Label htmlFor="career-message">Why AeThex, why now?</Label>
<Label htmlFor="career-message">
Why AeThex, why now?
</Label>
<Textarea
id="career-message"
value={careerForm.message}
@ -904,7 +1064,11 @@ const Opportunities = () => {
placeholder="Share recent accomplishments, what motivates you, or ideas you want to ship with AeThex."
/>
</div>
<Button type="submit" className="w-full sm:w-auto" disabled={submittingCareer}>
<Button
type="submit"
className="w-full sm:w-auto"
disabled={submittingCareer}
>
{submittingCareer ? (
<>
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
@ -925,30 +1089,43 @@ const Opportunities = () => {
<section className="relative z-10 border-b border-border/30">
<div className="container mx-auto px-4 py-16">
<div className="mb-10 text-center">
<Badge variant="outline" className="border-border/50 bg-background/40 text-muted-foreground">
<Badge
variant="outline"
className="border-border/50 bg-background/40 text-muted-foreground"
>
From hello to shipped impact
</Badge>
<h2 className="mt-4 text-3xl font-semibold text-white">
What happens after you apply
</h2>
<p className="mt-2 text-muted-foreground">
A guided experience that helps you connect with squads, evaluate fit, and start shipping meaningful work.
A guided experience that helps you connect with squads, evaluate
fit, and start shipping meaningful work.
</p>
</div>
<div className="grid gap-6 lg:grid-cols-3">
{applicationMilestones.map(({ icon: Icon, title, description }) => (
<Card key={title} className="border-border/40 bg-background/40 backdrop-blur">
<CardHeader className="space-y-4">
<div className="flex h-12 w-12 items-center justify-center rounded-full bg-aethex-500/15 text-aethex-200">
<Icon className="h-6 w-6" />
</div>
<CardTitle className="text-xl text-white">{title}</CardTitle>
</CardHeader>
<CardContent>
<p className="text-sm text-muted-foreground">{description}</p>
</CardContent>
</Card>
))}
{applicationMilestones.map(
({ icon: Icon, title, description }) => (
<Card
key={title}
className="border-border/40 bg-background/40 backdrop-blur"
>
<CardHeader className="space-y-4">
<div className="flex h-12 w-12 items-center justify-center rounded-full bg-aethex-500/15 text-aethex-200">
<Icon className="h-6 w-6" />
</div>
<CardTitle className="text-xl text-white">
{title}
</CardTitle>
</CardHeader>
<CardContent>
<p className="text-sm text-muted-foreground">
{description}
</p>
</CardContent>
</Card>
),
)}
</div>
</div>
</section>
@ -956,22 +1133,35 @@ const Opportunities = () => {
<section className="relative z-10">
<div className="container mx-auto px-4 py-16">
<div className="mb-10 text-center">
<Badge variant="outline" className="border-border/50 bg-background/40 text-muted-foreground">
<Badge
variant="outline"
className="border-border/50 bg-background/40 text-muted-foreground"
>
Frequently asked questions
</Badge>
<h2 className="mt-4 text-3xl font-semibold text-white">Everything you need to know</h2>
<h2 className="mt-4 text-3xl font-semibold text-white">
Everything you need to know
</h2>
<p className="mt-2 text-muted-foreground">
From application timelines to cross-team collaboration, here are answers to what most applicants ask.
From application timelines to cross-team collaboration, here are
answers to what most applicants ask.
</p>
</div>
<div className="mx-auto grid max-w-4xl gap-4">
{faqs.map((item) => (
<Card key={item.question} className="border-border/40 bg-background/50 backdrop-blur">
<Card
key={item.question}
className="border-border/40 bg-background/50 backdrop-blur"
>
<CardHeader>
<CardTitle className="text-lg text-white">{item.question}</CardTitle>
<CardTitle className="text-lg text-white">
{item.question}
</CardTitle>
</CardHeader>
<CardContent>
<p className="text-sm text-muted-foreground leading-relaxed">{item.answer}</p>
<p className="text-sm text-muted-foreground leading-relaxed">
{item.answer}
</p>
</CardContent>
</Card>
))}

View file

@ -7,11 +7,7 @@ import {
aethexAchievementService,
type AethexAchievement,
} from "@/lib/aethex-database-adapter";
import {
Avatar,
AvatarFallback,
AvatarImage,
} from "@/components/ui/avatar";
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import {
@ -81,7 +77,9 @@ const Profile = () => {
if (!user?.id) return;
setLoadingAchievements(true);
try {
const data = await aethexAchievementService.getUserAchievements(user.id);
const data = await aethexAchievementService.getUserAchievements(
user.id,
);
setAchievements(data.slice(0, 6));
} catch (error) {
console.warn("Failed to load achievements for profile overview", error);
@ -182,12 +180,18 @@ const Profile = () => {
</CardTitle>
<CardDescription className="flex flex-wrap items-center justify-center gap-2 text-sm">
{profile.user_type ? (
<Badge variant="outline" className="capitalize border-aethex-400/60 text-aethex-200">
<Badge
variant="outline"
className="capitalize border-aethex-400/60 text-aethex-200"
>
{profile.user_type.replace(/_/g, " ")}
</Badge>
) : null}
{profile.experience_level ? (
<Badge variant="outline" className="capitalize border-purple-400/50 text-purple-200">
<Badge
variant="outline"
className="capitalize border-purple-400/50 text-purple-200"
>
{profile.experience_level}
</Badge>
) : null}
@ -224,10 +228,19 @@ const Profile = () => {
</div>
<Separator className="bg-border/40" />
<div className="flex flex-col gap-3">
<Button asChild className="bg-gradient-to-r from-aethex-500 to-neon-blue">
<Link to={dashboardSettingsHref}>Edit profile in Dashboard</Link>
<Button
asChild
className="bg-gradient-to-r from-aethex-500 to-neon-blue"
>
<Link to={dashboardSettingsHref}>
Edit profile in Dashboard
</Link>
</Button>
<Button asChild variant="outline" className="border-border/50">
<Button
asChild
variant="outline"
className="border-border/50"
>
<Link to={passportHref}>View AeThex Passport</Link>
</Button>
</div>
@ -241,7 +254,9 @@ const Profile = () => {
<Shield className="h-5 w-5 text-aethex-300" />
Progress snapshot
</CardTitle>
<CardDescription>Where you stand across AeThex programs.</CardDescription>
<CardDescription>
Where you stand across AeThex programs.
</CardDescription>
</CardHeader>
<CardContent>
<div className="grid gap-4 sm:grid-cols-2">
@ -251,12 +266,18 @@ const Profile = () => {
className="flex flex-col gap-2 rounded-lg border border-border/40 bg-background/50 p-4 transition hover:border-aethex-400/60"
>
<div className="flex items-center justify-between">
<p className="text-sm text-muted-foreground">{label}</p>
<p className="text-sm text-muted-foreground">
{label}
</p>
<Icon className="h-5 w-5 text-aethex-300" />
</div>
<p className="text-xl font-semibold text-white">{value}</p>
<p className="text-xl font-semibold text-white">
{value}
</p>
{helper ? (
<p className="text-xs text-muted-foreground">{helper}</p>
<p className="text-xs text-muted-foreground">
{helper}
</p>
) : null}
</div>
))}
@ -271,13 +292,16 @@ const Profile = () => {
About {profile.full_name || username}
</CardTitle>
<CardDescription>
Community presence, specialties, and how to collaborate with you.
Community presence, specialties, and how to collaborate with
you.
</CardDescription>
</CardHeader>
<CardContent className="space-y-6">
<div className="grid gap-4 md:grid-cols-2">
<div className="rounded-lg border border-border/40 bg-background/50 p-4">
<p className="text-sm font-medium text-foreground/80">Role focus</p>
<p className="text-sm font-medium text-foreground/80">
Role focus
</p>
<p className="text-sm text-muted-foreground">
{profile.user_type
? profile.user_type.replace(/_/g, " ")
@ -285,15 +309,20 @@ const Profile = () => {
</p>
</div>
<div className="rounded-lg border border-border/40 bg-background/50 p-4">
<p className="text-sm font-medium text-foreground/80">Experience level</p>
<p className="text-sm font-medium text-foreground/80">
Experience level
</p>
<p className="text-sm text-muted-foreground">
{profile.experience_level || "Let collaborators know your seniority."}
{profile.experience_level ||
"Let collaborators know your seniority."}
</p>
</div>
</div>
<div className="space-y-2">
<h3 className="text-sm font-medium text-foreground/80">Links & presence</h3>
<h3 className="text-sm font-medium text-foreground/80">
Links & presence
</h3>
{socialLinks.length ? (
<div className="flex flex-wrap gap-2">
{socialLinks.map((link) => (
@ -329,7 +358,8 @@ const Profile = () => {
Recent achievements
</CardTitle>
<CardDescription>
Milestones you&apos;ve unlocked across AeThex games and programs.
Milestones you&apos;ve unlocked across AeThex games and
programs.
</CardDescription>
</CardHeader>
<CardContent>
@ -349,7 +379,10 @@ const Profile = () => {
<p className="font-medium text-foreground">
{achievement.name}
</p>
<Badge variant="outline" className="border-amber-400/60 text-amber-200">
<Badge
variant="outline"
className="border-amber-400/60 text-amber-200"
>
+{achievement.xp_reward} XP
</Badge>
</div>
@ -361,7 +394,8 @@ const Profile = () => {
</div>
) : (
<p className="rounded border border-dashed border-border/40 bg-background/40 p-4 text-sm text-muted-foreground">
Start contributing to unlock your first AeThex achievement.
Start contributing to unlock your first AeThex
achievement.
</p>
)}
</CardContent>

View file

@ -74,7 +74,11 @@ const curriculumModules: CurriculumModule[] = [
"Establish core mastery of the AeThex platform, from environment setup to shipping your first interactive experience.",
duration: "2.5 hrs",
level: "foundation",
focus: ["Workspace onboarding", "Project scaffolding", "Passport + identity"],
focus: [
"Workspace onboarding",
"Project scaffolding",
"Passport + identity",
],
learningGoals: [
"Configure a production-ready AeThex project",
"Understand AeThex Passport, identity, and role models",
@ -242,12 +246,14 @@ const curriculumHighlights = [
},
{
title: "Project-based milestones",
description: "Each module culminates in a capstone aligned to AeThex deliverables.",
description:
"Each module culminates in a capstone aligned to AeThex deliverables.",
icon: Target,
},
{
title: "Built with real data",
description: "Lessons reference live dashboards, Passport identities, and Supabase schemas.",
description:
"Lessons reference live dashboards, Passport identities, and Supabase schemas.",
icon: Lightbulb,
},
];
@ -273,19 +279,22 @@ const curriculumStats = [
const supplementalResources = [
{
title: "AeThex Playbooks",
description: "Download ready-made GTM, community, and growth playbooks to complement each module.",
description:
"Download ready-made GTM, community, and growth playbooks to complement each module.",
cta: "Browse playbooks",
href: "/docs/examples#playbooks",
},
{
title: "Live Mentorship Sessions",
description: "Join weekly office hours with AeThex engineers and producers to review your progress.",
description:
"Join weekly office hours with AeThex engineers and producers to review your progress.",
cta: "Reserve a seat",
href: "/mentorship",
},
{
title: "Certification Exams",
description: "Validate mastery with AeThex Builder and Operator certifications once you finish the track.",
description:
"Validate mastery with AeThex Builder and Operator certifications once you finish the track.",
cta: "View certification guide",
href: "/docs/platform#certification",
},
@ -305,9 +314,9 @@ export default function DocsCurriculum() {
Structured learning paths for builders, operators, and labs teams
</h1>
<p className="max-w-3xl text-base text-slate-200 sm:text-lg">
Progress through sequenced modules that combine documentation, interactive labs, and
project-based assignments. Graduate with deployment-ready AeThex experiences and
certification badges.
Progress through sequenced modules that combine documentation,
interactive labs, and project-based assignments. Graduate with
deployment-ready AeThex experiences and certification badges.
</p>
</div>
<div className="grid gap-4 sm:grid-cols-3">
@ -343,8 +352,9 @@ export default function DocsCurriculum() {
<Compass className="h-6 w-6 text-purple-300" /> Curriculum roadmap
</CardTitle>
<CardDescription className="text-slate-300">
Expand each module to view lessons, formats, and key objectives. Every module ends with a
capstone milestone that prepares you for certification.
Expand each module to view lessons, formats, and key objectives.
Every module ends with a capstone milestone that prepares you for
certification.
</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
@ -379,7 +389,9 @@ export default function DocsCurriculum() {
<h3 className="text-lg font-semibold text-white sm:text-xl">
{module.title}
</h3>
<p className="text-sm text-slate-300">{module.description}</p>
<p className="text-sm text-slate-300">
{module.description}
</p>
</div>
</div>
</AccordionTrigger>
@ -439,7 +451,8 @@ export default function DocsCurriculum() {
className="mt-3 h-8 w-fit gap-2 rounded-full border border-slate-800/60 bg-slate-900/50 px-3 text-xs text-slate-200 hover:border-purple-500/50 hover:text-white"
>
<Link to={lesson.path}>
Review lesson <ArrowRight className="h-3.5 w-3.5" />
Review lesson{" "}
<ArrowRight className="h-3.5 w-3.5" />
</Link>
</Button>
</div>
@ -464,7 +477,8 @@ export default function DocsCurriculum() {
</div>
) : (
<p className="text-sm text-purple-100/80">
Coming soon the AeThex team is curating the next advanced mission.
Coming soon the AeThex team is curating the next
advanced mission.
</p>
)}
</div>
@ -483,7 +497,8 @@ export default function DocsCurriculum() {
Why this curriculum works
</CardTitle>
<CardDescription className="text-slate-300">
Blending documentation, live labs, and project work keeps teams aligned and measurable.
Blending documentation, live labs, and project work keeps teams
aligned and measurable.
</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
@ -514,10 +529,12 @@ export default function DocsCurriculum() {
<Card className="border-slate-800 bg-slate-900/70 shadow-xl">
<CardHeader className="space-y-2">
<CardTitle className="flex items-center gap-2 text-xl text-white">
<BookOpenCheck className="h-5 w-5 text-purple-300" /> Supplemental resources
<BookOpenCheck className="h-5 w-5 text-purple-300" />{" "}
Supplemental resources
</CardTitle>
<CardDescription className="text-slate-300">
Extend the curriculum with live mentorship, playbooks, and certification tracks.
Extend the curriculum with live mentorship, playbooks, and
certification tracks.
</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
@ -526,8 +543,12 @@ export default function DocsCurriculum() {
key={resource.title}
className="space-y-2 rounded-2xl border border-slate-800/60 bg-slate-950/70 p-4"
>
<p className="text-sm font-semibold text-white">{resource.title}</p>
<p className="text-sm text-slate-300">{resource.description}</p>
<p className="text-sm font-semibold text-white">
{resource.title}
</p>
<p className="text-sm text-slate-300">
{resource.description}
</p>
<Button
asChild
variant="ghost"
@ -544,36 +565,48 @@ export default function DocsCurriculum() {
<Card className="border-slate-800 bg-slate-900/70 shadow-xl">
<CardHeader>
<CardTitle className="text-xl text-white">Team readiness checklist</CardTitle>
<CardTitle className="text-xl text-white">
Team readiness checklist
</CardTitle>
<CardDescription className="text-slate-300">
Confirm prerequisites before starting the Builder or Advanced tracks.
Confirm prerequisites before starting the Builder or Advanced
tracks.
</CardDescription>
</CardHeader>
<CardContent className="space-y-3">
<div className="flex items-start gap-3 rounded-2xl border border-slate-800/60 bg-slate-950/70 p-4">
<Calculator className="mt-1 h-5 w-5 text-purple-200" />
<div>
<p className="text-sm font-semibold text-white">Account + billing configured</p>
<p className="text-sm font-semibold text-white">
Account + billing configured
</p>
<p className="text-sm text-slate-300">
Ensure Supabase, billing providers, and environment variables are ready for production load.
Ensure Supabase, billing providers, and environment
variables are ready for production load.
</p>
</div>
</div>
<div className="flex items-start gap-3 rounded-2xl border border-slate-800/60 bg-slate-950/70 p-4">
<Sparkles className="mt-1 h-5 w-5 text-purple-200" />
<div>
<p className="text-sm font-semibold text-white">Core team enrolled</p>
<p className="text-sm font-semibold text-white">
Core team enrolled
</p>
<p className="text-sm text-slate-300">
Identify product, engineering, and operations owners to steward each module.
Identify product, engineering, and operations owners to
steward each module.
</p>
</div>
</div>
<div className="flex items-start gap-3 rounded-2xl border border-slate-800/60 bg-slate-950/70 p-4">
<Layers className="mt-1 h-5 w-5 text-purple-200" />
<div>
<p className="text-sm font-semibold text-white">Backlog aligned to modules</p>
<p className="text-sm font-semibold text-white">
Backlog aligned to modules
</p>
<p className="text-sm text-slate-300">
Map existing sprints to module milestones so curriculum progress mirrors roadmap delivery.
Map existing sprints to module milestones so curriculum
progress mirrors roadmap delivery.
</p>
</div>
</div>
@ -585,13 +618,19 @@ export default function DocsCurriculum() {
<Separator className="border-slate-800/70" />
<section className="rounded-3xl border border-slate-800/60 bg-slate-900/70 p-8 text-center">
<h2 className="text-2xl font-semibold text-white">Ready to certify your AeThex team?</h2>
<h2 className="text-2xl font-semibold text-white">
Ready to certify your AeThex team?
</h2>
<p className="mx-auto mt-3 max-w-2xl text-sm text-slate-300">
Track your completion inside the AeThex admin panel, then schedule a certification review with the
AeThex Labs crew. We award badges directly to your Passport once requirements are verified.
Track your completion inside the AeThex admin panel, then schedule a
certification review with the AeThex Labs crew. We award badges
directly to your Passport once requirements are verified.
</p>
<div className="mt-6 flex flex-wrap justify-center gap-3">
<Button asChild className="gap-2 rounded-full bg-purple-600 text-white hover:bg-purple-500">
<Button
asChild
className="gap-2 rounded-full bg-purple-600 text-white hover:bg-purple-500"
>
<Link to="/mentorship">
Join mentorship cohort <ArrowRight className="h-4 w-4" />
</Link>

View file

@ -19,12 +19,16 @@ export function createServer() {
// Admin-backed API (service role)
try {
const ownerEmail = (process.env.AETHEX_OWNER_EMAIL || "mrpiglr@gmail.com").toLowerCase();
const ownerEmail = (
process.env.AETHEX_OWNER_EMAIL || "mrpiglr@gmail.com"
).toLowerCase();
const isTableMissing = (err: any) => {
const code = err?.code;
const message = String(err?.message || err?.hint || err?.details || "");
return (
code === "42P01" || message.includes("relation") || message.includes("does not exist")
code === "42P01" ||
message.includes("relation") ||
message.includes("does not exist")
);
};
@ -391,9 +395,7 @@ export function createServer() {
}
return res.json(data || []);
} catch (e: any) {
return res
.status(500)
.json({ error: e?.message || String(e) });
return res.status(500).json({ error: e?.message || String(e) });
}
});
} catch (e) {