Prettier format pending files

This commit is contained in:
Builder.io 2025-11-08 03:49:51 +00:00
parent b3f92fe96d
commit 0afd536c9b
53 changed files with 1467 additions and 720 deletions

View file

@ -5,10 +5,7 @@ const supabaseServiceRole = process.env.SUPABASE_SERVICE_ROLE || "";
const supabase = createClient(supabaseUrl, supabaseServiceRole);
export async function getMyApplications(
req: Request,
userId: string
) {
export async function getMyApplications(req: Request, userId: string) {
const url = new URL(req.url);
const page = parseInt(url.searchParams.get("page") || "1");
const limit = parseInt(url.searchParams.get("limit") || "10");
@ -25,7 +22,7 @@ export async function getMyApplications(
if (!creator) {
return new Response(
JSON.stringify({ error: "Creator profile not found" }),
{ status: 404, headers: { "Content-Type": "application/json" } }
{ status: 404, headers: { "Content-Type": "application/json" } },
);
}
@ -43,7 +40,7 @@ export async function getMyApplications(
updated_at,
aethex_opportunities(id, title, arm_affiliation, job_type, posted_by_id, aethex_creators!aethex_opportunities_posted_by_id_fkey(username, avatar_url))
`,
{ count: "exact" }
{ count: "exact" },
)
.eq("creator_id", creator.id);
@ -73,20 +70,20 @@ export async function getMyApplications(
{
status: 200,
headers: { "Content-Type": "application/json" },
}
},
);
} catch (error) {
console.error("Error fetching applications:", error);
return new Response(
JSON.stringify({ error: "Failed to fetch applications" }),
{ status: 500, headers: { "Content-Type": "application/json" } }
{ status: 500, headers: { "Content-Type": "application/json" } },
);
}
}
export async function getApplicationsForOpportunity(
opportunityId: string,
userId: string
userId: string,
) {
try {
// Verify user owns this opportunity
@ -97,10 +94,10 @@ export async function getApplicationsForOpportunity(
.single();
if (!opportunity) {
return new Response(
JSON.stringify({ error: "Opportunity not found" }),
{ status: 404, headers: { "Content-Type": "application/json" } }
);
return new Response(JSON.stringify({ error: "Opportunity not found" }), {
status: 404,
headers: { "Content-Type": "application/json" },
});
}
const { data: creator } = await supabase
@ -110,10 +107,10 @@ export async function getApplicationsForOpportunity(
.single();
if (creator?.id !== opportunity.posted_by_id) {
return new Response(
JSON.stringify({ error: "Unauthorized" }),
{ status: 403, headers: { "Content-Type": "application/json" } }
);
return new Response(JSON.stringify({ error: "Unauthorized" }), {
status: 403,
headers: { "Content-Type": "application/json" },
});
}
const { data, error } = await supabase
@ -126,7 +123,7 @@ export async function getApplicationsForOpportunity(
cover_letter,
applied_at,
aethex_creators(username, avatar_url, bio, skills)
`
`,
)
.eq("opportunity_id", opportunityId)
.order("applied_at", { ascending: false });
@ -141,15 +138,12 @@ export async function getApplicationsForOpportunity(
console.error("Error fetching opportunity applications:", error);
return new Response(
JSON.stringify({ error: "Failed to fetch applications" }),
{ status: 500, headers: { "Content-Type": "application/json" } }
{ status: 500, headers: { "Content-Type": "application/json" } },
);
}
}
export async function submitApplication(
req: Request,
userId: string
) {
export async function submitApplication(req: Request, userId: string) {
try {
const body = await req.json();
const { opportunity_id, cover_letter } = body;
@ -164,7 +158,7 @@ export async function submitApplication(
if (!creator) {
return new Response(
JSON.stringify({ error: "Creator profile not found" }),
{ status: 404, headers: { "Content-Type": "application/json" } }
{ status: 404, headers: { "Content-Type": "application/json" } },
);
}
@ -179,7 +173,7 @@ export async function submitApplication(
if (!opportunity) {
return new Response(
JSON.stringify({ error: "Opportunity not found or closed" }),
{ status: 404, headers: { "Content-Type": "application/json" } }
{ status: 404, headers: { "Content-Type": "application/json" } },
);
}
@ -193,8 +187,10 @@ export async function submitApplication(
if (existing) {
return new Response(
JSON.stringify({ error: "You have already applied to this opportunity" }),
{ status: 400, headers: { "Content-Type": "application/json" } }
JSON.stringify({
error: "You have already applied to this opportunity",
}),
{ status: 400, headers: { "Content-Type": "application/json" } },
);
}
@ -219,7 +215,7 @@ export async function submitApplication(
console.error("Error submitting application:", error);
return new Response(
JSON.stringify({ error: "Failed to submit application" }),
{ status: 500, headers: { "Content-Type": "application/json" } }
{ status: 500, headers: { "Content-Type": "application/json" } },
);
}
}
@ -227,7 +223,7 @@ export async function submitApplication(
export async function updateApplicationStatus(
req: Request,
applicationId: string,
userId: string
userId: string,
) {
try {
const body = await req.json();
@ -241,16 +237,16 @@ export async function updateApplicationStatus(
id,
opportunity_id,
aethex_opportunities(posted_by_id)
`
`,
)
.eq("id", applicationId)
.single();
if (!application) {
return new Response(
JSON.stringify({ error: "Application not found" }),
{ status: 404, headers: { "Content-Type": "application/json" } }
);
return new Response(JSON.stringify({ error: "Application not found" }), {
status: 404,
headers: { "Content-Type": "application/json" },
});
}
const { data: creator } = await supabase
@ -260,10 +256,10 @@ export async function updateApplicationStatus(
.single();
if (creator?.id !== application.aethex_opportunities.posted_by_id) {
return new Response(
JSON.stringify({ error: "Unauthorized" }),
{ status: 403, headers: { "Content-Type": "application/json" } }
);
return new Response(JSON.stringify({ error: "Unauthorized" }), {
status: 403,
headers: { "Content-Type": "application/json" },
});
}
const { data, error } = await supabase
@ -287,14 +283,14 @@ export async function updateApplicationStatus(
console.error("Error updating application:", error);
return new Response(
JSON.stringify({ error: "Failed to update application" }),
{ status: 500, headers: { "Content-Type": "application/json" } }
{ status: 500, headers: { "Content-Type": "application/json" } },
);
}
}
export async function withdrawApplication(
applicationId: string,
userId: string
userId: string,
) {
try {
// Verify user owns this application
@ -305,10 +301,10 @@ export async function withdrawApplication(
.single();
if (!application) {
return new Response(
JSON.stringify({ error: "Application not found" }),
{ status: 404, headers: { "Content-Type": "application/json" } }
);
return new Response(JSON.stringify({ error: "Application not found" }), {
status: 404,
headers: { "Content-Type": "application/json" },
});
}
const { data: creator } = await supabase
@ -318,10 +314,10 @@ export async function withdrawApplication(
.single();
if (creator?.id !== application.creator_id) {
return new Response(
JSON.stringify({ error: "Unauthorized" }),
{ status: 403, headers: { "Content-Type": "application/json" } }
);
return new Response(JSON.stringify({ error: "Unauthorized" }), {
status: 403,
headers: { "Content-Type": "application/json" },
});
}
const { error } = await supabase
@ -339,7 +335,7 @@ export async function withdrawApplication(
console.error("Error withdrawing application:", error);
return new Response(
JSON.stringify({ error: "Failed to withdraw application" }),
{ status: 500, headers: { "Content-Type": "application/json" } }
{ status: 500, headers: { "Content-Type": "application/json" } },
);
}
}

View file

@ -28,7 +28,7 @@ export async function getCreators(req: Request) {
created_at,
aethex_projects(count)
`,
{ count: "exact" }
{ count: "exact" },
)
.eq("is_discoverable", true)
.order("created_at", { ascending: false });
@ -38,9 +38,7 @@ export async function getCreators(req: Request) {
}
if (search) {
query = query.or(
`username.ilike.%${search}%,bio.ilike.%${search}%`
);
query = query.or(`username.ilike.%${search}%,bio.ilike.%${search}%`);
}
const start = (page - 1) * limit;
@ -63,14 +61,14 @@ export async function getCreators(req: Request) {
{
status: 200,
headers: { "Content-Type": "application/json" },
}
},
);
} catch (error) {
console.error("Error fetching creators:", error);
return new Response(
JSON.stringify({ error: "Failed to fetch creators" }),
{ status: 500, headers: { "Content-Type": "application/json" } }
);
return new Response(JSON.stringify({ error: "Failed to fetch creators" }), {
status: 500,
headers: { "Content-Type": "application/json" },
});
}
}
@ -92,7 +90,7 @@ export async function getCreatorByUsername(username: string) {
updated_at,
aethex_projects(id, title, description, url, image_url, tags, is_featured),
aethex_skill_endorsements(skill, count)
`
`,
)
.eq("username", username)
.eq("is_discoverable", true)
@ -117,10 +115,7 @@ export async function getCreatorByUsername(username: string) {
}
}
export async function createCreatorProfile(
req: Request,
userId: string
) {
export async function createCreatorProfile(req: Request, userId: string) {
try {
const body = await req.json();
const {
@ -158,15 +153,12 @@ export async function createCreatorProfile(
console.error("Error creating creator profile:", error);
return new Response(
JSON.stringify({ error: "Failed to create creator profile" }),
{ status: 500, headers: { "Content-Type": "application/json" } }
{ status: 500, headers: { "Content-Type": "application/json" } },
);
}
}
export async function updateCreatorProfile(
req: Request,
userId: string
) {
export async function updateCreatorProfile(req: Request, userId: string) {
try {
const body = await req.json();
const {
@ -209,15 +201,12 @@ export async function updateCreatorProfile(
console.error("Error updating creator profile:", error);
return new Response(
JSON.stringify({ error: "Failed to update creator profile" }),
{ status: 500, headers: { "Content-Type": "application/json" } }
{ status: 500, headers: { "Content-Type": "application/json" } },
);
}
}
export async function addProjectToCreator(
req: Request,
creatorId: string
) {
export async function addProjectToCreator(req: Request, creatorId: string) {
try {
const body = await req.json();
const { title, description, url, image_url, tags, is_featured } = body;
@ -244,17 +233,17 @@ export async function addProjectToCreator(
});
} catch (error) {
console.error("Error adding project:", error);
return new Response(
JSON.stringify({ error: "Failed to add project" }),
{ status: 500, headers: { "Content-Type": "application/json" } }
);
return new Response(JSON.stringify({ error: "Failed to add project" }), {
status: 500,
headers: { "Content-Type": "application/json" },
});
}
}
export async function endorseSkill(
req: Request,
creatorId: string,
endorsedByUserId: string
endorsedByUserId: string,
) {
try {
const body = await req.json();
@ -270,7 +259,7 @@ export async function endorseSkill(
if (!endorsingCreator) {
return new Response(
JSON.stringify({ error: "Endorsing user not found" }),
{ status: 404, headers: { "Content-Type": "application/json" } }
{ status: 404, headers: { "Content-Type": "application/json" } },
);
}
@ -292,9 +281,9 @@ export async function endorseSkill(
});
} catch (error) {
console.error("Error endorsing skill:", error);
return new Response(
JSON.stringify({ error: "Failed to endorse skill" }),
{ status: 500, headers: { "Content-Type": "application/json" } }
);
return new Response(JSON.stringify({ error: "Failed to endorse skill" }), {
status: 500,
headers: { "Content-Type": "application/json" },
});
}
}

View file

@ -5,10 +5,7 @@ const supabaseServiceRole = process.env.SUPABASE_SERVICE_ROLE || "";
const supabase = createClient(supabaseUrl, supabaseServiceRole);
export async function linkDevConnectAccount(
req: Request,
userId: string
) {
export async function linkDevConnectAccount(req: Request, userId: string) {
try {
const body = await req.json();
const { devconnect_username, devconnect_profile_url } = body;
@ -16,7 +13,7 @@ export async function linkDevConnectAccount(
if (!devconnect_username) {
return new Response(
JSON.stringify({ error: "DevConnect username is required" }),
{ status: 400, headers: { "Content-Type": "application/json" } }
{ status: 400, headers: { "Content-Type": "application/json" } },
);
}
@ -29,8 +26,10 @@ export async function linkDevConnectAccount(
if (!creator) {
return new Response(
JSON.stringify({ error: "Creator profile not found. Create profile first." }),
{ status: 404, headers: { "Content-Type": "application/json" } }
JSON.stringify({
error: "Creator profile not found. Create profile first.",
}),
{ status: 404, headers: { "Content-Type": "application/json" } },
);
}
@ -50,7 +49,9 @@ export async function linkDevConnectAccount(
.from("aethex_devconnect_links")
.update({
devconnect_username,
devconnect_profile_url: devconnect_profile_url || `https://devconnect.com/${devconnect_username}`,
devconnect_profile_url:
devconnect_profile_url ||
`https://devconnect.com/${devconnect_username}`,
})
.eq("aethex_creator_id", creator.id)
.select()
@ -66,7 +67,9 @@ export async function linkDevConnectAccount(
.insert({
aethex_creator_id: creator.id,
devconnect_username,
devconnect_profile_url: devconnect_profile_url || `https://devconnect.com/${devconnect_username}`,
devconnect_profile_url:
devconnect_profile_url ||
`https://devconnect.com/${devconnect_username}`,
})
.select()
.single();
@ -89,7 +92,7 @@ export async function linkDevConnectAccount(
console.error("Error linking DevConnect account:", error);
return new Response(
JSON.stringify({ error: "Failed to link DevConnect account" }),
{ status: 500, headers: { "Content-Type": "application/json" } }
{ status: 500, headers: { "Content-Type": "application/json" } },
);
}
}
@ -106,7 +109,7 @@ export async function getDevConnectLink(userId: string) {
if (!creator) {
return new Response(
JSON.stringify({ error: "Creator profile not found" }),
{ status: 404, headers: { "Content-Type": "application/json" } }
{ status: 404, headers: { "Content-Type": "application/json" } },
);
}
@ -128,7 +131,7 @@ export async function getDevConnectLink(userId: string) {
console.error("Error fetching DevConnect link:", error);
return new Response(
JSON.stringify({ error: "Failed to fetch DevConnect link" }),
{ status: 500, headers: { "Content-Type": "application/json" } }
{ status: 500, headers: { "Content-Type": "application/json" } },
);
}
}
@ -145,7 +148,7 @@ export async function unlinkDevConnectAccount(userId: string) {
if (!creator) {
return new Response(
JSON.stringify({ error: "Creator profile not found" }),
{ status: 404, headers: { "Content-Type": "application/json" } }
{ status: 404, headers: { "Content-Type": "application/json" } },
);
}
@ -170,15 +173,12 @@ export async function unlinkDevConnectAccount(userId: string) {
console.error("Error unlinking DevConnect account:", error);
return new Response(
JSON.stringify({ error: "Failed to unlink DevConnect account" }),
{ status: 500, headers: { "Content-Type": "application/json" } }
{ status: 500, headers: { "Content-Type": "application/json" } },
);
}
}
export async function verifyDevConnectLink(
req: Request,
userId: string
) {
export async function verifyDevConnectLink(req: Request, userId: string) {
try {
const body = await req.json();
const { verification_code } = body;
@ -193,7 +193,7 @@ export async function verifyDevConnectLink(
if (!creator) {
return new Response(
JSON.stringify({ error: "Creator profile not found" }),
{ status: 404, headers: { "Content-Type": "application/json" } }
{ status: 404, headers: { "Content-Type": "application/json" } },
);
}
@ -216,7 +216,7 @@ export async function verifyDevConnectLink(
console.error("Error verifying DevConnect link:", error);
return new Response(
JSON.stringify({ error: "Failed to verify DevConnect link" }),
{ status: 500, headers: { "Content-Type": "application/json" } }
{ status: 500, headers: { "Content-Type": "application/json" } },
);
}
}

View file

@ -34,7 +34,7 @@ export async function getOpportunities(req: Request) {
created_at,
aethex_applications(count)
`,
{ count: "exact" }
{ count: "exact" },
)
.eq("status", "open");
@ -43,9 +43,7 @@ export async function getOpportunities(req: Request) {
}
if (search) {
query = query.or(
`title.ilike.%${search}%,description.ilike.%${search}%`
);
query = query.or(`title.ilike.%${search}%,description.ilike.%${search}%`);
}
if (jobType) {
@ -80,13 +78,13 @@ export async function getOpportunities(req: Request) {
{
status: 200,
headers: { "Content-Type": "application/json" },
}
},
);
} catch (error) {
console.error("Error fetching opportunities:", error);
return new Response(
JSON.stringify({ error: "Failed to fetch opportunities" }),
{ status: 500, headers: { "Content-Type": "application/json" } }
{ status: 500, headers: { "Content-Type": "application/json" } },
);
}
}
@ -111,7 +109,7 @@ export async function getOpportunityById(opportunityId: string) {
created_at,
updated_at,
aethex_applications(count)
`
`,
)
.eq("id", opportunityId)
.eq("status", "open")
@ -126,10 +124,7 @@ export async function getOpportunityById(opportunityId: string) {
}
}
export async function createOpportunity(
req: Request,
userId: string
) {
export async function createOpportunity(req: Request, userId: string) {
try {
const body = await req.json();
const {
@ -151,8 +146,10 @@ export async function createOpportunity(
if (!creator) {
return new Response(
JSON.stringify({ error: "Creator profile not found. Create profile first." }),
{ status: 404, headers: { "Content-Type": "application/json" } }
JSON.stringify({
error: "Creator profile not found. Create profile first.",
}),
{ status: 404, headers: { "Content-Type": "application/json" } },
);
}
@ -182,7 +179,7 @@ export async function createOpportunity(
console.error("Error creating opportunity:", error);
return new Response(
JSON.stringify({ error: "Failed to create opportunity" }),
{ status: 500, headers: { "Content-Type": "application/json" } }
{ status: 500, headers: { "Content-Type": "application/json" } },
);
}
}
@ -190,7 +187,7 @@ export async function createOpportunity(
export async function updateOpportunity(
req: Request,
opportunityId: string,
userId: string
userId: string,
) {
try {
const body = await req.json();
@ -212,10 +209,10 @@ export async function updateOpportunity(
.single();
if (!opportunity) {
return new Response(
JSON.stringify({ error: "Opportunity not found" }),
{ status: 404, headers: { "Content-Type": "application/json" } }
);
return new Response(JSON.stringify({ error: "Opportunity not found" }), {
status: 404,
headers: { "Content-Type": "application/json" },
});
}
const { data: creator } = await supabase
@ -225,10 +222,10 @@ export async function updateOpportunity(
.single();
if (creator?.id !== opportunity.posted_by_id) {
return new Response(
JSON.stringify({ error: "Unauthorized" }),
{ status: 403, headers: { "Content-Type": "application/json" } }
);
return new Response(JSON.stringify({ error: "Unauthorized" }), {
status: 403,
headers: { "Content-Type": "application/json" },
});
}
const { data, error } = await supabase
@ -257,15 +254,12 @@ export async function updateOpportunity(
console.error("Error updating opportunity:", error);
return new Response(
JSON.stringify({ error: "Failed to update opportunity" }),
{ status: 500, headers: { "Content-Type": "application/json" } }
{ status: 500, headers: { "Content-Type": "application/json" } },
);
}
}
export async function closeOpportunity(
opportunityId: string,
userId: string
) {
export async function closeOpportunity(opportunityId: string, userId: string) {
try {
// Verify user owns this opportunity
const { data: opportunity } = await supabase
@ -275,10 +269,10 @@ export async function closeOpportunity(
.single();
if (!opportunity) {
return new Response(
JSON.stringify({ error: "Opportunity not found" }),
{ status: 404, headers: { "Content-Type": "application/json" } }
);
return new Response(JSON.stringify({ error: "Opportunity not found" }), {
status: 404,
headers: { "Content-Type": "application/json" },
});
}
const { data: creator } = await supabase
@ -288,10 +282,10 @@ export async function closeOpportunity(
.single();
if (creator?.id !== opportunity.posted_by_id) {
return new Response(
JSON.stringify({ error: "Unauthorized" }),
{ status: 403, headers: { "Content-Type": "application/json" } }
);
return new Response(JSON.stringify({ error: "Unauthorized" }), {
status: 403,
headers: { "Content-Type": "application/json" },
});
}
const { data, error } = await supabase
@ -314,7 +308,7 @@ export async function closeOpportunity(
console.error("Error closing opportunity:", error);
return new Response(
JSON.stringify({ error: "Failed to close opportunity" }),
{ status: 500, headers: { "Content-Type": "application/json" } }
{ status: 500, headers: { "Content-Type": "application/json" } },
);
}
}

View file

@ -144,7 +144,10 @@ const App = () => (
/>
<Route path="/profile" element={<Profile />} />
<Route path="/profile/me" element={<Profile />} />
<Route path="/profile/applications" element={<MyApplications />} />
<Route
path="/profile/applications"
element={<MyApplications />}
/>
<Route path="/developers" element={<DevelopersDirectory />} />
<Route
@ -183,9 +186,15 @@ const App = () => (
{/* Creator Network routes */}
<Route path="/creators" element={<CreatorDirectory />} />
<Route path="/creators/:username" element={<CreatorProfile />} />
<Route
path="/creators/:username"
element={<CreatorProfile />}
/>
<Route path="/opportunities" element={<OpportunitiesHub />} />
<Route path="/opportunities/:id" element={<OpportunityDetail />} />
<Route
path="/opportunities/:id"
element={<OpportunityDetail />}
/>
{/* Service routes */}
<Route path="/game-development" element={<GameDevelopment />} />

View file

@ -63,7 +63,7 @@ export async function getMyApplications(filters?: {
export async function getApplicationsForOpportunity(opportunityId: string) {
const response = await fetch(
`${API_BASE}/api/opportunities/${opportunityId}/applications`
`${API_BASE}/api/opportunities/${opportunityId}/applications`,
);
if (!response.ok) throw new Error("Failed to fetch applications");
const data = await response.json();
@ -88,20 +88,28 @@ export async function updateApplicationStatus(
data: {
status: "reviewing" | "accepted" | "rejected";
response_message?: string;
}
},
): Promise<Application> {
const response = await fetch(`${API_BASE}/api/applications/${applicationId}/status`, {
const response = await fetch(
`${API_BASE}/api/applications/${applicationId}/status`,
{
method: "PUT",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(data),
});
},
);
if (!response.ok) throw new Error("Failed to update application");
return response.json();
}
export async function withdrawApplication(applicationId: string): Promise<void> {
const response = await fetch(`${API_BASE}/api/applications/${applicationId}`, {
export async function withdrawApplication(
applicationId: string,
): Promise<void> {
const response = await fetch(
`${API_BASE}/api/applications/${applicationId}`,
{
method: "DELETE",
});
},
);
if (!response.ok) throw new Error("Failed to withdraw application");
}

View file

@ -130,35 +130,50 @@ export async function addProject(data: {
return response.json();
}
export async function updateProject(projectId: string, data: {
export async function updateProject(
projectId: string,
data: {
title?: string;
description?: string;
url?: string;
image_url?: string;
tags?: string[];
is_featured?: boolean;
}): Promise<Project> {
const response = await fetch(`${API_BASE}/api/creators/me/projects/${projectId}`, {
},
): Promise<Project> {
const response = await fetch(
`${API_BASE}/api/creators/me/projects/${projectId}`,
{
method: "PUT",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(data),
});
},
);
if (!response.ok) throw new Error("Failed to update project");
return response.json();
}
export async function deleteProject(projectId: string): Promise<void> {
const response = await fetch(`${API_BASE}/api/creators/me/projects/${projectId}`, {
const response = await fetch(
`${API_BASE}/api/creators/me/projects/${projectId}`,
{
method: "DELETE",
});
},
);
if (!response.ok) throw new Error("Failed to delete project");
}
export async function endorseSkill(creatorId: string, skill: string): Promise<void> {
const response = await fetch(`${API_BASE}/api/creators/${creatorId}/endorse`, {
export async function endorseSkill(
creatorId: string,
skill: string,
): Promise<void> {
const response = await fetch(
`${API_BASE}/api/creators/${creatorId}/endorse`,
{
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ skill }),
});
},
);
if (!response.ok) throw new Error("Failed to endorse skill");
}

View file

@ -36,7 +36,9 @@ export async function unlinkDevConnectAccount(): Promise<void> {
if (!response.ok) throw new Error("Failed to unlink DevConnect account");
}
export async function verifyDevConnectLink(verificationCode: string): Promise<DevConnectLink> {
export async function verifyDevConnectLink(
verificationCode: string,
): Promise<DevConnectLink> {
const response = await fetch(`${API_BASE}/api/devconnect/link/verify`, {
method: "POST",
headers: { "Content-Type": "application/json" },

View file

@ -57,7 +57,8 @@ export async function getOpportunities(filters?: {
if (filters?.arm) params.append("arm", filters.arm);
if (filters?.search) params.append("search", filters.search);
if (filters?.jobType) params.append("jobType", filters.jobType);
if (filters?.experienceLevel) params.append("experienceLevel", filters.experienceLevel);
if (filters?.experienceLevel)
params.append("experienceLevel", filters.experienceLevel);
if (filters?.sort) params.append("sort", filters.sort);
if (filters?.page) params.append("page", String(filters.page));
if (filters?.limit) params.append("limit", String(filters.limit));
@ -73,7 +74,9 @@ export async function getOpportunityById(id: string): Promise<Opportunity> {
return response.json();
}
export async function createOpportunity(data: CreateOpportunityData): Promise<Opportunity> {
export async function createOpportunity(
data: CreateOpportunityData,
): Promise<Opportunity> {
const response = await fetch(`${API_BASE}/api/opportunities`, {
method: "POST",
headers: { "Content-Type": "application/json" },
@ -85,7 +88,7 @@ export async function createOpportunity(data: CreateOpportunityData): Promise<Op
export async function updateOpportunity(
id: string,
data: Partial<CreateOpportunityData>
data: Partial<CreateOpportunityData>,
): Promise<Opportunity> {
const response = await fetch(`${API_BASE}/api/opportunities/${id}`, {
method: "PUT",
@ -104,7 +107,9 @@ export async function closeOpportunity(id: string): Promise<void> {
}
export async function getApplicationsForOpportunity(opportunityId: string) {
const response = await fetch(`${API_BASE}/api/opportunities/${opportunityId}/applications`);
const response = await fetch(
`${API_BASE}/api/opportunities/${opportunityId}/applications`,
);
if (!response.ok) throw new Error("Failed to fetch applications");
return response.json();
}

View file

@ -35,7 +35,8 @@ export interface ArmBadgeProps {
export function ArmBadge({ arm, size = "md" }: ArmBadgeProps) {
const colors = ARM_COLORS[arm.toLowerCase()] || ARM_COLORS.labs;
const sizeClass = size === "sm" ? "text-xs" : size === "lg" ? "text-base" : "text-sm";
const sizeClass =
size === "sm" ? "text-xs" : size === "lg" ? "text-base" : "text-sm";
return (
<Badge className={`${colors.bg} border-0 ${colors.text} ${sizeClass}`}>

View file

@ -19,7 +19,9 @@ export function CreatorCard({ creator }: CreatorCardProps) {
<div className="flex items-start justify-between mb-4">
<Avatar className="h-16 w-16">
<AvatarImage src={creator.avatar_url} alt={creator.username} />
<AvatarFallback>{creator.username.charAt(0).toUpperCase()}</AvatarFallback>
<AvatarFallback>
{creator.username.charAt(0).toUpperCase()}
</AvatarFallback>
</Avatar>
{creator.devconnect_linked && (
<div className="text-xs bg-cyan-500/10 text-cyan-300 px-2 py-1 rounded-full border border-cyan-500/20">

View file

@ -47,8 +47,10 @@ export function DevConnectLinkModal({
onSuccess?.();
} catch (error) {
toast(
error instanceof Error ? error.message : "Failed to link DevConnect account",
"error"
error instanceof Error
? error.message
: "Failed to link DevConnect account",
"error",
);
} finally {
setIsLoading(false);
@ -61,7 +63,8 @@ export function DevConnectLinkModal({
<AlertDialogHeader>
<AlertDialogTitle>Link Your DevConnect Account</AlertDialogTitle>
<AlertDialogDescription>
Enter your DevConnect username to link your profile. This allows you to showcase your presence on both platforms.
Enter your DevConnect username to link your profile. This allows you
to showcase your presence on both platforms.
</AlertDialogDescription>
</AlertDialogHeader>
@ -79,7 +82,8 @@ export function DevConnectLinkModal({
/>
</div>
<p className="text-xs text-gray-500">
Your username will be used to create a link to your DevConnect profile
Your username will be used to create a link to your DevConnect
profile
</p>
</div>
</div>

View file

@ -29,7 +29,8 @@ export function OpportunityCard({ opportunity }: OpportunityCardProps) {
const formatSalary = (min?: number, max?: number) => {
if (!min && !max) return "Not specified";
if (min && max) return `$${min.toLocaleString()} - $${max.toLocaleString()}`;
if (min && max)
return `$${min.toLocaleString()} - $${max.toLocaleString()}`;
if (min) return `$${min.toLocaleString()}+`;
if (max) return `Up to $${max.toLocaleString()}`;
};
@ -41,11 +42,15 @@ export function OpportunityCard({ opportunity }: OpportunityCardProps) {
<div className="flex items-start gap-3 flex-1">
<Avatar className="h-12 w-12">
<AvatarImage src={poster.avatar_url} alt={poster.username} />
<AvatarFallback>{poster.username.charAt(0).toUpperCase()}</AvatarFallback>
<AvatarFallback>
{poster.username.charAt(0).toUpperCase()}
</AvatarFallback>
</Avatar>
<div>
<p className="text-xs text-gray-400">Posted by</p>
<p className="text-sm font-semibold text-white">@{poster.username}</p>
<p className="text-sm font-semibold text-white">
@{poster.username}
</p>
</div>
</div>
<div className="text-xs text-gray-400 whitespace-nowrap">
@ -86,7 +91,9 @@ export function OpportunityCard({ opportunity }: OpportunityCardProps) {
<div className="flex items-center text-gray-400">
<Clock className="h-4 w-4 mr-2 text-blue-400" />
{opportunity.aethex_applications.count}{" "}
{opportunity.aethex_applications.count === 1 ? "applicant" : "applicants"}
{opportunity.aethex_applications.count === 1
? "applicant"
: "applicants"}
</div>
)}
</div>

View file

@ -15,10 +15,34 @@ interface CreatorProfileProps {
}
const AVAILABLE_ARMS = [
{ id: "labs", name: "Labs", description: "R&D & Innovation", color: "from-yellow-500 to-orange-500", icon: Zap },
{ id: "gameforge", name: "GameForge", description: "Game Development", color: "from-green-500 to-emerald-500", icon: Code },
{ id: "corp", name: "Corp", description: "Enterprise Consulting", color: "from-blue-500 to-cyan-500", icon: Users },
{ id: "foundation", name: "Foundation", description: "Education & Open Source", color: "from-red-500 to-pink-500", icon: Users },
{
id: "labs",
name: "Labs",
description: "R&D & Innovation",
color: "from-yellow-500 to-orange-500",
icon: Zap,
},
{
id: "gameforge",
name: "GameForge",
description: "Game Development",
color: "from-green-500 to-emerald-500",
icon: Code,
},
{
id: "corp",
name: "Corp",
description: "Enterprise Consulting",
color: "from-blue-500 to-cyan-500",
icon: Users,
},
{
id: "foundation",
name: "Foundation",
description: "Education & Open Source",
color: "from-red-500 to-pink-500",
icon: Users,
},
];
const SKILL_SUGGESTIONS = [
@ -48,7 +72,11 @@ export default function CreatorProfile({
totalSteps,
}: CreatorProfileProps) {
const [inputValue, setInputValue] = useState("");
const creatorData = data.creatorProfile || { bio: "", skills: [], primaryArm: "" };
const creatorData = data.creatorProfile || {
bio: "",
skills: [],
primaryArm: "",
};
const canProceed = useMemo(() => {
return creatorData.primaryArm && creatorData.skills.length > 0;
@ -138,7 +166,9 @@ export default function CreatorProfile({
</div>
<div>
<div className="font-semibold text-white">{arm.name}</div>
<div className="text-xs text-gray-400">{arm.description}</div>
<div className="text-xs text-gray-400">
{arm.description}
</div>
</div>
</div>
</button>
@ -150,7 +180,10 @@ export default function CreatorProfile({
{/* Skills Selection */}
<div className="space-y-4">
<label className="text-sm font-semibold text-gray-200 block">
What are your skills? * <span className="text-xs text-gray-400 font-normal">(Add at least 1)</span>
What are your skills? *{" "}
<span className="text-xs text-gray-400 font-normal">
(Add at least 1)
</span>
</label>
{/* Skills Input */}
@ -221,11 +254,7 @@ export default function CreatorProfile({
{/* Navigation */}
<div className="flex gap-3 pt-8 border-t border-gray-800">
<Button
onClick={prevStep}
variant="outline"
className="flex-1"
>
<Button onClick={prevStep} variant="outline" className="flex-1">
Back
</Button>
<Button

View file

@ -1,7 +1,16 @@
import Layout from "@/components/Layout";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Badge } from "@/components/ui/badge";
import { Beaker, Briefcase, Heart, Network, Lock, TrendingUp, Users, Shield } from "lucide-react";
import {
Beaker,
Briefcase,
Heart,
Network,
Lock,
TrendingUp,
Users,
Shield,
} from "lucide-react";
export default function About() {
const pillars = [
@ -11,9 +20,16 @@ export default function About() {
color: "from-yellow-500 to-orange-500",
icon: Beaker,
tagline: "The Innovation Engine",
description: "Bleeding-edge R&D focused on proprietary technology and thought leadership",
focus: ["Advanced AI & Machine Learning", "Next-gen Web Architectures", "Procedural Content Generation", "Graphics & Performance Optimization"],
function: "High-burn speculative research creating lasting competitive advantage",
description:
"Bleeding-edge R&D focused on proprietary technology and thought leadership",
focus: [
"Advanced AI & Machine Learning",
"Next-gen Web Architectures",
"Procedural Content Generation",
"Graphics & Performance Optimization",
],
function:
"High-burn speculative research creating lasting competitive advantage",
},
{
id: "corp",
@ -21,9 +37,16 @@ export default function About() {
color: "from-blue-500 to-cyan-500",
icon: Briefcase,
tagline: "The Profit Engine",
description: "High-margin consulting and enterprise solutions subsidizing R&D investment",
focus: ["Custom Software Development", "Enterprise Consulting", "Game Development Services", "Digital Transformation"],
function: "Stable revenue generation and financial discipline for investor confidence",
description:
"High-margin consulting and enterprise solutions subsidizing R&D investment",
focus: [
"Custom Software Development",
"Enterprise Consulting",
"Game Development Services",
"Digital Transformation",
],
function:
"Stable revenue generation and financial discipline for investor confidence",
},
{
id: "foundation",
@ -31,9 +54,16 @@ export default function About() {
color: "from-red-500 to-pink-500",
icon: Heart,
tagline: "Community & Education",
description: "Open-source and educational mission building goodwill and talent pipeline",
focus: ["Open-Source Code Repositories", "Educational Curriculum", "Community Workshops", "Talent Development"],
function: "Goodwill moat, specialized talent pipeline, and ecosystem growth",
description:
"Open-source and educational mission building goodwill and talent pipeline",
focus: [
"Open-Source Code Repositories",
"Educational Curriculum",
"Community Workshops",
"Talent Development",
],
function:
"Goodwill moat, specialized talent pipeline, and ecosystem growth",
},
{
id: "devlink",
@ -41,28 +71,38 @@ export default function About() {
color: "from-cyan-500 to-blue-500",
icon: Network,
tagline: "Talent Network",
description: "B2B SaaS platform for specialized professional networking and recruitment",
focus: ["Recruitment Platform", "Talent Matching", "Professional Network", "Career Development"],
function: "Access-based moat and lock-in effect for specialized human capital",
description:
"B2B SaaS platform for specialized professional networking and recruitment",
focus: [
"Recruitment Platform",
"Talent Matching",
"Professional Network",
"Career Development",
],
function:
"Access-based moat and lock-in effect for specialized human capital",
},
];
const moats = [
{
title: "Technological Moat",
description: "Labs creates proprietary AI and architectural innovations licensed to Corp and Dev-Link",
description:
"Labs creates proprietary AI and architectural innovations licensed to Corp and Dev-Link",
icon: Shield,
color: "bg-yellow-500/20 border-yellow-500/40 text-yellow-300",
},
{
title: "Talent Moat",
description: "Dev-Link and Foundation establish curated access to specialized immersive developers",
description:
"Dev-Link and Foundation establish curated access to specialized immersive developers",
icon: Users,
color: "bg-cyan-500/20 border-cyan-500/40 text-cyan-300",
},
{
title: "Community Moat",
description: "Foundation builds trust and goodwill through open-source and educational mission",
description:
"Foundation builds trust and goodwill through open-source and educational mission",
icon: Heart,
color: "bg-red-500/20 border-red-500/40 text-red-300",
},
@ -71,22 +111,26 @@ export default function About() {
const benefits = [
{
title: "Risk Segregation",
description: "Labs' high-burn R&D is isolated from operational liabilities, protecting core assets",
description:
"Labs' high-burn R&D is isolated from operational liabilities, protecting core assets",
icon: Lock,
},
{
title: "Tax Optimization",
description: "Corp and Dev-Link as profit centers, Foundation as 501(c)(3), optimizing UBIT exposure",
description:
"Corp and Dev-Link as profit centers, Foundation as 501(c)(3), optimizing UBIT exposure",
icon: TrendingUp,
},
{
title: "Investor Confidence",
description: "Low burn multiples demonstrated through profitable Corp operations subsidizing R&D",
description:
"Low burn multiples demonstrated through profitable Corp operations subsidizing R&D",
icon: TrendingUp,
},
{
title: "IP Centralization",
description: "Labs acts as IP holding company with licensing to subsidiaries via transfer pricing",
description:
"Labs acts as IP holding company with licensing to subsidiaries via transfer pricing",
icon: Shield,
},
];
@ -98,10 +142,17 @@ export default function About() {
<section className="py-16 lg:py-24 border-b border-gray-800">
<div className="container mx-auto max-w-6xl px-4">
<h1 className="text-5xl lg:text-7xl font-black mb-6">
Building an Integrated <span className="bg-gradient-to-r from-yellow-300 via-blue-300 to-red-300 bg-clip-text text-transparent">Ecosystem</span>
Building an Integrated{" "}
<span className="bg-gradient-to-r from-yellow-300 via-blue-300 to-red-300 bg-clip-text text-transparent">
Ecosystem
</span>
</h1>
<p className="text-xl text-gray-300 max-w-3xl">
AeThex operates as a unified four-pillar organization that combines speculative innovation, profitable operations, community impact, and specialized talent acquisition. This structure creates multiple reinforcing competitive moats while managing risk and maintaining investor confidence.
AeThex operates as a unified four-pillar organization that
combines speculative innovation, profitable operations, community
impact, and specialized talent acquisition. This structure creates
multiple reinforcing competitive moats while managing risk and
maintaining investor confidence.
</p>
</div>
</section>
@ -114,29 +165,44 @@ export default function About() {
{pillars.map((pillar) => {
const Icon = pillar.icon;
return (
<Card key={pillar.id} className="bg-gray-900/50 border-gray-800 hover:border-gray-700 transition-all">
<Card
key={pillar.id}
className="bg-gray-900/50 border-gray-800 hover:border-gray-700 transition-all"
>
<CardHeader>
<div className={`w-12 h-12 rounded-lg bg-gradient-to-r ${pillar.color} flex items-center justify-center mb-4`}>
<div
className={`w-12 h-12 rounded-lg bg-gradient-to-r ${pillar.color} flex items-center justify-center mb-4`}
>
<Icon className="h-6 w-6 text-white" />
</div>
<CardTitle>
<div className="text-2xl font-black">{pillar.name}</div>
<div className="text-sm font-normal text-gray-400 mt-1">{pillar.tagline}</div>
<div className="text-sm font-normal text-gray-400 mt-1">
{pillar.tagline}
</div>
</CardTitle>
</CardHeader>
<CardContent className="space-y-4">
<p className="text-gray-300">{pillar.description}</p>
<div>
<p className="text-xs font-semibold text-gray-400 mb-2">KEY AREAS</p>
<p className="text-xs font-semibold text-gray-400 mb-2">
KEY AREAS
</p>
<ul className="space-y-1">
{pillar.focus.map((item, idx) => (
<li key={idx} className="text-sm text-gray-400"> {item}</li>
<li key={idx} className="text-sm text-gray-400">
{item}
</li>
))}
</ul>
</div>
<div className="pt-4 border-t border-gray-800">
<p className="text-xs font-semibold text-gray-400 mb-2">STRATEGIC FUNCTION</p>
<p className="text-sm text-gray-300">{pillar.function}</p>
<p className="text-xs font-semibold text-gray-400 mb-2">
STRATEGIC FUNCTION
</p>
<p className="text-sm text-gray-300">
{pillar.function}
</p>
</div>
</CardContent>
</Card>
@ -149,21 +215,30 @@ export default function About() {
{/* Competitive Moats */}
<section className="py-16 border-b border-gray-800">
<div className="container mx-auto max-w-6xl px-4">
<h2 className="text-3xl font-bold mb-6">Integrated Competitive Moats</h2>
<h2 className="text-3xl font-bold mb-6">
Integrated Competitive Moats
</h2>
<p className="text-gray-300 mb-12 max-w-3xl">
While competitors can replicate technology, the AeThex structure creates multiple, mutually-reinforcing barriers to entry that are far more resilient and defensible.
While competitors can replicate technology, the AeThex structure
creates multiple, mutually-reinforcing barriers to entry that are
far more resilient and defensible.
</p>
<div className="grid md:grid-cols-3 gap-6">
{moats.map((moat, idx) => {
const Icon = moat.icon;
return (
<Card key={idx} className={`border ${moat.color.split(" ")[1]} ${moat.color.split(" ")[0]} bg-black/40`}>
<Card
key={idx}
className={`border ${moat.color.split(" ")[1]} ${moat.color.split(" ")[0]} bg-black/40`}
>
<CardHeader>
<Icon className="h-8 w-8 mb-4" />
<CardTitle className="text-lg">{moat.title}</CardTitle>
</CardHeader>
<CardContent>
<p className="text-sm text-gray-300">{moat.description}</p>
<p className="text-sm text-gray-300">
{moat.description}
</p>
</CardContent>
</Card>
);
@ -185,8 +260,12 @@ export default function About() {
<div className="flex gap-4">
<Icon className="h-6 w-6 text-blue-400 flex-shrink-0" />
<div>
<h3 className="font-semibold mb-2">{benefit.title}</h3>
<p className="text-sm text-gray-400">{benefit.description}</p>
<h3 className="font-semibold mb-2">
{benefit.title}
</h3>
<p className="text-sm text-gray-400">
{benefit.description}
</p>
</div>
</div>
</CardContent>
@ -208,9 +287,14 @@ export default function About() {
</CardHeader>
<CardContent className="space-y-3">
<p className="text-sm text-gray-300">
Benefit Corporation status legally protects our dual mission of profit and public benefit, enabling strategic investment in research and community regardless of leadership or capital changes.
Benefit Corporation status legally protects our dual mission
of profit and public benefit, enabling strategic investment
in research and community regardless of leadership or
capital changes.
</p>
<Badge className="bg-blue-500/20 text-blue-300">Benefit Corp</Badge>
<Badge className="bg-blue-500/20 text-blue-300">
Benefit Corp
</Badge>
</CardContent>
</Card>
<Card className="bg-gray-900/50 border-gray-800">
@ -219,9 +303,14 @@ export default function About() {
</CardHeader>
<CardContent className="space-y-3">
<p className="text-sm text-gray-300">
Wholly-owned C-Corporation subsidiaries with separate boards, bank accounts, and intercompany agreements maintain the corporate veil and protect parent assets from operational liabilities.
Wholly-owned C-Corporation subsidiaries with separate
boards, bank accounts, and intercompany agreements maintain
the corporate veil and protect parent assets from
operational liabilities.
</p>
<Badge className="bg-yellow-500/20 text-yellow-300">Risk Segregation</Badge>
<Badge className="bg-yellow-500/20 text-yellow-300">
Risk Segregation
</Badge>
</CardContent>
</Card>
<Card className="bg-gray-900/50 border-gray-800">
@ -230,9 +319,14 @@ export default function About() {
</CardHeader>
<CardContent className="space-y-3">
<p className="text-sm text-gray-300">
Labs acts as IP Holding Company with formal licensing agreements to Corp and Dev-Link at Fair Market Value, enabling tax-efficient transfer pricing and centralized IP control.
Labs acts as IP Holding Company with formal licensing
agreements to Corp and Dev-Link at Fair Market Value,
enabling tax-efficient transfer pricing and centralized IP
control.
</p>
<Badge className="bg-green-500/20 text-green-300">IPCo Strategy</Badge>
<Badge className="bg-green-500/20 text-green-300">
IPCo Strategy
</Badge>
</CardContent>
</Card>
<Card className="bg-gray-900/50 border-gray-800">
@ -241,9 +335,13 @@ export default function About() {
</CardHeader>
<CardContent className="space-y-3">
<p className="text-sm text-gray-300">
Independent board majority, Fund Accounting, and related-party transaction safeguards prevent private inurement and maintain 501(c)(3) tax-exempt status.
Independent board majority, Fund Accounting, and
related-party transaction safeguards prevent private
inurement and maintain 501(c)(3) tax-exempt status.
</p>
<Badge className="bg-red-500/20 text-red-300">Compliance First</Badge>
<Badge className="bg-red-500/20 text-red-300">
Compliance First
</Badge>
</CardContent>
</Card>
</div>

View file

@ -1154,29 +1154,56 @@ export default function Admin() {
<Lightbulb className="h-5 w-5 text-yellow-400" />
<CardTitle>Labs - Research & Innovation</CardTitle>
</div>
<CardDescription>Research initiatives, publications, and innovation pipeline</CardDescription>
<CardDescription>
Research initiatives, publications, and innovation
pipeline
</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
<div className="grid grid-cols-1 md:grid-cols-4 gap-3">
<div className="bg-background/40 rounded-lg p-4 border border-border/40">
<p className="text-sm text-muted-foreground mb-2">Active Research Projects</p>
<p className="text-2xl font-bold text-yellow-400">12</p>
<p className="text-xs text-gray-400 mt-2"> 3 new this quarter</p>
<p className="text-sm text-muted-foreground mb-2">
Active Research Projects
</p>
<p className="text-2xl font-bold text-yellow-400">
12
</p>
<p className="text-xs text-gray-400 mt-2">
3 new this quarter
</p>
</div>
<div className="bg-background/40 rounded-lg p-4 border border-border/40">
<p className="text-sm text-muted-foreground mb-2">Publications</p>
<p className="text-2xl font-bold text-yellow-400">8</p>
<p className="text-xs text-gray-400 mt-2">Peer-reviewed papers</p>
<p className="text-sm text-muted-foreground mb-2">
Publications
</p>
<p className="text-2xl font-bold text-yellow-400">
8
</p>
<p className="text-xs text-gray-400 mt-2">
Peer-reviewed papers
</p>
</div>
<div className="bg-background/40 rounded-lg p-4 border border-border/40">
<p className="text-sm text-muted-foreground mb-2">Research Team Size</p>
<p className="text-2xl font-bold text-yellow-400">24</p>
<p className="text-xs text-gray-400 mt-2">PhD & researchers</p>
<p className="text-sm text-muted-foreground mb-2">
Research Team Size
</p>
<p className="text-2xl font-bold text-yellow-400">
24
</p>
<p className="text-xs text-gray-400 mt-2">
PhD & researchers
</p>
</div>
<div className="bg-background/40 rounded-lg p-4 border border-border/40">
<p className="text-sm text-muted-foreground mb-2">Impact Citations</p>
<p className="text-2xl font-bold text-yellow-400">342</p>
<p className="text-xs text-gray-400 mt-2">Academic references</p>
<p className="text-sm text-muted-foreground mb-2">
Impact Citations
</p>
<p className="text-2xl font-bold text-yellow-400">
342
</p>
<p className="text-xs text-gray-400 mt-2">
Academic references
</p>
</div>
</div>
</CardContent>
@ -1190,29 +1217,55 @@ export default function Admin() {
<Zap className="h-5 w-5 text-green-400" />
<CardTitle>GameForge - Game Development</CardTitle>
</div>
<CardDescription>Monthly shipping cycles and game production metrics</CardDescription>
<CardDescription>
Monthly shipping cycles and game production metrics
</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
<div className="grid grid-cols-1 md:grid-cols-4 gap-3">
<div className="bg-background/40 rounded-lg p-4 border border-border/40">
<p className="text-sm text-muted-foreground mb-2">Games in Production</p>
<p className="text-2xl font-bold text-green-400">18</p>
<p className="text-xs text-gray-400 mt-2">Active development</p>
<p className="text-sm text-muted-foreground mb-2">
Games in Production
</p>
<p className="text-2xl font-bold text-green-400">
18
</p>
<p className="text-xs text-gray-400 mt-2">
Active development
</p>
</div>
<div className="bg-background/40 rounded-lg p-4 border border-border/40">
<p className="text-sm text-muted-foreground mb-2">Games Shipped</p>
<p className="text-2xl font-bold text-green-400">45</p>
<p className="text-xs text-gray-400 mt-2">This year</p>
<p className="text-sm text-muted-foreground mb-2">
Games Shipped
</p>
<p className="text-2xl font-bold text-green-400">
45
</p>
<p className="text-xs text-gray-400 mt-2">
This year
</p>
</div>
<div className="bg-background/40 rounded-lg p-4 border border-border/40">
<p className="text-sm text-muted-foreground mb-2">Player Base</p>
<p className="text-2xl font-bold text-green-400">2.8M</p>
<p className="text-xs text-gray-400 mt-2">Monthly active</p>
<p className="text-sm text-muted-foreground mb-2">
Player Base
</p>
<p className="text-2xl font-bold text-green-400">
2.8M
</p>
<p className="text-xs text-gray-400 mt-2">
Monthly active
</p>
</div>
<div className="bg-background/40 rounded-lg p-4 border border-border/40">
<p className="text-sm text-muted-foreground mb-2">Avg Shipping Velocity</p>
<p className="text-2xl font-bold text-green-400">3.2x</p>
<p className="text-xs text-gray-400 mt-2">vs industry standard</p>
<p className="text-sm text-muted-foreground mb-2">
Avg Shipping Velocity
</p>
<p className="text-2xl font-bold text-green-400">
3.2x
</p>
<p className="text-xs text-gray-400 mt-2">
vs industry standard
</p>
</div>
</div>
</CardContent>
@ -1226,29 +1279,55 @@ export default function Admin() {
<Briefcase className="h-5 w-5 text-blue-400" />
<CardTitle>Corp - Enterprise Services</CardTitle>
</div>
<CardDescription>Enterprise consulting and business solutions</CardDescription>
<CardDescription>
Enterprise consulting and business solutions
</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
<div className="grid grid-cols-1 md:grid-cols-4 gap-3">
<div className="bg-background/40 rounded-lg p-4 border border-border/40">
<p className="text-sm text-muted-foreground mb-2">Active Clients</p>
<p className="text-2xl font-bold text-blue-400">34</p>
<p className="text-xs text-gray-400 mt-2">Enterprise accounts</p>
<p className="text-sm text-muted-foreground mb-2">
Active Clients
</p>
<p className="text-2xl font-bold text-blue-400">
34
</p>
<p className="text-xs text-gray-400 mt-2">
Enterprise accounts
</p>
</div>
<div className="bg-background/40 rounded-lg p-4 border border-border/40">
<p className="text-sm text-muted-foreground mb-2">ARR</p>
<p className="text-2xl font-bold text-blue-400">$4.2M</p>
<p className="text-xs text-gray-400 mt-2">Annual recurring</p>
<p className="text-sm text-muted-foreground mb-2">
ARR
</p>
<p className="text-2xl font-bold text-blue-400">
$4.2M
</p>
<p className="text-xs text-gray-400 mt-2">
Annual recurring
</p>
</div>
<div className="bg-background/40 rounded-lg p-4 border border-border/40">
<p className="text-sm text-muted-foreground mb-2">Retention Rate</p>
<p className="text-2xl font-bold text-blue-400">94%</p>
<p className="text-xs text-gray-400 mt-2">12-month</p>
<p className="text-sm text-muted-foreground mb-2">
Retention Rate
</p>
<p className="text-2xl font-bold text-blue-400">
94%
</p>
<p className="text-xs text-gray-400 mt-2">
12-month
</p>
</div>
<div className="bg-background/40 rounded-lg p-4 border border-border/40">
<p className="text-sm text-muted-foreground mb-2">Consulting Team</p>
<p className="text-2xl font-bold text-blue-400">16</p>
<p className="text-xs text-gray-400 mt-2">Senior consultants</p>
<p className="text-sm text-muted-foreground mb-2">
Consulting Team
</p>
<p className="text-2xl font-bold text-blue-400">
16
</p>
<p className="text-xs text-gray-400 mt-2">
Senior consultants
</p>
</div>
</div>
</CardContent>
@ -1260,51 +1339,93 @@ export default function Admin() {
<CardHeader>
<div className="flex items-center gap-2">
<Heart className="h-5 w-5 text-red-400" />
<CardTitle>Foundation - Education & Community</CardTitle>
<CardTitle>
Foundation - Education & Community
</CardTitle>
</div>
<CardDescription>Educational impact and talent pipeline effectiveness</CardDescription>
<CardDescription>
Educational impact and talent pipeline effectiveness
</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
<div className="space-y-4">
<div>
<h4 className="font-semibold text-sm mb-3">Student Metrics</h4>
<h4 className="font-semibold text-sm mb-3">
Student Metrics
</h4>
<div className="grid grid-cols-1 md:grid-cols-3 gap-3">
<div className="bg-background/40 rounded-lg p-4 border border-border/40">
<p className="text-sm text-muted-foreground mb-2">Course Completion</p>
<p className="text-2xl font-bold text-green-400">87.5%</p>
<p className="text-xs text-gray-400 mt-2"> +12% YoY</p>
<p className="text-sm text-muted-foreground mb-2">
Course Completion
</p>
<p className="text-2xl font-bold text-green-400">
87.5%
</p>
<p className="text-xs text-gray-400 mt-2">
+12% YoY
</p>
</div>
<div className="bg-background/40 rounded-lg p-4 border border-border/40">
<p className="text-sm text-muted-foreground mb-2">Active Learners</p>
<p className="text-2xl font-bold text-blue-400">342</p>
<p className="text-xs text-gray-400 mt-2"> +28 new</p>
<p className="text-sm text-muted-foreground mb-2">
Active Learners
</p>
<p className="text-2xl font-bold text-blue-400">
342
</p>
<p className="text-xs text-gray-400 mt-2">
+28 new
</p>
</div>
<div className="bg-background/40 rounded-lg p-4 border border-border/40">
<p className="text-sm text-muted-foreground mb-2">Placement Rate</p>
<p className="text-2xl font-bold text-yellow-400">42%</p>
<p className="text-xs text-gray-400 mt-2">54 hired</p>
<p className="text-sm text-muted-foreground mb-2">
Placement Rate
</p>
<p className="text-2xl font-bold text-yellow-400">
42%
</p>
<p className="text-xs text-gray-400 mt-2">
54 hired
</p>
</div>
</div>
</div>
<div>
<h4 className="font-semibold text-sm mb-3">Open Source Impact</h4>
<h4 className="font-semibold text-sm mb-3">
Open Source Impact
</h4>
<div className="grid grid-cols-1 md:grid-cols-4 gap-3">
<div className="bg-background/40 rounded-lg p-3 border border-border/40">
<p className="text-xs text-gray-400 mb-1">GitHub Forks</p>
<p className="text-xl font-bold text-purple-400">2.3K</p>
<p className="text-xs text-gray-400 mb-1">
GitHub Forks
</p>
<p className="text-xl font-bold text-purple-400">
2.3K
</p>
</div>
<div className="bg-background/40 rounded-lg p-3 border border-border/40">
<p className="text-xs text-gray-400 mb-1">PRs</p>
<p className="text-xl font-bold text-green-400">184</p>
<p className="text-xs text-gray-400 mb-1">
PRs
</p>
<p className="text-xl font-bold text-green-400">
184
</p>
</div>
<div className="bg-background/40 rounded-lg p-3 border border-border/40">
<p className="text-xs text-gray-400 mb-1">External Usage</p>
<p className="text-xl font-bold text-blue-400">452</p>
<p className="text-xs text-gray-400 mb-1">
External Usage
</p>
<p className="text-xl font-bold text-blue-400">
452
</p>
</div>
<div className="bg-background/40 rounded-lg p-3 border border-border/40">
<p className="text-xs text-gray-400 mb-1">Contributors</p>
<p className="text-xl font-bold text-cyan-400">67</p>
<p className="text-xs text-gray-400 mb-1">
Contributors
</p>
<p className="text-xl font-bold text-cyan-400">
67
</p>
</div>
</div>
</div>
@ -1320,29 +1441,55 @@ export default function Admin() {
<Users className="h-5 w-5 text-purple-400" />
<CardTitle>Nexus - Talent Marketplace</CardTitle>
</div>
<CardDescription>Creator network and opportunity matching metrics</CardDescription>
<CardDescription>
Creator network and opportunity matching metrics
</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
<div className="grid grid-cols-1 md:grid-cols-4 gap-3">
<div className="bg-background/40 rounded-lg p-4 border border-border/40">
<p className="text-sm text-muted-foreground mb-2">Active Creators</p>
<p className="text-2xl font-bold text-purple-400">1,240</p>
<p className="text-xs text-gray-400 mt-2"> +180 this month</p>
<p className="text-sm text-muted-foreground mb-2">
Active Creators
</p>
<p className="text-2xl font-bold text-purple-400">
1,240
</p>
<p className="text-xs text-gray-400 mt-2">
+180 this month
</p>
</div>
<div className="bg-background/40 rounded-lg p-4 border border-border/40">
<p className="text-sm text-muted-foreground mb-2">Open Opportunities</p>
<p className="text-2xl font-bold text-purple-400">342</p>
<p className="text-xs text-gray-400 mt-2">Across all arms</p>
<p className="text-sm text-muted-foreground mb-2">
Open Opportunities
</p>
<p className="text-2xl font-bold text-purple-400">
342
</p>
<p className="text-xs text-gray-400 mt-2">
Across all arms
</p>
</div>
<div className="bg-background/40 rounded-lg p-4 border border-border/40">
<p className="text-sm text-muted-foreground mb-2">Successful Matches</p>
<p className="text-2xl font-bold text-purple-400">87</p>
<p className="text-xs text-gray-400 mt-2">This quarter</p>
<p className="text-sm text-muted-foreground mb-2">
Successful Matches
</p>
<p className="text-2xl font-bold text-purple-400">
87
</p>
<p className="text-xs text-gray-400 mt-2">
This quarter
</p>
</div>
<div className="bg-background/40 rounded-lg p-4 border border-border/40">
<p className="text-sm text-muted-foreground mb-2">Match Success Rate</p>
<p className="text-2xl font-bold text-purple-400">68%</p>
<p className="text-xs text-gray-400 mt-2">Application to hire</p>
<p className="text-sm text-muted-foreground mb-2">
Match Success Rate
</p>
<p className="text-2xl font-bold text-purple-400">
68%
</p>
<p className="text-xs text-gray-400 mt-2">
Application to hire
</p>
</div>
</div>
</CardContent>

View file

@ -137,7 +137,11 @@ export default function Corp() {
The Profit Engine
</h1>
<p className="text-xl text-blue-100/70 max-w-3xl">
AeThex Corp delivers high-margin enterprise consulting and specialized software development. We leverage proprietary technologies from Labs to create cutting-edge solutions while generating stable, benchmarkable revenue that funds our ambitious R&D roadmap.
AeThex Corp delivers high-margin enterprise consulting and
specialized software development. We leverage proprietary
technologies from Labs to create cutting-edge solutions while
generating stable, benchmarkable revenue that funds our
ambitious R&D roadmap.
</p>
</div>
@ -160,7 +164,9 @@ export default function Corp() {
{/* Creator Network CTAs */}
<div className="mt-8 pt-8 border-t border-blue-400/20">
<p className="text-sm text-blue-200/70 mb-4">Explore our creator community:</p>
<p className="text-sm text-blue-200/70 mb-4">
Explore our creator community:
</p>
<div className="flex flex-col sm:flex-row gap-3">
<Button
size="sm"
@ -221,7 +227,9 @@ export default function Corp() {
className="bg-blue-950/20 border-blue-400/30 hover:border-blue-400/60 transition-all"
>
<CardHeader>
<div className={`w-12 h-12 rounded-lg bg-gradient-to-r ${service.color} flex items-center justify-center text-white mb-4`}>
<div
className={`w-12 h-12 rounded-lg bg-gradient-to-r ${service.color} flex items-center justify-center text-white mb-4`}
>
<Briefcase className="h-6 w-6" />
</div>
<CardTitle className="text-blue-300">
@ -234,7 +242,10 @@ export default function Corp() {
</p>
<ul className="space-y-2">
{service.examples.map((example, i) => (
<li key={i} className="flex items-center gap-2 text-sm text-blue-300">
<li
key={i}
className="flex items-center gap-2 text-sm text-blue-300"
>
<CheckCircle className="h-4 w-4 text-blue-400" />
{example}
</li>
@ -255,10 +266,7 @@ export default function Corp() {
</h2>
<div className="space-y-6">
{recentWins.map((win, idx) => (
<Card
key={idx}
className="bg-blue-950/20 border-blue-400/30"
>
<Card key={idx} className="bg-blue-950/20 border-blue-400/30">
<CardContent className="pt-6">
<div className="grid md:grid-cols-2 lg:grid-cols-4 gap-6">
<div>
@ -326,10 +334,7 @@ export default function Corp() {
description: "Specialized developers for your team",
},
].map((model, idx) => (
<Card
key={idx}
className="bg-blue-950/30 border-blue-400/40"
>
<Card key={idx} className="bg-blue-950/30 border-blue-400/40">
<CardContent className="pt-6 text-center">
<h3 className="font-bold text-blue-300 mb-2">
{model.model}
@ -351,7 +356,8 @@ export default function Corp() {
Ready to Partner?
</h2>
<p className="text-lg text-blue-100/80 mb-8">
Let's discuss your business challenges and build a solution that drives results.
Let's discuss your business challenges and build a solution that
drives results.
</p>
<Button
className="bg-blue-400 text-black shadow-[0_0_30px_rgba(59,130,246,0.35)] hover:bg-blue-300"

View file

@ -137,7 +137,9 @@ export default function DevLink() {
{/* Creator Network CTAs */}
<div className="mt-8 pt-8 border-t border-cyan-400/20">
<p className="text-sm text-cyan-200/70 mb-4">Explore our creator community:</p>
<p className="text-sm text-cyan-200/70 mb-4">
Explore our creator community:
</p>
<div className="flex flex-col sm:flex-row gap-3 justify-center">
<Button
size="sm"

View file

@ -1,6 +1,6 @@
import { useEffect, useState } from 'react';
import { useDiscord } from '@/contexts/DiscordContext';
import LoadingScreen from '@/components/LoadingScreen';
import { useEffect, useState } from "react";
import { useDiscord } from "@/contexts/DiscordContext";
import LoadingScreen from "@/components/LoadingScreen";
interface DiscordSDK {
ready: () => Promise<void>;
@ -22,7 +22,7 @@ export default function DiscordActivity() {
try {
// Discord SDK should be loaded by the script in index.html
if (!(window as any).DiscordSDK) {
throw new Error('Discord SDK not loaded');
throw new Error("Discord SDK not loaded");
}
const discord = (window as any).DiscordSDK as DiscordSDK;
@ -33,13 +33,16 @@ export default function DiscordActivity() {
// Subscribe to close events
if (discord.subscribe) {
discord.subscribe('ACTIVITY_INSTANCE_PARTICIPANTS_UPDATE', (data: any) => {
console.log('Discord participants updated:', data);
});
discord.subscribe(
"ACTIVITY_INSTANCE_PARTICIPANTS_UPDATE",
(data: any) => {
console.log("Discord participants updated:", data);
},
);
}
} catch (err) {
const errorMessage = err instanceof Error ? err.message : String(err);
console.error('Failed to initialize Discord SDK:', err);
console.error("Failed to initialize Discord SDK:", err);
setError(errorMessage);
}
};
@ -50,14 +53,17 @@ export default function DiscordActivity() {
}, []);
if (error) {
const isCloudflareError = error.includes('Direct IP access') || error.includes('Error 1003');
const isSDKError = error.includes('Discord SDK');
const isCloudflareError =
error.includes("Direct IP access") || error.includes("Error 1003");
const isSDKError = error.includes("Discord SDK");
return (
<div className="min-h-screen flex items-center justify-center bg-background p-4">
<div className="max-w-md w-full text-center space-y-6">
<div className="space-y-2">
<h1 className="text-2xl font-bold text-red-400">Connection Error</h1>
<h1 className="text-2xl font-bold text-red-400">
Connection Error
</h1>
<p className="text-sm text-muted-foreground">
Unable to initialize Discord Activity
</p>
@ -68,9 +74,12 @@ export default function DiscordActivity() {
{isCloudflareError && (
<div className="space-y-2 pt-2 border-t border-red-400/30">
<p className="text-xs text-red-200 font-semibold">🌐 Cloudflare Blocking Access</p>
<p className="text-xs text-red-200 font-semibold">
🌐 Cloudflare Blocking Access
</p>
<p className="text-xs text-red-200/80">
This error occurs when accessing AeThex via an IP address. Please access through the proper domain:
This error occurs when accessing AeThex via an IP address.
Please access through the proper domain:
</p>
<code className="block text-xs bg-black/50 p-2 rounded text-yellow-300 break-all">
https://aethex.dev/discord
@ -80,18 +89,26 @@ export default function DiscordActivity() {
{isSDKError && (
<div className="space-y-2 pt-2 border-t border-red-400/30">
<p className="text-xs text-red-200 font-semibold">🎮 Discord SDK Issue</p>
<p className="text-xs text-red-200 font-semibold">
🎮 Discord SDK Issue
</p>
<p className="text-xs text-red-200/80">
Make sure you're opening this as a Discord Activity within a Discord server, not as a standalone website.
Make sure you're opening this as a Discord Activity within a
Discord server, not as a standalone website.
</p>
</div>
)}
</div>
<div className="space-y-2">
<p className="text-xs text-muted-foreground">Troubleshooting steps:</p>
<p className="text-xs text-muted-foreground">
Troubleshooting steps:
</p>
<ul className="text-xs text-muted-foreground space-y-1 text-left list-disc list-inside">
<li>Access via domain: <span className="text-aethex-300">aethex.dev/discord</span></li>
<li>
Access via domain:{" "}
<span className="text-aethex-300">aethex.dev/discord</span>
</li>
<li>Open in Discord Activity, not as regular website</li>
<li>Ensure Discord server has AeThex Activity installed</li>
<li>Try refreshing the Discord window</li>
@ -123,8 +140,12 @@ export default function DiscordActivity() {
return (
<div className="min-h-screen flex items-center justify-center bg-background">
<div className="text-center space-y-4">
<h1 className="text-2xl font-bold text-foreground">Welcome to AeThex</h1>
<p className="text-muted-foreground">Discord user information unavailable</p>
<h1 className="text-2xl font-bold text-foreground">
Welcome to AeThex
</h1>
<p className="text-muted-foreground">
Discord user information unavailable
</p>
</div>
</div>
);
@ -134,40 +155,55 @@ export default function DiscordActivity() {
return (
<div className="min-h-screen bg-background">
<div className="p-4 text-sm text-muted-foreground border-b border-border/50">
<p>👋 Welcome <strong>{discordUser.username}</strong> to AeThex Discord Activity</p>
<p>
👋 Welcome <strong>{discordUser.username}</strong> to AeThex Discord
Activity
</p>
</div>
<div className="container mx-auto py-8">
<div className="text-center space-y-4">
<h1 className="text-3xl font-bold text-foreground">AeThex Community</h1>
<p className="text-muted-foreground">Full platform access from Discord</p>
<h1 className="text-3xl font-bold text-foreground">
AeThex Community
</h1>
<p className="text-muted-foreground">
Full platform access from Discord
</p>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 max-w-2xl mx-auto mt-8">
<a
href="/feed"
className="p-4 rounded-lg border border-border/50 hover:border-aethex-400/50 transition text-left"
>
<div className="font-semibold">Community Feed</div>
<p className="text-sm text-muted-foreground mt-1">View posts and engage with creators</p>
<p className="text-sm text-muted-foreground mt-1">
View posts and engage with creators
</p>
</a>
<a
href="/community"
className="p-4 rounded-lg border border-border/50 hover:border-aethex-400/50 transition text-left"
>
<div className="font-semibold">Community Hub</div>
<p className="text-sm text-muted-foreground mt-1">Connect with mentors and developers</p>
<p className="text-sm text-muted-foreground mt-1">
Connect with mentors and developers
</p>
</a>
<a
href="/dashboard"
className="p-4 rounded-lg border border-border/50 hover:border-aethex-400/50 transition text-left"
>
<div className="font-semibold">Dashboard</div>
<p className="text-sm text-muted-foreground mt-1">Manage your projects and profile</p>
<p className="text-sm text-muted-foreground mt-1">
Manage your projects and profile
</p>
</a>
<a
href="/roadmap"
className="p-4 rounded-lg border border-border/50 hover:border-aethex-400/50 transition text-left"
>
<div className="font-semibold">Roadmap</div>
<p className="text-sm text-muted-foreground mt-1">Vote on upcoming features</p>
<p className="text-sm text-muted-foreground mt-1">
Vote on upcoming features
</p>
</a>
</div>
</div>

View file

@ -35,15 +35,15 @@ export default function Foundation() {
},
{
name: "Developer CLI",
description: "Command-line tools for streamlined game development workflow",
description:
"Command-line tools for streamlined game development workflow",
stars: "1.2K",
language: "Go",
link: "github.com/aethex/dev-cli",
},
{
name: "Multiplayer Framework",
description:
"Drop-in networking layer for real-time multiplayer games",
description: "Drop-in networking layer for real-time multiplayer games",
stars: "980",
language: "TypeScript",
link: "github.com/aethex/multiplayer",
@ -147,7 +147,11 @@ export default function Foundation() {
Community Impact & Talent Pipeline
</h1>
<p className="text-xl text-red-100/70 max-w-3xl">
AeThex Foundation builds goodwill through open-source code (permissive licensing for maximum adoption), educational curriculum, and community workshops. We create a specialized talent pipeline feeding our ecosystem while advancing the broader developer community.
AeThex Foundation builds goodwill through open-source code
(permissive licensing for maximum adoption), educational
curriculum, and community workshops. We create a specialized
talent pipeline feeding our ecosystem while advancing the
broader developer community.
</p>
</div>
@ -170,7 +174,9 @@ export default function Foundation() {
{/* Creator Network CTAs */}
<div className="mt-8 pt-8 border-t border-red-400/20">
<p className="text-sm text-red-200/70 mb-4">Explore our creator community:</p>
<p className="text-sm text-red-200/70 mb-4">
Explore our creator community:
</p>
<div className="flex flex-col sm:flex-row gap-3">
<Button
size="sm"
@ -300,10 +306,7 @@ export default function Foundation() {
</h2>
<div className="grid md:grid-cols-2 gap-6">
{resources.map((resource, idx) => (
<Card
key={idx}
className="bg-red-950/20 border-red-400/30"
>
<Card key={idx} className="bg-red-950/20 border-red-400/30">
<CardContent className="pt-6">
<div className="space-y-3">
<div>
@ -405,7 +408,9 @@ export default function Foundation() {
Ways to Contribute
</h2>
<p className="text-lg text-red-100/80 mb-12 max-w-3xl">
Join our community and help us build the future of game development. Whether you're a developer, designer, educator, or enthusiast, there's a place for you.
Join our community and help us build the future of game
development. Whether you're a developer, designer, educator, or
enthusiast, there's a place for you.
</p>
<div className="grid md:grid-cols-3 gap-6">
{[
@ -425,10 +430,7 @@ export default function Foundation() {
"Help us improve by finding and reporting bugs",
},
].map((item, idx) => (
<Card
key={idx}
className="bg-red-950/20 border-red-400/30"
>
<Card key={idx} className="bg-red-950/20 border-red-400/30">
<CardContent className="pt-6 text-center">
<h3 className="font-bold text-red-300 mb-2">
{item.title}

View file

@ -2,7 +2,14 @@ import Layout from "@/components/Layout";
import { Button } from "@/components/ui/button";
import { Badge } from "@/components/ui/badge";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Gamepad2, Calendar, Users, TrendingUp, Rocket, ArrowRight } from "lucide-react";
import {
Gamepad2,
Calendar,
Users,
TrendingUp,
Rocket,
ArrowRight,
} from "lucide-react";
import { useNavigate } from "react-router-dom";
export default function GameForge() {
@ -97,7 +104,11 @@ export default function GameForge() {
Shipping Games Monthly
</h1>
<p className="text-xl text-green-100/70 max-w-3xl">
AeThex GameForge is our internal production studio that demonstrates disciplined, efficient development. We ship a new game every month using proprietary development pipelines and tools from Labs, proving our technology's real-world impact while maintaining controlled burn rates.
AeThex GameForge is our internal production studio that
demonstrates disciplined, efficient development. We ship a new
game every month using proprietary development pipelines and
tools from Labs, proving our technology's real-world impact
while maintaining controlled burn rates.
</p>
</div>
@ -257,7 +268,8 @@ export default function GameForge() {
{
phase: "Prototyping",
duration: "1 week",
description: "Build playable prototype to test core mechanics",
description:
"Build playable prototype to test core mechanics",
},
{
phase: "Development",
@ -312,7 +324,8 @@ export default function GameForge() {
Part of Our Shipping Culture
</h2>
<p className="text-lg text-green-100/80 mb-8">
Our team represents the best of game development talent. Meet the people who make monthly shipping possible.
Our team represents the best of game development talent. Meet
the people who make monthly shipping possible.
</p>
<Button
className="bg-green-400 text-black shadow-[0_0_30px_rgba(34,197,94,0.35)] hover:bg-green-300"

View file

@ -2,7 +2,14 @@ import Layout from "@/components/Layout";
import { Button } from "@/components/ui/button";
import { Badge } from "@/components/ui/badge";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Microscope, Zap, Users, ArrowRight, Lightbulb, Target } from "lucide-react";
import {
Microscope,
Zap,
Users,
ArrowRight,
Lightbulb,
Target,
} from "lucide-react";
import { useNavigate } from "react-router-dom";
export default function Labs() {
@ -29,8 +36,7 @@ export default function Labs() {
},
{
title: "Procedural Content Generation",
description:
"Algorithms for infinite, dynamic game world generation",
description: "Algorithms for infinite, dynamic game world generation",
status: "Published Research",
team: 4,
impact: "Game development tools",
@ -57,7 +63,8 @@ export default function Labs() {
{
title: "Open Source: AeThex Game Engine",
date: "November 2024",
description: "Lightweight, high-performance game engine for web and native",
description:
"Lightweight, high-performance game engine for web and native",
stars: "2.5K GitHub stars",
},
{
@ -99,10 +106,16 @@ export default function Labs() {
The Innovation Engine
</h1>
<p className="text-xl text-yellow-100/70 max-w-3xl">
AeThex Labs is our dedicated R&D pillar, focused on breakthrough technologies that create lasting competitive advantage. We invest in bleeding-edge researchfrom advanced AI to next-generation web architectureswhile cultivating thought leadership that shapes industry direction.
AeThex Labs is our dedicated R&D pillar, focused on
breakthrough technologies that create lasting competitive
advantage. We invest in bleeding-edge researchfrom advanced
AI to next-generation web architectureswhile cultivating
thought leadership that shapes industry direction.
</p>
<p className="text-xl text-yellow-100/80 max-w-3xl">
Applied R&D pushing the boundaries of what's possible in software, games, and digital experiences. Our research today shapes tomorrow's products.
Applied R&D pushing the boundaries of what's possible in
software, games, and digital experiences. Our research today
shapes tomorrow's products.
</p>
</div>
@ -125,7 +138,9 @@ export default function Labs() {
{/* Creator Network CTAs */}
<div className="mt-8 pt-8 border-t border-yellow-400/20">
<p className="text-sm text-yellow-200/70 mb-4">Explore our creator community:</p>
<p className="text-sm text-yellow-200/70 mb-4">
Explore our creator community:
</p>
<div className="flex flex-col sm:flex-row gap-3">
<Button
size="sm"
@ -161,7 +176,9 @@ export default function Labs() {
className="bg-yellow-950/20 border-yellow-400/30 hover:border-yellow-400/60 transition-all"
>
<CardHeader>
<div className={`w-12 h-12 rounded-lg bg-gradient-to-r ${project.color} flex items-center justify-center text-white mb-4`}>
<div
className={`w-12 h-12 rounded-lg bg-gradient-to-r ${project.color} flex items-center justify-center text-white mb-4`}
>
<Lightbulb className="h-6 w-6" />
</div>
<CardTitle className="text-yellow-300">
@ -267,7 +284,9 @@ export default function Labs() {
Meet the Lab
</h2>
<p className="text-lg text-yellow-100/80 max-w-3xl mb-12">
Our research team consists of PhD-level researchers, innovative engineers, and pioneering thinkers. We collaborate across disciplines to tackle the hardest problems in technology.
Our research team consists of PhD-level researchers, innovative
engineers, and pioneering thinkers. We collaborate across
disciplines to tackle the hardest problems in technology.
</p>
<Button
className="bg-yellow-400 text-black hover:bg-yellow-300"
@ -286,7 +305,8 @@ export default function Labs() {
Be Part of the Innovation
</h2>
<p className="text-lg text-yellow-100/80 mb-8">
We're hiring researchers and engineers to push the boundaries of what's possible.
We're hiring researchers and engineers to push the boundaries of
what's possible.
</p>
<Button
className="bg-yellow-400 text-black shadow-[0_0_30px_rgba(251,191,36,0.35)] hover:bg-yellow-300"

View file

@ -2,7 +2,15 @@ import Layout from "@/components/Layout";
import { Button } from "@/components/ui/button";
import { Badge } from "@/components/ui/badge";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Users, Briefcase, Zap, Target, Network, Sparkles, ArrowRight } from "lucide-react";
import {
Users,
Briefcase,
Zap,
Target,
Network,
Sparkles,
ArrowRight,
} from "lucide-react";
import { useNavigate } from "react-router-dom";
export default function Nexus() {
@ -12,32 +20,38 @@ export default function Nexus() {
{
icon: Users,
title: "Discover Talent",
description: "Browse creators across all AeThex arms with powerful filters and search."
description:
"Browse creators across all AeThex arms with powerful filters and search.",
},
{
icon: Briefcase,
title: "Post Opportunities",
description: "Create job postings and collaboration requests for your team or studio."
description:
"Create job postings and collaboration requests for your team or studio.",
},
{
icon: Network,
title: "Cross-Arm Integration",
description: "Find talent from Labs, GameForge, Corp, Foundation, and DevConnect."
description:
"Find talent from Labs, GameForge, Corp, Foundation, and DevConnect.",
},
{
icon: Sparkles,
title: "Hybrid Marketplace",
description: "Access both AeThex creators and DevConnect developers in one place."
description:
"Access both AeThex creators and DevConnect developers in one place.",
},
{
icon: Target,
title: "Smart Matching",
description: "Match opportunities with creators based on skills, experience, and interests."
description:
"Match opportunities with creators based on skills, experience, and interests.",
},
{
icon: Zap,
title: "Instant Apply",
description: "Apply for opportunities directly or track your applications in real-time."
description:
"Apply for opportunities directly or track your applications in real-time.",
},
];
@ -135,7 +149,8 @@ export default function Nexus() {
Everything You Need
</h2>
<p className="text-purple-200/70">
Connect creators with opportunities in a single, unified marketplace
Connect creators with opportunities in a single, unified
marketplace
</p>
</div>
@ -173,7 +188,8 @@ export default function Nexus() {
Multi-Arm Marketplace
</h2>
<p className="text-purple-200/70">
Access talent and opportunities from all AeThex arms in one place
Access talent and opportunities from all AeThex arms in one
place
</p>
</div>
@ -205,7 +221,8 @@ export default function Nexus() {
Ready to Connect?
</h2>
<p className="text-purple-200/80 mb-8">
Join the Nexus today and find your next opportunity or team member
Join the Nexus today and find your next opportunity or team
member
</p>
<div className="flex flex-col sm:flex-row gap-4 justify-center">

View file

@ -53,7 +53,7 @@ export default function CorpContactUs() {
const handleInputChange = (
e: React.ChangeEvent<
HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement
>
>,
) => {
const { name, value } = e.target;
setFormData((prev) => ({ ...prev, [name]: value }));
@ -91,7 +91,9 @@ export default function CorpContactUs() {
Get In Touch
</h1>
<p className="text-lg text-blue-100/80 max-w-3xl">
Have questions about our services? Want to schedule a consultation? Contact our enterprise team and we'll get back to you quickly.
Have questions about our services? Want to schedule a
consultation? Contact our enterprise team and we'll get back to
you quickly.
</p>
</div>
</section>
@ -272,9 +274,9 @@ export default function CorpContactUs() {
Expected Response Time
</h3>
<p className="text-blue-200/80">
We typically respond to inquiries within 24 business hours.
For urgent matters, please call our direct line during
business hours (9AM-6PM EST, Monday-Friday).
We typically respond to inquiries within 24 business
hours. For urgent matters, please call our direct line
during business hours (9AM-6PM EST, Monday-Friday).
</p>
</div>
</div>

View file

@ -15,7 +15,8 @@ export default function CorpScheduleConsultation() {
id: "custom-dev",
name: "Custom Software Development",
duration: "60 min",
description: "Discuss your project requirements and our development approach",
description:
"Discuss your project requirements and our development approach",
details: [
"Project scope assessment",
"Technology stack recommendations",
@ -116,7 +117,9 @@ export default function CorpScheduleConsultation() {
Schedule Your Consultation
</h1>
<p className="text-lg text-blue-100/80 max-w-3xl">
Get expert guidance from our enterprise solutions team. Choose a service, pick your time, and let's discuss how we can help transform your business.
Get expert guidance from our enterprise solutions team. Choose a
service, pick your time, and let's discuss how we can help
transform your business.
</p>
</div>
</section>
@ -247,7 +250,9 @@ export default function CorpScheduleConsultation() {
What to Expect
</h2>
<p className="text-lg text-blue-100/80 mb-8">
Our consultants will assess your needs, provide expert recommendations, and create a customized solution proposal tailored to your business goals.
Our consultants will assess your needs, provide expert
recommendations, and create a customized solution proposal
tailored to your business goals.
</p>
<Button
className="bg-blue-400 text-black shadow-[0_0_30px_rgba(59,130,246,0.35)] hover:bg-blue-300"

View file

@ -133,7 +133,8 @@ export default function CorpViewCaseStudies() {
Case Studies
</h1>
<p className="text-lg text-blue-100/80 max-w-3xl">
Real-world success stories from our enterprise clients. See how we've helped transform businesses across industries.
Real-world success stories from our enterprise clients. See how
we've helped transform businesses across industries.
</p>
</div>
</section>
@ -272,7 +273,8 @@ export default function CorpViewCaseStudies() {
Ready for Your Success Story?
</h2>
<p className="text-lg text-blue-100/80 mb-8">
Let's discuss how we can help transform your business and achieve similar results.
Let's discuss how we can help transform your business and
achieve similar results.
</p>
<Button
className="bg-blue-400 text-black shadow-[0_0_30px_rgba(59,130,246,0.35)] hover:bg-blue-300"

View file

@ -15,7 +15,7 @@ export default function CreatorDirectory() {
const [isLoading, setIsLoading] = useState(true);
const [search, setSearch] = useState(searchParams.get("search") || "");
const [selectedArm, setSelectedArm] = useState<string | undefined>(
searchParams.get("arm") || undefined
searchParams.get("arm") || undefined,
);
const [page, setPage] = useState(parseInt(searchParams.get("page") || "1"));
const [totalPages, setTotalPages] = useState(0);
@ -82,7 +82,8 @@ export default function CreatorDirectory() {
</h1>
</div>
<p className="text-lg text-gray-300 max-w-2xl mx-auto">
Discover talented creators across all AeThex arms. Filter by specialty, skills, and arm affiliation.
Discover talented creators across all AeThex arms. Filter by
specialty, skills, and arm affiliation.
</p>
</div>
@ -166,8 +167,10 @@ export default function CreatorDirectory() {
Previous
</Button>
<div className="flex items-center gap-1">
{Array.from({ length: totalPages }, (_, i) => i + 1).map(
(p) => (
{Array.from(
{ length: totalPages },
(_, i) => i + 1,
).map((p) => (
<Button
key={p}
onClick={() => setPage(p)}
@ -176,11 +179,12 @@ export default function CreatorDirectory() {
>
{p}
</Button>
)
)}
))}
</div>
<Button
onClick={() => setPage(Math.min(totalPages, page + 1))}
onClick={() =>
setPage(Math.min(totalPages, page + 1))
}
disabled={page === totalPages}
variant="outline"
>

View file

@ -50,7 +50,9 @@ export default function CreatorProfile() {
<div className="min-h-screen bg-black text-white flex items-center justify-center">
<div className="text-center">
<h1 className="text-3xl font-bold mb-4">Creator Not Found</h1>
<p className="text-gray-400 mb-6">The creator you're looking for doesn't exist.</p>
<p className="text-gray-400 mb-6">
The creator you're looking for doesn't exist.
</p>
<Button onClick={() => navigate("/creators")}>
<ArrowLeft className="h-4 w-4 mr-2" />
Back to Creators
@ -87,7 +89,10 @@ export default function CreatorProfile() {
<CardContent className="p-8">
<div className="flex flex-col md:flex-row items-start md:items-center gap-6 mb-6">
<Avatar className="h-24 w-24">
<AvatarImage src={creator.avatar_url} alt={creator.username} />
<AvatarImage
src={creator.avatar_url}
alt={creator.username}
/>
<AvatarFallback>
{creator.username.charAt(0).toUpperCase()}
</AvatarFallback>
@ -95,7 +100,9 @@ export default function CreatorProfile() {
<div className="flex-1">
<div className="flex items-center gap-3 mb-2">
<h1 className="text-3xl font-bold">@{creator.username}</h1>
<h1 className="text-3xl font-bold">
@{creator.username}
</h1>
{creator.devconnect_linked && (
<Badge className="bg-cyan-500/10 text-cyan-300 border-cyan-500/20">
<ExternalLink className="h-3 w-3 mr-1" />
@ -112,16 +119,16 @@ export default function CreatorProfile() {
{creator.arm_affiliations &&
creator.arm_affiliations
.filter((arm) => arm !== creator.primary_arm)
.map((arm) => (
<ArmBadge key={arm} arm={arm} />
))}
.map((arm) => <ArmBadge key={arm} arm={arm} />)}
</div>
<div className="flex gap-3">
{creator.devconnect_link && (
<Button asChild>
<a
href={creator.devconnect_link.devconnect_profile_url}
href={
creator.devconnect_link.devconnect_profile_url
}
target="_blank"
rel="noopener noreferrer"
>

View file

@ -62,7 +62,9 @@ export default function DevLinkAbout() {
About Dev-Link
</h1>
<p className="text-lg text-cyan-100/80 max-w-3xl">
The professional networking platform built for Roblox developers. Connect, collaborate, and grow your career in game development.
The professional networking platform built for Roblox
developers. Connect, collaborate, and grow your career in game
development.
</p>
</div>
</section>
@ -75,7 +77,13 @@ export default function DevLinkAbout() {
<Card className="bg-cyan-950/20 border-cyan-400/30">
<CardContent className="pt-6">
<p className="text-lg text-cyan-200/80 leading-relaxed">
Dev-Link is on a mission to empower Roblox developers worldwide. We believe that the Roblox platform has created an incredible community of creators who deserve a professional space to connect, showcase their work, and find amazing opportunities. Just like LinkedIn transformed professional networking, Dev-Link is transforming how Roblox developers collaborate and build their careers.
Dev-Link is on a mission to empower Roblox developers
worldwide. We believe that the Roblox platform has created
an incredible community of creators who deserve a
professional space to connect, showcase their work, and find
amazing opportunities. Just like LinkedIn transformed
professional networking, Dev-Link is transforming how Roblox
developers collaborate and build their careers.
</p>
</CardContent>
</Card>
@ -91,7 +99,10 @@ export default function DevLinkAbout() {
{values.map((value, idx) => {
const Icon = value.icon;
return (
<Card key={idx} className="bg-cyan-950/20 border-cyan-400/30">
<Card
key={idx}
className="bg-cyan-950/20 border-cyan-400/30"
>
<CardContent className="pt-6">
<Icon className="h-8 w-8 text-cyan-400 mb-3" />
<h3 className="text-lg font-bold text-cyan-300 mb-2">

View file

@ -89,7 +89,8 @@ export default function DevLinkJobs() {
Job Board
</h1>
<p className="text-lg text-cyan-100/80 max-w-3xl">
Find your next opportunity in the Roblox ecosystem. Full-time, part-time, and contract roles available.
Find your next opportunity in the Roblox ecosystem. Full-time,
part-time, and contract roles available.
</p>
</div>
</section>
@ -118,11 +119,13 @@ export default function DevLinkJobs() {
<div className="space-y-3">
<div className="flex items-center gap-3">
<Badge className={`${
<Badge
className={`${
job.type === "Full-time"
? "bg-green-500/20 text-green-300 border border-green-400/40"
: "bg-cyan-500/20 text-cyan-300 border border-cyan-400/40"
}`}>
}`}
>
{job.type}
</Badge>
<span className="flex items-center gap-1 text-sm text-cyan-200/70">
@ -170,9 +173,7 @@ export default function DevLinkJobs() {
<p className="text-lg text-cyan-100/80 mb-8">
Reach 50K+ talented Roblox developers.
</p>
<Button
className="bg-cyan-400 text-black hover:bg-cyan-300"
>
<Button className="bg-cyan-400 text-black hover:bg-cyan-300">
Post a Job Opening
<ArrowRight className="ml-2 h-4 w-4" />
</Button>

View file

@ -84,7 +84,8 @@ export default function DevLinkProfiles() {
Developer Directory
</h1>
<p className="text-lg text-cyan-100/80 max-w-3xl">
Find and connect with talented Roblox developers, browse portfolios, and discover collaboration opportunities.
Find and connect with talented Roblox developers, browse
portfolios, and discover collaboration opportunities.
</p>
</div>
</section>
@ -115,7 +116,10 @@ export default function DevLinkProfiles() {
<CardContent className="pt-6">
<div className="flex items-start justify-between mb-4">
<div className="w-12 h-12 rounded-full bg-gradient-to-r from-cyan-500 to-blue-500 flex items-center justify-center text-white font-bold">
{dev.name.split(" ").map(n => n[0]).join("")}
{dev.name
.split(" ")
.map((n) => n[0])
.join("")}
</div>
{dev.available && (
<Badge className="bg-green-500/20 text-green-300 border border-green-400/40 text-xs">
@ -144,7 +148,9 @@ export default function DevLinkProfiles() {
<div className="pt-4 border-t border-cyan-400/10 space-y-3">
<div className="flex justify-between text-sm">
<span className="text-cyan-200/70">{dev.portfolio}</span>
<span className="text-cyan-200/70">
{dev.portfolio}
</span>
<span className="flex items-center gap-1 text-yellow-400">
<Star className="h-4 w-4 fill-yellow-400" />
{dev.rating}
@ -172,9 +178,7 @@ export default function DevLinkProfiles() {
<p className="text-lg text-cyan-100/80 mb-8">
Showcase your work and connect with other developers.
</p>
<Button
className="bg-cyan-400 text-black hover:bg-cyan-300"
>
<Button className="bg-cyan-400 text-black hover:bg-cyan-300">
Get Started
<ArrowRight className="ml-2 h-4 w-4" />
</Button>

View file

@ -84,7 +84,8 @@ export default function FoundationContribute() {
},
{
name: "Developer CLI",
description: "Command-line tools for streamlined game development workflow",
description:
"Command-line tools for streamlined game development workflow",
stars: "1.2K",
language: "Go",
issues: "15 open",
@ -92,8 +93,7 @@ export default function FoundationContribute() {
},
{
name: "Multiplayer Framework",
description:
"Drop-in networking layer for real-time multiplayer games",
description: "Drop-in networking layer for real-time multiplayer games",
stars: "980",
language: "TypeScript",
issues: "10 open",
@ -155,7 +155,9 @@ export default function FoundationContribute() {
Ways to Contribute
</h1>
<p className="text-lg text-red-100/80 max-w-3xl">
Join our community and help us build amazing open-source projects. There are many ways to contribute, regardless of your skill level.
Join our community and help us build amazing open-source
projects. There are many ways to contribute, regardless of your
skill level.
</p>
</div>
</section>

View file

@ -173,7 +173,8 @@ export default function FoundationGetInvolved() {
Get Involved
</h1>
<p className="text-lg text-red-100/80 max-w-3xl">
Join our thriving community of developers. Whether you're just starting or a seasoned pro, there's a place for you here.
Join our thriving community of developers. Whether you're just
starting or a seasoned pro, there's a place for you here.
</p>
</div>
</section>

View file

@ -25,12 +25,7 @@ export default function FoundationLearnMore() {
"Complete introduction to game development concepts and best practices",
lessons: "50",
duration: "20 hours",
topics: [
"Game loops",
"Physics",
"Input handling",
"Asset management",
],
topics: ["Game loops", "Physics", "Input handling", "Asset management"],
free: true,
},
{
@ -59,12 +54,7 @@ export default function FoundationLearnMore() {
"Learn scalable architectural patterns used in professional game development",
modules: "8",
projects: "4",
topics: [
"MVC pattern",
"ECS systems",
"Networking",
"State management",
],
topics: ["MVC pattern", "ECS systems", "Networking", "State management"],
free: true,
},
{
@ -174,7 +164,9 @@ export default function FoundationLearnMore() {
Free Learning Resources
</h1>
<p className="text-lg text-red-100/80 max-w-3xl">
Learn game development from the ground up with our free, comprehensive educational resources. Everything you need to become an expert developer.
Learn game development from the ground up with our free,
comprehensive educational resources. Everything you need to
become an expert developer.
</p>
</div>
</section>
@ -426,8 +418,8 @@ export default function FoundationLearnMore() {
Start Your Learning Journey
</h2>
<p className="text-lg text-red-100/80 mb-8">
Choose a learning path and begin mastering game development today.
Everything is completely free and open to the community.
Choose a learning path and begin mastering game development
today. Everything is completely free and open to the community.
</p>
<Button
className="bg-red-400 text-black shadow-[0_0_30px_rgba(239,68,68,0.35)] hover:bg-red-300"

View file

@ -65,7 +65,8 @@ export default function GameForgeJoinGameForge() {
The Team
</h1>
<p className="text-lg text-green-100/80 max-w-3xl">
The talented developers, designers, and producers who ship a game every month.
The talented developers, designers, and producers who ship a
game every month.
</p>
</div>
</section>
@ -80,7 +81,10 @@ export default function GameForgeJoinGameForge() {
>
<CardContent className="pt-6">
<div className="w-12 h-12 rounded-full bg-gradient-to-r from-green-500 to-emerald-500 flex items-center justify-center text-white font-bold mb-3">
{member.name.split(" ").map(n => n[0]).join("")}
{member.name
.split(" ")
.map((n) => n[0])
.join("")}
</div>
<h3 className="text-lg font-bold text-green-300 mb-1">
{member.name}
@ -115,7 +119,8 @@ export default function GameForgeJoinGameForge() {
{
icon: <Zap className="h-6 w-6" />,
title: "Ship Every Month",
description: "We deliver a complete, polished game every month",
description:
"We deliver a complete, polished game every month",
},
{
icon: <Heart className="h-6 w-6" />,
@ -125,7 +130,8 @@ export default function GameForgeJoinGameForge() {
{
icon: <Users className="h-6 w-6" />,
title: "Together",
description: "Cross-functional teams working toward one goal",
description:
"Cross-functional teams working toward one goal",
},
].map((item, idx) => (
<Card

View file

@ -122,7 +122,9 @@ export default function GameForgeStartBuilding() {
Production Pipeline
</h1>
<p className="text-lg text-green-100/80 max-w-3xl">
How we ship a game every month. Our proven process, team coordination, and development tools that make monthly shipping possible.
How we ship a game every month. Our proven process, team
coordination, and development tools that make monthly shipping
possible.
</p>
</div>
</section>
@ -234,11 +236,13 @@ export default function GameForgeStartBuilding() {
</p>
</div>
</div>
<Badge className={`${
<Badge
className={`${
item.status === "On Track"
? "bg-green-500/20 text-green-300 border border-green-400/40"
: "bg-purple-500/20 text-purple-300 border border-purple-400/40"
}`}>
}`}
>
{item.status}
</Badge>
</div>
@ -257,7 +261,10 @@ export default function GameForgeStartBuilding() {
</h2>
<div className="space-y-4">
{productionPhases.map((item, idx) => (
<Card key={idx} className="bg-green-950/20 border-green-400/30">
<Card
key={idx}
className="bg-green-950/20 border-green-400/30"
>
<CardContent className="pt-6">
<div className="flex items-start gap-6">
<div className="h-12 w-12 rounded-lg bg-gradient-to-r from-green-500 to-emerald-500 flex items-center justify-center text-white font-bold flex-shrink-0">

View file

@ -75,7 +75,8 @@ export default function GameForgeViewPortfolio() {
Released Games
</h1>
<p className="text-lg text-green-100/80 max-w-3xl">
Games shipped by GameForge. See player stats, revenue, and team sizes from our monthly releases.
Games shipped by GameForge. See player stats, revenue, and team
sizes from our monthly releases.
</p>
</div>
</section>
@ -109,9 +110,7 @@ export default function GameForgeViewPortfolio() {
<p className="text-lg font-bold text-green-300">
{game.players}
</p>
<p className="text-xs text-green-200/60">
active
</p>
<p className="text-xs text-green-200/60">active</p>
</div>
<div className="space-y-1">
@ -119,7 +118,8 @@ export default function GameForgeViewPortfolio() {
RATING
</p>
<p className="text-lg font-bold text-green-300 flex items-center gap-1">
{game.rating} <Star className="h-4 w-4 fill-yellow-400 text-yellow-400" />
{game.rating}{" "}
<Star className="h-4 w-4 fill-yellow-400 text-yellow-400" />
</p>
<p className="text-xs text-green-200/60">
{game.downloads} downloads

View file

@ -2,7 +2,14 @@ import Layout from "@/components/Layout";
import { Button } from "@/components/ui/button";
import { Badge } from "@/components/ui/badge";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { ArrowRight, Users, Clock, Zap, Download, ExternalLink } from "lucide-react";
import {
ArrowRight,
Users,
Clock,
Zap,
Download,
ExternalLink,
} from "lucide-react";
import { useNavigate } from "react-router-dom";
export default function LabsExploreResearch() {
@ -105,7 +112,9 @@ export default function LabsExploreResearch() {
Research Projects
</h1>
<p className="text-lg text-yellow-100/80 max-w-3xl">
Explore the cutting-edge research being conducted in AeThex Labs. Each project represents our commitment to pushing the boundaries of technology.
Explore the cutting-edge research being conducted in AeThex
Labs. Each project represents our commitment to pushing the
boundaries of technology.
</p>
</div>
</section>
@ -125,13 +134,15 @@ export default function LabsExploreResearch() {
<CardTitle className="text-2xl text-yellow-300 mb-2">
{project.title}
</CardTitle>
<Badge className={`${
<Badge
className={`${
project.status === "Published"
? "bg-green-500/20 border border-green-400/40 text-green-300"
: project.status === "Active Research"
? "bg-yellow-500/20 border border-yellow-400/40 text-yellow-300"
: "bg-purple-500/20 border border-purple-400/40 text-purple-300"
}`}>
}`}
>
{project.status}
</Badge>
</div>
@ -148,7 +159,9 @@ export default function LabsExploreResearch() {
<CardContent className="space-y-6">
{/* Description */}
<p className="text-yellow-200/80">{project.description}</p>
<p className="text-yellow-200/80">
{project.description}
</p>
{/* Key Achievements */}
<div>
@ -188,7 +201,9 @@ export default function LabsExploreResearch() {
{/* Impact & Links */}
<div className="pt-4 border-t border-yellow-400/10 flex flex-wrap items-center justify-between gap-4">
<p className="text-sm text-yellow-200/80">
<span className="font-semibold text-yellow-400">Impact:</span>{" "}
<span className="font-semibold text-yellow-400">
Impact:
</span>{" "}
{project.impact}
</p>
<div className="flex gap-3">
@ -228,7 +243,8 @@ export default function LabsExploreResearch() {
Interested in Research?
</h2>
<p className="text-lg text-yellow-100/80 mb-8">
Join our research team and contribute to the future of technology.
Join our research team and contribute to the future of
technology.
</p>
<Button
className="bg-yellow-400 text-black hover:bg-yellow-300"

View file

@ -2,7 +2,14 @@ import Layout from "@/components/Layout";
import { Button } from "@/components/ui/button";
import { Badge } from "@/components/ui/badge";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { CheckCircle, Users, BookOpen, Code, Zap, ArrowRight } from "lucide-react";
import {
CheckCircle,
Users,
BookOpen,
Code,
Zap,
ArrowRight,
} from "lucide-react";
import { useNavigate } from "react-router-dom";
export default function LabsGetInvolved() {
@ -109,7 +116,9 @@ export default function LabsGetInvolved() {
Get Involved
</h1>
<p className="text-lg text-yellow-100/80 max-w-3xl">
There are many ways to collaborate with AeThex Labs. Whether you're a researcher, developer, or thought leader, we'd love to work together.
There are many ways to collaborate with AeThex Labs. Whether
you're a researcher, developer, or thought leader, we'd love
to work together.
</p>
</div>
</div>
@ -125,7 +134,9 @@ export default function LabsGetInvolved() {
className="bg-yellow-950/20 border-yellow-400/30 hover:border-yellow-400/60 transition-all"
>
<CardHeader>
<div className={`w-12 h-12 rounded-lg bg-gradient-to-r ${opp.color} flex items-center justify-center text-white mb-4`}>
<div
className={`w-12 h-12 rounded-lg bg-gradient-to-r ${opp.color} flex items-center justify-center text-white mb-4`}
>
{opp.icon}
</div>
<CardTitle className="text-yellow-300">

View file

@ -104,7 +104,9 @@ export default function LabsJoinTeam() {
Meet the Lab
</h1>
<p className="text-lg text-yellow-100/80 max-w-3xl">
World-class researchers and engineers dedicated to advancing technology. Our team includes PhD researchers, published authors, and visionary thinkers.
World-class researchers and engineers dedicated to advancing
technology. Our team includes PhD researchers, published
authors, and visionary thinkers.
</p>
</div>
</div>
@ -235,7 +237,8 @@ export default function LabsJoinTeam() {
Join the Lab
</h2>
<p className="text-lg text-yellow-100/80 mb-8">
We're looking for brilliant minds to join our team. Researchers, engineers, and visionaries welcome.
We're looking for brilliant minds to join our team. Researchers,
engineers, and visionaries welcome.
</p>
<Button
className="bg-yellow-400 text-black hover:bg-yellow-300"

View file

@ -15,7 +15,7 @@ export default function OpportunitiesHub() {
const [isLoading, setIsLoading] = useState(true);
const [search, setSearch] = useState(searchParams.get("search") || "");
const [selectedArm, setSelectedArm] = useState<string | undefined>(
searchParams.get("arm") || undefined
searchParams.get("arm") || undefined,
);
const [page, setPage] = useState(parseInt(searchParams.get("page") || "1"));
const [totalPages, setTotalPages] = useState(0);
@ -83,7 +83,8 @@ export default function OpportunitiesHub() {
</h1>
</div>
<p className="text-lg text-gray-300 max-w-2xl mx-auto">
Find jobs, collaborations, and research opportunities across all AeThex arms.
Find jobs, collaborations, and research opportunities across
all AeThex arms.
</p>
</div>
@ -167,8 +168,10 @@ export default function OpportunitiesHub() {
Previous
</Button>
<div className="flex items-center gap-1">
{Array.from({ length: totalPages }, (_, i) => i + 1).map(
(p) => (
{Array.from(
{ length: totalPages },
(_, i) => i + 1,
).map((p) => (
<Button
key={p}
onClick={() => setPage(p)}
@ -177,11 +180,12 @@ export default function OpportunitiesHub() {
>
{p}
</Button>
)
)}
))}
</div>
<Button
onClick={() => setPage(Math.min(totalPages, page + 1))}
onClick={() =>
setPage(Math.min(totalPages, page + 1))
}
disabled={page === totalPages}
variant="outline"
>

View file

@ -79,7 +79,7 @@ export default function OpportunityDetail() {
} catch (error) {
toast(
error instanceof Error ? error.message : "Failed to submit application",
"error"
"error",
);
} finally {
setIsApplying(false);
@ -88,7 +88,8 @@ export default function OpportunityDetail() {
const formatSalary = (min?: number, max?: number) => {
if (!min && !max) return "Not specified";
if (min && max) return `$${min.toLocaleString()} - $${max.toLocaleString()}`;
if (min && max)
return `$${min.toLocaleString()} - $${max.toLocaleString()}`;
if (min) return `$${min.toLocaleString()}+`;
if (max) return `Up to $${max.toLocaleString()}`;
};
@ -152,9 +153,7 @@ export default function OpportunityDetail() {
<ArmBadge arm={opportunity.arm_affiliation} />
</div>
<h1 className="text-4xl font-bold mb-4">
{opportunity.title}
</h1>
<h1 className="text-4xl font-bold mb-4">{opportunity.title}</h1>
<div className="grid grid-cols-2 md:grid-cols-4 gap-4 mb-8 py-6 border-t border-b border-slate-700">
<div>
@ -171,7 +170,7 @@ export default function OpportunityDetail() {
<p className="font-semibold text-sm">
{formatSalary(
opportunity.salary_min,
opportunity.salary_max
opportunity.salary_max,
)}
</p>
</div>
@ -196,7 +195,10 @@ export default function OpportunityDetail() {
{/* Posted By */}
<div className="flex items-center gap-4 mb-8 p-4 bg-slate-700/30 rounded-lg">
<Avatar className="h-12 w-12">
<AvatarImage src={poster.avatar_url} alt={poster.username} />
<AvatarImage
src={poster.avatar_url}
alt={poster.username}
/>
<AvatarFallback>
{poster.username.charAt(0).toUpperCase()}
</AvatarFallback>
@ -206,9 +208,7 @@ export default function OpportunityDetail() {
<p className="font-semibold">@{poster.username}</p>
</div>
<Button
onClick={() =>
navigate(`/creators/${poster.username}`)
}
onClick={() => navigate(`/creators/${poster.username}`)}
variant="outline"
>
View Profile
@ -253,8 +253,8 @@ export default function OpportunityDetail() {
<AlertDialogHeader>
<AlertDialogTitle>Apply for {opportunity.title}</AlertDialogTitle>
<AlertDialogDescription>
Submit your application with a cover letter explaining why you're
interested in this opportunity.
Submit your application with a cover letter explaining why
you're interested in this opportunity.
</AlertDialogDescription>
</AlertDialogHeader>
@ -280,9 +280,7 @@ export default function OpportunityDetail() {
disabled={isApplying || !coverLetter.trim()}
className="gap-2"
>
{isApplying && (
<Loader2 className="h-4 w-4 animate-spin" />
)}
{isApplying && <Loader2 className="h-4 w-4 animate-spin" />}
Submit Application
</AlertDialogAction>
</div>

View file

@ -51,15 +51,13 @@ export default function MyApplications() {
try {
await withdrawApplication(applicationId);
toast("Application withdrawn", "success");
setApplications(
applications.filter((app) => app.id !== applicationId)
);
setApplications(applications.filter((app) => app.id !== applicationId));
} catch (error) {
toast(
error instanceof Error
? error.message
: "Failed to withdraw application",
"error"
"error",
);
}
};
@ -215,7 +213,7 @@ export default function MyApplications() {
/>
))}
</TabsContent>
)
),
)}
</Tabs>
</>
@ -265,10 +263,7 @@ function ApplicationCard({
<div className="flex items-center gap-3 mb-3">
<Avatar className="h-10 w-10">
<AvatarImage
src={poster.avatar_url}
alt={poster.username}
/>
<AvatarImage src={poster.avatar_url} alt={poster.username} />
<AvatarFallback>
{poster.username.charAt(0).toUpperCase()}
</AvatarFallback>

View file

@ -7,6 +7,7 @@ AeThex can be embedded as a Discord Activity, allowing users to access the platf
## What is a Discord Activity?
A Discord Activity is an embedded application that runs within Discord. It allows users to:
- Access AeThex features directly in Discord
- Share their experience with server members
- Collaborate in real-time without leaving Discord
@ -51,6 +52,7 @@ This file tells Discord how to handle the AeThex Activity:
```
**Key Configuration Points:**
- `instance_url`: Where Discord Activity iframe loads (MUST be domain, not IP)
- `redirect_uris`: OAuth callback endpoint (MUST match Discord app settings)
- `scopes`: What Discord permissions the Activity requests
@ -58,15 +60,18 @@ This file tells Discord how to handle the AeThex Activity:
### 2. Code Configuration
**Frontend Pages** (`code/client/pages/`):
- `DiscordActivity.tsx` - Main Activity page mounted at `/discord`
- Discord OAuth callback handler at `/discord/callback`
**Context** (`code/client/contexts/DiscordContext.tsx`):
- Manages Discord user session
- Handles OAuth flow
- Exposes Discord user data to components
**Routes** (`code/client/App.tsx`):
```typescript
<Route path="/discord" element={<DiscordActivity />} />
<Route path="/discord/callback" element={<DiscordOAuthCallback />} />
@ -85,6 +90,7 @@ This must be present for Discord Activity to initialize.
## Local Testing
### Prerequisites
- Node.js 18+
- npm or yarn
- Running AeThex dev server
@ -92,6 +98,7 @@ This must be present for Discord Activity to initialize.
### Steps
1. **Start the dev server:**
```bash
npm run dev
# or
@ -99,6 +106,7 @@ This must be present for Discord Activity to initialize.
```
2. **Access locally via tunnel** (if testing Discord Activity):
- Use a tool like `ngrok` to expose localhost to Discord
- Or use Discord's local testing tools
@ -111,6 +119,7 @@ This must be present for Discord Activity to initialize.
⚠️ **Note**: Discord Activities require HTTPS and a proper domain. Local testing with IP addresses will fail with **Cloudflare Error 1003**.
Local testing workarounds:
- Use `ngrok` with a tunnel URL
- Use Discord's local testing documentation
- Test OAuth flow after deploying to staging
@ -145,6 +154,7 @@ Local testing workarounds:
### Environment Variables
No special environment variables needed for Discord Activity. The configuration is done via:
- `code/public/discord-manifest.json`
- Discord Application settings
- `code/client/contexts/DiscordContext.tsx`
@ -169,16 +179,19 @@ No special environment variables needed for Discord Activity. The configuration
### Implementation Details
**DiscordActivity.tsx** handles:
- Discord SDK initialization
- OAuth trigger and callback handling
- Activity UI rendering
**DiscordContext.tsx** manages:
- Discord user state
- Token storage
- Session lifecycle
**API calls** use Discord access token for:
- User identification
- Guild information
- Activity-specific operations
@ -190,6 +203,7 @@ No special environment variables needed for Discord Activity. The configuration
**Cause**: Accessing Activity via IP address instead of domain
**Solution**:
```
❌ http://192.168.1.100:5173/discord
✅ https://aethex.dev/discord
@ -197,6 +211,7 @@ No special environment variables needed for Discord Activity. The configuration
**Error Message in UI**:
Users will see a helpful error message explaining:
- The issue (Cloudflare blocking IP access)
- How to fix it (use domain instead)
- Troubleshooting steps
@ -206,6 +221,7 @@ Users will see a helpful error message explaining:
**Cause**: Discord SDK not loaded or manifest invalid
**Solution**:
- Verify Discord SDK script in `code/index.html`
- Check manifest is accessible at `/public/discord-manifest.json`
- Verify Discord Application ID: `578971245454950421`
@ -244,6 +260,7 @@ Users will see a helpful error message explaining:
**Error**: Blank white screen in Discord Activity
**Debug Steps**:
1. Open browser DevTools in Discord
2. Check Console for errors
3. Check Network tab for failed requests
@ -251,6 +268,7 @@ Users will see a helpful error message explaining:
5. Verify Cloudflare isn't blocking traffic
**Common Causes**:
- IP address used instead of domain (Cloudflare Error 1003)
- Discord SDK script failed to load
- Manifest file not accessible
@ -261,12 +279,14 @@ Users will see a helpful error message explaining:
**Error**: OAuth flow doesn't complete or redirect fails
**Debug Steps**:
1. Check Discord Application settings - redirect URIs match exactly
2. Verify OAuth callback route exists: `/discord/callback`
3. Check browser console for authorization error codes
4. Verify Discord Application permissions (identify, email, guilds)
**Common Causes**:
- Redirect URI mismatch between manifest and Discord app
- Discord Application doesn't have "Identify" scope enabled
- Activity not installed in Discord server
@ -276,6 +296,7 @@ Users will see a helpful error message explaining:
**Error**: Guild list is empty or shows no servers
**Debug Steps**:
1. Verify "guilds" scope is in OAuth config
2. Check user actually has permission in those guilds
3. Verify Discord OAuth token has guilds scope
@ -316,6 +337,7 @@ Using your deployment provider (Netlify, Vercel, custom):
### Step 5: Add to Discord
In Discord Developer Portal:
1. Go to Application Settings
2. Add Activity URL: `https://aethex.dev/discord`
3. Set OAuth2 Redirect URIs to: `https://aethex.dev/discord/callback`

View file

@ -7,12 +7,14 @@ The AeThex organizational structure implements a centralized IP holding company
## 1. Labs as IP Holding Company (IPCo)
All core intellectual property developed by Labs is owned by Labs, including:
- Patent portfolios (AI, algorithms, architectures)
- Software copyrights and source code
- Trade secrets and proprietary methodologies
- Trademarks and brand assets
**Benefits:**
- **Clean IP Title**: Consolidated, encumbrance-free IP ownership improves enterprise valuation
- **Protection**: IP separated from operational liabilities (Corp consulting disputes, Dev-Link platform issues)
- **Management**: Centralized IP portfolio administration across subsidiaries
@ -23,6 +25,7 @@ All core intellectual property developed by Labs is owned by Labs, including:
### Commercial Technology License
Corp receives a Commercial License to "make and use" proprietary Lab technologies in commercial service delivery, specifically:
- Advanced AI models for game development optimization
- Custom algorithms for multiplayer architecture
- Tools and frameworks created through R&D
@ -43,6 +46,7 @@ Licensing fees from Corp to Labs must comply with the **Arm's Length Principle**
For unique intangibles (advanced AI), cost-plus pricing is insufficient. Instead:
1. **Economic Value Uplift**: Measure how Lab IP enables Corp to charge premium rates
- Example: If Lab AI enables Corp to charge 30% premium for specialized dev services, royalty should reflect that uplift
- Comparable: Industry benchmarks for AI licensing typically 15-25% of incremental revenue
@ -67,6 +71,7 @@ Labs AI licensing to Corp:
## 3. Labs → Dev-Link: Licensing for Platform Features
Dev-Link platform may license Lab IP for:
- AI-assisted candidate assessment and matching
- Specialized skill evaluation algorithms
- Predictive analytics for placement success
@ -92,12 +97,14 @@ Charge = Actual Costs + Markup (typically 5-15%)
```
**Eligible Services:**
- HR administration, payroll processing
- IT infrastructure and support
- General accounting and bookkeeping
- Legal documentation review
**Requirements:**
- Clearly define which activities qualify
- Meticulously track direct costs
- Document markup selection (compare to similar service providers)
@ -106,6 +113,7 @@ Charge = Actual Costs + Markup (typically 5-15%)
### Documentation
Maintain **Intercompany Service Agreement** including:
- Services provided
- Cost allocation methodology
- Markup rationale
@ -118,11 +126,13 @@ The Foundation may receive services or assets from Corp/Dev-Link. **All must be
### Examples of Related-Party Transactions
1. **Corp provides office space to Foundation**
- Must charge Fair Market Rent (comparable leases in area)
- Document: Annual appraisal or comparable rent analysis
- Forbidden: Below-market lease = private inurement
2. **Corp donates curriculum materials to Foundation**
- Document as charitable contribution
- Fair value: Cost of development + reasonable markup
- Record in Foundation's fund accounting system
@ -135,6 +145,7 @@ The Foundation may receive services or assets from Corp/Dev-Link. **All must be
### Conflict of Interest Policy
Foundation must adopt and enforce a Conflict of Interest Policy including:
- Board members declare conflicts with related for-profit entities
- Disclosure of family/business relationships with Corp/Dev-Link executives
- Voting restrictions: Conflicted directors abstain on related-party votes
@ -147,11 +158,13 @@ Foundation must adopt and enforce a Conflict of Interest Policy including:
### Methodology
**For Technology Licensing (Labs IP):**
- Comparable License Analysis (market rates for similar IP)
- Relief-from-Royalty (value of IP to users)
- Residual Profit Split (allocate profit between Lab innovation and operational execution)
**For Services:**
- Comparable Uncontrolled Price (market rates for same services)
- Cost-Plus Analysis (cost + reasonable markup)
- Resale Price Method (if service is resold externally)
@ -204,6 +217,7 @@ All transfers from Corp must be documented as grants/contributions with restrict
### Related-Party Transaction Board Approval
Before any Corp transfer to Foundation:
1. Independent majority votes
2. Minority includes conflicted party vote counts documented
3. FMV analysis presented
@ -213,6 +227,7 @@ Before any Corp transfer to Foundation:
### Annual Form 990 Reporting
Foundation must file IRS Form 990 disclosing:
- Compensation of officers/directors
- Related-party transactions
- All grants and contributions received
@ -223,6 +238,7 @@ Foundation must file IRS Form 990 disclosing:
## 9. Benefit Corporation Governance (Parent Level)
Parent company incorporated as Benefit Corporation (not C-Corp) specifically to:
- Balance shareholder profit with stakeholder interests
- Legally protect Foundation funding decisions
- Enable long-term R&D investment (Labs) even during fiscal pressure
@ -231,6 +247,7 @@ Parent company incorporated as Benefit Corporation (not C-Corp) specifically to:
### Board Duties
Benefit Corporation directors have legal duty to:
1. Consider impact on stakeholders (workers, customers, community)
2. Balance shareholder returns with general public benefit
3. Document consideration of non-shareholder interests in board minutes
@ -240,6 +257,7 @@ This provides legal cover for capital allocation to Foundation or high-burn Labs
## 10. Private Inurement Prevention
### Definition
Private inurement = net earnings of Foundation inure to benefit of any shareholder/individual = immediate loss of 501(c)(3) status.
### High-Risk Transactions
@ -259,15 +277,18 @@ Private inurement = net earnings of Foundation inure to benefit of any sharehold
## 11. Transfer Pricing Documentation Requirements
### When Required
Any payment between related entities (Labs→Corp, Corp→Foundation, etc.) requires documentation.
### Documentation Package
1. **Intercompany Agreement**
- Parties, services/IP, payment terms, effective date
- Signed and dated
2. **Transfer Pricing Study**
- Executive summary
- Functional analysis (functions, assets, risks of each party)
- Economic analysis (industry data, market conditions)
@ -316,6 +337,7 @@ Maintain and update:
## Conclusion
This framework ensures:
1. **IP Protection**: Centralized control and defensive valuation
2. **Tax Compliance**: Arm's Length transfer pricing reduces audit risk
3. **Liability Insulation**: Separate entities prevent cross-contamination

View file

@ -14,9 +14,7 @@ const DISCORD_CLIENT_ID = process.env.DISCORD_CLIENT_ID || "578971245454950421";
const DISCORD_BOT_TOKEN = process.env.DISCORD_BOT_TOKEN;
if (!DISCORD_BOT_TOKEN) {
console.error(
"❌ DISCORD_BOT_TOKEN environment variable is required",
);
console.error("❌ DISCORD_BOT_TOKEN environment variable is required");
process.exit(1);
}
@ -103,14 +101,14 @@ async function registerCommands() {
});
console.log("\n📍 Next steps:");
console.log(
"1. Go to Discord Developer Portal > Application Settings",
);
console.log("1. Go to Discord Developer Portal > Application Settings");
console.log(
"2. Set Interactions Endpoint URL to: https://aethex.dev/api/discord/interactions",
);
console.log("3. Save changes");
console.log("4. Test commands in Discord with /creators, /opportunities, etc.");
console.log(
"4. Test commands in Discord with /creators, /opportunities, etc.",
);
} catch (error) {
console.error("❌ Error registering commands:", error);
process.exit(1);

View file

@ -115,7 +115,10 @@ const handleDiscordInteractions = (
// For MESSAGE_COMPONENT interactions (buttons, etc.)
if (interaction.type === 3) {
console.log("[Discord] Message component interaction:", interaction.data.custom_id);
console.log(
"[Discord] Message component interaction:",
interaction.data.custom_id,
);
return res.json({
type: 4,
@ -2418,7 +2421,7 @@ export function createServer() {
primary_arm,
created_at
`,
{ count: "exact" }
{ count: "exact" },
)
.eq("is_discoverable", true)
.order("created_at", { ascending: false });
@ -2475,7 +2478,7 @@ export function createServer() {
primary_arm,
created_at,
updated_at
`
`,
)
.eq("username", username)
.eq("is_discoverable", true)
@ -2507,10 +2510,21 @@ export function createServer() {
// Create creator profile
app.post("/api/creators", async (req, res) => {
try {
const { user_id, username, bio, skills, avatar_url, experience_level, primary_arm, arm_affiliations } = req.body;
const {
user_id,
username,
bio,
skills,
avatar_url,
experience_level,
primary_arm,
arm_affiliations,
} = req.body;
if (!user_id || !username) {
return res.status(400).json({ error: "user_id and username required" });
return res
.status(400)
.json({ error: "user_id and username required" });
}
const { data, error } = await adminSupabase
@ -2523,7 +2537,9 @@ export function createServer() {
avatar_url: avatar_url || null,
experience_level: experience_level || null,
primary_arm: primary_arm || null,
arm_affiliations: Array.isArray(arm_affiliations) ? arm_affiliations : [],
arm_affiliations: Array.isArray(arm_affiliations)
? arm_affiliations
: [],
})
.select()
.single();
@ -2538,7 +2554,9 @@ export function createServer() {
return res.status(201).json(data);
} catch (e: any) {
console.error("[Creator API] Error creating creator:", e?.message);
return res.status(500).json({ error: "Failed to create creator profile" });
return res
.status(500)
.json({ error: "Failed to create creator profile" });
}
});
@ -2550,7 +2568,16 @@ export function createServer() {
return res.status(400).json({ error: "creator id required" });
}
const { bio, skills, avatar_url, experience_level, primary_arm, arm_affiliations, is_discoverable, allow_recommendations } = req.body;
const {
bio,
skills,
avatar_url,
experience_level,
primary_arm,
arm_affiliations,
is_discoverable,
allow_recommendations,
} = req.body;
const { data, error } = await adminSupabase
.from("aethex_creators")
@ -2560,7 +2587,9 @@ export function createServer() {
avatar_url,
experience_level,
primary_arm,
arm_affiliations: Array.isArray(arm_affiliations) ? arm_affiliations : undefined,
arm_affiliations: Array.isArray(arm_affiliations)
? arm_affiliations
: undefined,
is_discoverable,
allow_recommendations,
updated_at: new Date().toISOString(),
@ -2574,7 +2603,9 @@ export function createServer() {
return res.json(data);
} catch (e: any) {
console.error("[Creator API] Error updating creator:", e?.message);
return res.status(500).json({ error: "Failed to update creator profile" });
return res
.status(500)
.json({ error: "Failed to update creator profile" });
}
});
@ -2606,7 +2637,7 @@ export function createServer() {
status,
created_at
`,
{ count: "exact" }
{ count: "exact" },
)
.eq("status", "open");
@ -2615,7 +2646,9 @@ export function createServer() {
}
if (search) {
query = query.or(`title.ilike.%${search}%,description.ilike.%${search}%`);
query = query.or(
`title.ilike.%${search}%,description.ilike.%${search}%`,
);
}
if (jobType) {
@ -2646,7 +2679,10 @@ export function createServer() {
},
});
} catch (e: any) {
console.error("[Opportunities API] Error fetching opportunities:", e?.message);
console.error(
"[Opportunities API] Error fetching opportunities:",
e?.message,
);
return res.status(500).json({ error: "Failed to fetch opportunities" });
}
});
@ -2676,7 +2712,7 @@ export function createServer() {
status,
created_at,
updated_at
`
`,
)
.eq("id", opportunityId)
.eq("status", "open")
@ -2691,7 +2727,10 @@ export function createServer() {
return res.json(data);
} catch (e: any) {
console.error("[Opportunities API] Error fetching opportunity:", e?.message);
console.error(
"[Opportunities API] Error fetching opportunity:",
e?.message,
);
return res.status(500).json({ error: "Failed to fetch opportunity" });
}
});
@ -2699,7 +2738,16 @@ export function createServer() {
// Create opportunity
app.post("/api/opportunities", async (req, res) => {
try {
const { user_id, title, description, job_type, salary_min, salary_max, experience_level, arm_affiliation } = req.body;
const {
user_id,
title,
description,
job_type,
salary_min,
salary_max,
experience_level,
arm_affiliation,
} = req.body;
if (!user_id || !title) {
return res.status(400).json({ error: "user_id and title required" });
@ -2712,7 +2760,11 @@ export function createServer() {
.single();
if (!creator) {
return res.status(404).json({ error: "Creator profile not found. Create profile first." });
return res
.status(404)
.json({
error: "Creator profile not found. Create profile first.",
});
}
const { data, error } = await adminSupabase
@ -2735,7 +2787,10 @@ export function createServer() {
return res.status(201).json(data);
} catch (e: any) {
console.error("[Opportunities API] Error creating opportunity:", e?.message);
console.error(
"[Opportunities API] Error creating opportunity:",
e?.message,
);
return res.status(500).json({ error: "Failed to create opportunity" });
}
});
@ -2744,10 +2799,21 @@ export function createServer() {
app.put("/api/opportunities/:id", async (req, res) => {
try {
const opportunityId = String(req.params.id || "").trim();
const { user_id, title, description, job_type, salary_min, salary_max, experience_level, status } = req.body;
const {
user_id,
title,
description,
job_type,
salary_min,
salary_max,
experience_level,
status,
} = req.body;
if (!opportunityId || !user_id) {
return res.status(400).json({ error: "opportunity id and user_id required" });
return res
.status(400)
.json({ error: "opportunity id and user_id required" });
}
const { data: opportunity } = await adminSupabase
@ -2790,7 +2856,10 @@ export function createServer() {
return res.json(data);
} catch (e: any) {
console.error("[Opportunities API] Error updating opportunity:", e?.message);
console.error(
"[Opportunities API] Error updating opportunity:",
e?.message,
);
return res.status(500).json({ error: "Failed to update opportunity" });
}
});
@ -2831,7 +2900,7 @@ export function createServer() {
updated_at,
aethex_opportunities(id, title, arm_affiliation, job_type, posted_by_id, aethex_creators!aethex_opportunities_posted_by_id_fkey(username, avatar_url))
`,
{ count: "exact" }
{ count: "exact" },
)
.eq("creator_id", creator.id);
@ -2858,7 +2927,10 @@ export function createServer() {
},
});
} catch (e: any) {
console.error("[Applications API] Error fetching applications:", e?.message);
console.error(
"[Applications API] Error fetching applications:",
e?.message,
);
return res.status(500).json({ error: "Failed to fetch applications" });
}
});
@ -2869,7 +2941,9 @@ export function createServer() {
const { user_id, opportunity_id, cover_letter } = req.body;
if (!user_id || !opportunity_id) {
return res.status(400).json({ error: "user_id and opportunity_id required" });
return res
.status(400)
.json({ error: "user_id and opportunity_id required" });
}
const { data: creator } = await adminSupabase
@ -2890,7 +2964,9 @@ export function createServer() {
.single();
if (!opportunity) {
return res.status(404).json({ error: "Opportunity not found or closed" });
return res
.status(404)
.json({ error: "Opportunity not found or closed" });
}
const { data: existing } = await adminSupabase
@ -2901,7 +2977,9 @@ export function createServer() {
.maybeSingle();
if (existing) {
return res.status(400).json({ error: "You have already applied to this opportunity" });
return res
.status(400)
.json({ error: "You have already applied to this opportunity" });
}
const { data, error } = await adminSupabase
@ -2919,7 +2997,10 @@ export function createServer() {
return res.status(201).json(data);
} catch (e: any) {
console.error("[Applications API] Error submitting application:", e?.message);
console.error(
"[Applications API] Error submitting application:",
e?.message,
);
return res.status(500).json({ error: "Failed to submit application" });
}
});
@ -2931,7 +3012,9 @@ export function createServer() {
const { user_id, status, response_message } = req.body;
if (!applicationId || !user_id) {
return res.status(400).json({ error: "application id and user_id required" });
return res
.status(400)
.json({ error: "application id and user_id required" });
}
const { data: application } = await adminSupabase
@ -2941,7 +3024,7 @@ export function createServer() {
id,
opportunity_id,
aethex_opportunities(posted_by_id)
`
`,
)
.eq("id", applicationId)
.single();
@ -2975,7 +3058,10 @@ export function createServer() {
return res.json(data);
} catch (e: any) {
console.error("[Applications API] Error updating application:", e?.message);
console.error(
"[Applications API] Error updating application:",
e?.message,
);
return res.status(500).json({ error: "Failed to update application" });
}
});
@ -2987,7 +3073,9 @@ export function createServer() {
const { user_id } = req.body;
if (!applicationId || !user_id) {
return res.status(400).json({ error: "application id and user_id required" });
return res
.status(400)
.json({ error: "application id and user_id required" });
}
const { data: application } = await adminSupabase
@ -3019,18 +3107,26 @@ export function createServer() {
return res.json({ ok: true });
} catch (e: any) {
console.error("[Applications API] Error withdrawing application:", e?.message);
return res.status(500).json({ error: "Failed to withdraw application" });
console.error(
"[Applications API] Error withdrawing application:",
e?.message,
);
return res
.status(500)
.json({ error: "Failed to withdraw application" });
}
});
// Link DevConnect account
app.post("/api/devconnect/link", async (req, res) => {
try {
const { user_id, devconnect_username, devconnect_profile_url } = req.body;
const { user_id, devconnect_username, devconnect_profile_url } =
req.body;
if (!user_id || !devconnect_username) {
return res.status(400).json({ error: "user_id and devconnect_username required" });
return res
.status(400)
.json({ error: "user_id and devconnect_username required" });
}
const { data: creator } = await adminSupabase
@ -3040,7 +3136,11 @@ export function createServer() {
.single();
if (!creator) {
return res.status(404).json({ error: "Creator profile not found. Create profile first." });
return res
.status(404)
.json({
error: "Creator profile not found. Create profile first.",
});
}
const { data: existing } = await adminSupabase
@ -3057,7 +3157,9 @@ export function createServer() {
.from("aethex_devconnect_links")
.update({
devconnect_username,
devconnect_profile_url: devconnect_profile_url || `https://dev-link.me/${devconnect_username}`,
devconnect_profile_url:
devconnect_profile_url ||
`https://dev-link.me/${devconnect_username}`,
})
.eq("aethex_creator_id", creator.id)
.select()
@ -3072,7 +3174,9 @@ export function createServer() {
.insert({
aethex_creator_id: creator.id,
devconnect_username,
devconnect_profile_url: devconnect_profile_url || `https://dev-link.me/${devconnect_username}`,
devconnect_profile_url:
devconnect_profile_url ||
`https://dev-link.me/${devconnect_username}`,
})
.select()
.single();
@ -3089,7 +3193,9 @@ export function createServer() {
return res.status(status).json(result);
} catch (e: any) {
console.error("[DevConnect API] Error linking account:", e?.message);
return res.status(500).json({ error: "Failed to link DevConnect account" });
return res
.status(500)
.json({ error: "Failed to link DevConnect account" });
}
});
@ -3125,7 +3231,9 @@ export function createServer() {
return res.json({ data: data || null });
} catch (e: any) {
console.error("[DevConnect API] Error fetching link:", e?.message);
return res.status(500).json({ error: "Failed to fetch DevConnect link" });
return res
.status(500)
.json({ error: "Failed to fetch DevConnect link" });
}
});
@ -3163,10 +3271,11 @@ export function createServer() {
return res.json({ ok: true });
} catch (e: any) {
console.error("[DevConnect API] Error unlinking account:", e?.message);
return res.status(500).json({ error: "Failed to unlink DevConnect account" });
return res
.status(500)
.json({ error: "Failed to unlink DevConnect account" });
}
});
} catch (e) {
console.warn("Admin API not initialized:", e);
}

View file

@ -1,82 +1,76 @@
# Phase 3: Testing & Validation - COMPLETE ✅
## Overview
Phase 3 successfully delivered comprehensive testing infrastructure for the AeThex Creator Network, covering end-to-end flows, error handling, performance measurement, and security audit protocols.
## 📦 Deliverables
### 1. End-to-End Test Suite (`code/tests/e2e-creator-network.test.ts`)
**Status:** ✅ Complete (490 lines)
**Test Flows Covered:**
- FLOW 1: Creator Registration & Profile Setup
- Create 2 creator profiles with different arms
- Verify profile data accuracy
- FLOW 2: Opportunity Creation & Discovery
- Create opportunities
- Browse with filters
- Pagination verification
- FLOW 3: Creator Discovery & Profiles
- Browse creators with arm filters
- Individual profile retrieval
- Profile data validation
- FLOW 4: Application Submission & Tracking
- Submit applications
- Prevent duplicate applications
- Get applications list
- Update application status
- FLOW 5: DevConnect Linking
- Link DevConnect accounts
- Get DevConnect links
- Unlink accounts
- FLOW 6: Advanced Filtering & Search
- Search creators
- Filter opportunities
- Pagination testing
**Features:**
- Performance timing for each operation
- Detailed error messages
- Comprehensive test summary with pass/fail counts
### 2. Error Handling Test Suite (`code/tests/error-handling.test.ts`)
**Status:** ✅ Complete (447 lines)
**Test Categories:**
1. **Input Validation Errors** (4 tests)
- Missing required fields (user_id, username, title, opportunity_id)
- Validation of mandatory parameters
2. **Not Found Errors** (3 tests)
- Non-existent creators, opportunities, applications
- 404 responses for missing resources
3. **Authorization & Ownership Errors** (2 tests)
- Invalid creator IDs
- Unauthorized access attempts
4. **Duplicate & Conflict Errors** (2 tests)
- Duplicate username prevention
- Duplicate application prevention
5. **Missing Required Relationships** (2 tests)
- Creating opportunities without creator profile
- Applying without creator profile
6. **Invalid Query Parameters** (3 tests)
- Invalid pagination parameters
- Oversized limits
- Invalid arm filters
7. **Empty & Null Values** (2 tests)
- Empty user_id and username
- Empty search strings
8. **DevConnect Linking Errors** (3 tests)
- Missing required fields
- Non-existent creator
@ -85,10 +79,13 @@ Phase 3 successfully delivered comprehensive testing infrastructure for the AeTh
**Total:** 22 error handling test cases
### 3. Performance Test Suite (`code/tests/performance.test.ts`)
**Status:** ✅ Complete (282 lines)
**Benchmarked Categories:**
1. **GET Endpoints** (Browse, Filter, Individual Retrieval)
- /api/creators (pagination)
- /api/opportunities (pagination)
- /api/applications
@ -99,11 +96,13 @@ Phase 3 successfully delivered comprehensive testing infrastructure for the AeTh
- /api/devconnect/link
2. **POST Endpoints** (Create Operations)
- POST /api/creators
- POST /api/opportunities
- POST /api/applications
3. **PUT Endpoints** (Update Operations)
- PUT /api/creators/:id
- PUT /api/opportunities/:id
@ -112,6 +111,7 @@ Phase 3 successfully delivered comprehensive testing infrastructure for the AeTh
- Deep pagination
**Metrics Collected:**
- Average response time (ms)
- Min/Max response times
- P95/P99 percentiles
@ -119,54 +119,66 @@ Phase 3 successfully delivered comprehensive testing infrastructure for the AeTh
- Performance target compliance
**Performance Targets:**
- GET endpoints: < 100ms
- POST endpoints: < 200ms
- PUT endpoints: < 150ms
- Complex queries: < 250ms
### 4. Security Audit Checklist (`code/tests/SECURITY_AUDIT.md`)
**Status:** ✅ Complete (276 lines)
**Sections:**
1. **Authentication & Authorization**
- JWT validation
- User context extraction
- Authorization checks
2. **Row Level Security (RLS) Policies**
- Per-table RLS policies
- Visibility controls
- Ownership enforcement
3. **Data Protection**
- Sensitive data handling
- Private field protection
- Rate limiting
4. **Input Validation & Sanitization**
- Text field validation
- File upload security
- Array field validation
- Numeric field validation
5. **API Endpoint Security**
- Per-endpoint security checklist
- GET/POST/PUT/DELETE security
- Parameter validation
6. **SQL Injection Prevention**
- Parameterized queries
- Search/filter safety
7. **CORS & External Access**
- CORS headers
- URL validation
8. **Audit Logging**
- Critical action logging
- Log retention
9. **API Response Security**
- Error message safety
- Response headers
@ -180,6 +192,7 @@ Phase 3 successfully delivered comprehensive testing infrastructure for the AeTh
## 📊 Testing Coverage
### APIs Tested
- ✅ GET /api/creators (browse, filters, search, pagination)
- ✅ GET /api/creators/:username (individual profile)
- ✅ POST /api/creators (create profile)
@ -197,6 +210,7 @@ Phase 3 successfully delivered comprehensive testing infrastructure for the AeTh
- ✅ DELETE /api/devconnect/link (unlink account)
### Test Scenarios Covered
- ✅ Complete user journeys (signup → profile → post → apply → track)
- ✅ Filtering and search functionality
- ✅ Pagination and sorting
@ -213,6 +227,7 @@ Phase 3 successfully delivered comprehensive testing infrastructure for the AeTh
## 🎯 Key Findings
### Strengths
1. **Comprehensive API**: All creator network endpoints fully functional
2. **Error Handling**: Proper HTTP status codes and error messages
3. **Data Validation**: Required fields validated on all endpoints
@ -220,6 +235,7 @@ Phase 3 successfully delivered comprehensive testing infrastructure for the AeTh
5. **Performance**: Response times within acceptable ranges
### Recommendations
1. **Security**: Implement full RLS policies (see SECURITY_AUDIT.md)
2. **Rate Limiting**: Add rate limiting to prevent abuse
3. **Logging**: Implement audit logging for critical operations
@ -229,6 +245,7 @@ Phase 3 successfully delivered comprehensive testing infrastructure for the AeTh
## 🚀 What's Next
Phase 4: Onboarding Integration
- Integrate creator profile setup into signup flow
- Auto-create creator profiles on account creation
- Collect creator preferences during onboarding

View file

@ -1,15 +1,18 @@
# Creator Network Security Audit Checklist
## Phase 3: Testing & Validation
### 🔐 Authentication & Authorization
- [ ] **JWT Validation**
- [ ] All protected endpoints require valid JWT token
- [ ] Expired tokens are rejected
- [ ] Invalid/malformed tokens return 401
- [ ] Token claims are validated before processing
- [ ] **User Context Extraction**
- [ ] user_id is extracted from Supabase auth context (not request body)
- [ ] User cannot access/modify other users' data
- [ ] Session invalidation works properly on logout
@ -24,23 +27,27 @@
### 🛡️ Row Level Security (RLS) Policies
- [ ] **aethex_creators table**
- [ ] Users can read own profile
- [ ] Users can update own profile
- [ ] Public profiles are discoverable (is_discoverable=true)
- [ ] Private profiles (is_discoverable=false) are hidden from directory
- [ ] **aethex_opportunities table**
- [ ] Anyone can read open opportunities
- [ ] Only creator can update/delete own opportunities
- [ ] Closed opportunities not visible to applicants
- [ ] **aethex_applications table**
- [ ] Users can read their own applications
- [ ] Applicant can only see their own applications
- [ ] Opportunity creator can see applications for their opportunities
- [ ] Users cannot access others' applications
- [ ] **aethex_devconnect_links table**
- [ ] Users can only access their own DevConnect links
- [ ] Links cannot be modified by non-owners
@ -51,11 +58,13 @@
### 🔒 Data Protection
- [ ] **Sensitive Data**
- [ ] Passwords are never returned in API responses
- [ ] Email addresses are not exposed in public profiles
- [ ] Private notes/applications are not leaked
- [ ] **Cover Letters**
- [ ] Only applicant and opportunity creator can see cover letters
- [ ] Cover letters are not visible in search results
@ -67,16 +76,19 @@
### 🚫 Input Validation & Sanitization
- [ ] **Text Fields**
- [ ] Bio/description max length enforced (e.g., 500 chars)
- [ ] Username format validated (alphanumeric, dashes, underscores)
- [ ] HTML/script tags are escaped in output
- [ ] **File Uploads**
- [ ] Avatar URLs are validated/whitelisted
- [ ] No malicious file types accepted
- [ ] File size limits enforced
- [ ] **Array Fields**
- [ ] Skills array has max length
- [ ] Arm affiliations are from valid set
- [ ] Invalid values are rejected
@ -89,16 +101,20 @@
### 🔗 API Endpoint Security
**Creators Endpoints:**
- [ ] GET /api/creators
- [ ] Pagination parameters validated
- [ ] Search doesn't expose private fields
- [ ] Arm filter works correctly
- [ ] GET /api/creators/:username
- [ ] Returns 404 if profile is not discoverable
- [ ] No sensitive data leaked
- [ ] POST /api/creators
- [ ] Requires auth
- [ ] user_id extracted from auth context
- [ ] Duplicate username prevention works
@ -109,16 +125,20 @@
- [ ] No privilege escalation possible
**Opportunities Endpoints:**
- [ ] GET /api/opportunities
- [ ] Only open opportunities shown
- [ ] Closed/draft opportunities hidden
- [ ] Pagination and filters work
- [ ] GET /api/opportunities/:id
- [ ] Only returns open opportunities
- [ ] Creator info is sanitized
- [ ] POST /api/opportunities
- [ ] Requires auth + creator profile
- [ ] user_id extracted from auth
- [ ] Only opportunity creator can post
@ -129,18 +149,22 @@
- [ ] Can't change posted_by_id
**Applications Endpoints:**
- [ ] GET /api/applications
- [ ] Requires user_id + auth
- [ ] Users only see their own applications
- [ ] Opportunity creators can view applications
- [ ] POST /api/applications
- [ ] Requires auth + creator profile
- [ ] Validates opportunity exists
- [ ] Prevents duplicate applications
- [ ] Validates cover letter length
- [ ] PUT /api/applications/:id
- [ ] Requires auth
- [ ] Only opportunity creator can update
- [ ] Can only change status/response_message
@ -152,12 +176,15 @@
- [ ] Application is properly deleted
**DevConnect Endpoints:**
- [ ] POST /api/devconnect/link
- [ ] Requires auth + creator profile
- [ ] user_id from auth context
- [ ] Validates DevConnect username format
- [ ] GET /api/devconnect/link
- [ ] Requires user_id + auth
- [ ] Users only see their own link
- [ ] Returns null if not linked
@ -170,6 +197,7 @@
### 🔍 SQL Injection Prevention
- [ ] **Parameterized Queries**
- [ ] All Supabase queries use parameterized queries (not string concatenation)
- [ ] User input never directly in SQL strings
- [ ] Search queries are sanitized
@ -182,6 +210,7 @@
### 🌐 CORS & External Access
- [ ] **CORS Headers**
- [ ] Only allowed origins can call API
- [ ] Credentials are properly scoped
- [ ] Preflight requests handled correctly
@ -194,6 +223,7 @@
### 📋 Audit Logging
- [ ] **Critical Actions Logged**
- [ ] User account creation
- [ ] Opportunity creation/deletion
- [ ] Application status changes
@ -208,6 +238,7 @@
### 🔄 API Response Security
- [ ] **Error Messages**
- [ ] Don't leak system details
- [ ] Don't expose database structure
- [ ] Generic error messages for auth failures
@ -222,11 +253,13 @@
### 📱 Frontend Security
- [ ] **Token Management**
- [ ] Tokens stored securely (not localStorage if possible)
- [ ] Tokens cleared on logout
- [ ] Token refresh handled properly
- [ ] **XSS Prevention**
- [ ] User input escaped in templates
- [ ] No dangerouslySetInnerHTML without sanitization
- [ ] No eval() or similar dangerous functions
@ -238,17 +271,20 @@
### ✅ Testing Recommendations
1. **Penetration Testing**
- Test SQL injection attempts
- Test XSS payloads in input fields
- Test CSRF attacks
- Test broken access control
2. **Authorization Testing**
- Try accessing other users' resources
- Test privilege escalation attempts
- Verify RLS policies are enforced
3. **Data Validation Testing**
- Send oversized inputs
- Send malformed data
- Test boundary values
@ -269,7 +305,6 @@
---
**Audit Date:** _________________
**Auditor:** _________________
**Audit Date:** ********\_********
**Auditor:** ********\_********
**Status:** PENDING ⏳

View file

@ -19,14 +19,14 @@ const log = (result: TestResult) => {
results.push(result);
const symbol = result.status === "✓" ? "✓" : "✗";
console.log(
`${symbol} ${result.method.padEnd(6)} ${result.endpoint.padEnd(40)} - ${result.message}`
`${symbol} ${result.method.padEnd(6)} ${result.endpoint.padEnd(40)} - ${result.message}`,
);
};
const testEndpoint = async (
method: string,
endpoint: string,
body?: any
body?: any,
): Promise<any> => {
try {
const options: RequestInit = {
@ -56,7 +56,7 @@ async function runTests() {
try {
const { response, data } = await testEndpoint(
"GET",
"/api/creators?page=1&limit=10"
"/api/creators?page=1&limit=10",
);
if (response.ok) {
log({
@ -89,7 +89,7 @@ async function runTests() {
try {
const { response, data } = await testEndpoint(
"GET",
"/api/creators/testuser123"
"/api/creators/testuser123",
);
if (response.status === 404) {
log({
@ -178,7 +178,7 @@ async function runTests() {
try {
const { response, data } = await testEndpoint(
"GET",
"/api/opportunities?page=1&limit=10"
"/api/opportunities?page=1&limit=10",
);
if (response.ok) {
log({
@ -211,7 +211,7 @@ async function runTests() {
try {
const { response, data } = await testEndpoint(
"GET",
"/api/opportunities/fake-id-123"
"/api/opportunities/fake-id-123",
);
if (response.status === 404) {
log({
@ -251,7 +251,7 @@ async function runTests() {
description: "A test job opportunity",
job_type: "contract",
arm_affiliation: "gameforge",
}
},
);
if (response.status === 404) {
@ -320,7 +320,7 @@ async function runTests() {
{
user_id: "test-user-123",
devconnect_username: "testuser",
}
},
);
if (response.status === 404) {
@ -363,12 +363,14 @@ async function runTests() {
const passed = results.filter((r) => r.status === "✓").length;
const failed = results.filter((r) => r.status === "✗").length;
console.log(
`\nTest Summary: ${passed} passed, ${failed} failed out of ${results.length} tests`
`\nTest Summary: ${passed} passed, ${failed} failed out of ${results.length} tests`,
);
if (failed > 0) {
console.log("\n❌ Failed tests:");
results.filter((r) => r.status === "✗").forEach((r) => {
results
.filter((r) => r.status === "✗")
.forEach((r) => {
console.log(` - ${r.method} ${r.endpoint}: ${r.message}`);
});
} else {

View file

@ -21,9 +21,14 @@ const results: TestCase[] = [];
const BASE_URL = "http://localhost:5173";
// Test utilities
const sleep = (ms: number) => new Promise(resolve => setTimeout(resolve, ms));
const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
const test = (name: string, passed: boolean, message: string, duration: number = 0) => {
const test = (
name: string,
passed: boolean,
message: string,
duration: number = 0,
) => {
results.push({ name, passed, message, duration });
const symbol = passed ? "✓" : "✗";
console.log(`${symbol} ${name}`);
@ -43,7 +48,12 @@ const assertExists = (value: any, msg: string) => {
}
};
const assertInRange = (actual: number, min: number, max: number, msg: string) => {
const assertInRange = (
actual: number,
min: number,
max: number,
msg: string,
) => {
if (actual < min || actual > max) {
throw new Error(`${msg}: value ${actual} not in range [${min}, ${max}]`);
}
@ -69,7 +79,7 @@ async function runE2ETests() {
// FLOW 1: Creator Registration and Profile Setup
console.log("\n📝 FLOW 1: Creator Registration & Profile Setup");
console.log("=" .repeat(50));
console.log("=".repeat(50));
try {
// Create first creator profile
@ -95,13 +105,21 @@ async function runE2ETests() {
"Create creator profile 1",
createRes1.status === 201,
`Status: ${createRes1.status}`,
createCreator1Duration
createCreator1Duration,
);
if (createRes1.ok) {
assertExists(creator1Data.id, "Creator ID should exist");
assertEquals(creator1Data.username, testUsers.creator1.username, "Username mismatch");
assertEquals(creator1Data.primary_arm, "gameforge", "Primary arm mismatch");
assertEquals(
creator1Data.username,
testUsers.creator1.username,
"Username mismatch",
);
assertEquals(
creator1Data.primary_arm,
"gameforge",
"Primary arm mismatch",
);
}
// Create second creator profile
@ -127,7 +145,7 @@ async function runE2ETests() {
"Create creator profile 2",
createRes2.status === 201,
`Status: ${createRes2.status}`,
createCreator2Duration
createCreator2Duration,
);
} catch (error: any) {
test("Create creator profiles", false, error.message);
@ -135,7 +153,7 @@ async function runE2ETests() {
// FLOW 2: Opportunity Creation
console.log("\n📋 FLOW 2: Opportunity Creation & Discovery");
console.log("=" .repeat(50));
console.log("=".repeat(50));
let opportunityId: string | null = null;
@ -148,7 +166,8 @@ async function runE2ETests() {
body: JSON.stringify({
user_id: testUsers.creator1.id,
title: "Senior Game Dev - Unity Project",
description: "Looking for experienced Unity developer for 6-month contract",
description:
"Looking for experienced Unity developer for 6-month contract",
job_type: "contract",
salary_min: 80000,
salary_max: 120000,
@ -163,20 +182,24 @@ async function runE2ETests() {
"Create opportunity",
oppRes.status === 201,
`Status: ${oppRes.status}`,
createOppDuration
createOppDuration,
);
if (oppRes.ok) {
opportunityId = oppData.id;
assertExists(oppData.id, "Opportunity ID should exist");
assertEquals(oppData.title, "Senior Game Dev - Unity Project", "Title mismatch");
assertEquals(
oppData.title,
"Senior Game Dev - Unity Project",
"Title mismatch",
);
assertEquals(oppData.status, "open", "Status should be open");
}
// Browse opportunities with filters
const browseOppStart = Date.now();
const browseRes = await fetch(
`${BASE_URL}/api/opportunities?arm=gameforge&page=1&limit=10`
`${BASE_URL}/api/opportunities?arm=gameforge&page=1&limit=10`,
);
const browseData = await browseRes.json();
const browseOppDuration = Date.now() - browseOppStart;
@ -185,7 +208,7 @@ async function runE2ETests() {
"Browse opportunities with filters",
browseRes.ok && Array.isArray(browseData.data),
`Status: ${browseRes.status}, Found: ${browseData.data?.length || 0}`,
browseOppDuration
browseOppDuration,
);
if (browseRes.ok) {
@ -198,13 +221,13 @@ async function runE2ETests() {
// FLOW 3: Creator Discovery
console.log("\n👥 FLOW 3: Creator Discovery & Profiles");
console.log("=" .repeat(50));
console.log("=".repeat(50));
try {
// Browse creators
const browseCreatorsStart = Date.now();
const creatorsRes = await fetch(
`${BASE_URL}/api/creators?arm=gameforge&page=1&limit=20`
`${BASE_URL}/api/creators?arm=gameforge&page=1&limit=20`,
);
const creatorsData = await creatorsRes.json();
const browseCreatorsDuration = Date.now() - browseCreatorsStart;
@ -213,13 +236,13 @@ async function runE2ETests() {
"Browse creators with arm filter",
creatorsRes.ok && Array.isArray(creatorsData.data),
`Status: ${creatorsRes.status}, Found: ${creatorsData.data?.length || 0}`,
browseCreatorsDuration
browseCreatorsDuration,
);
// Get individual creator profile
const getCreatorStart = Date.now();
const creatorRes = await fetch(
`${BASE_URL}/api/creators/${testUsers.creator1.username}`
`${BASE_URL}/api/creators/${testUsers.creator1.username}`,
);
const creatorData = await creatorRes.json();
const getCreatorDuration = Date.now() - getCreatorStart;
@ -228,13 +251,17 @@ async function runE2ETests() {
"Get creator profile by username",
creatorRes.ok && creatorData.username === testUsers.creator1.username,
`Status: ${creatorRes.status}, Username: ${creatorData.username}`,
getCreatorDuration
getCreatorDuration,
);
if (creatorRes.ok) {
assertExists(creatorData.bio, "Bio should exist");
assertExists(creatorData.skills, "Skills should exist");
assertEquals(Array.isArray(creatorData.arm_affiliations), true, "Arm affiliations should be array");
assertEquals(
Array.isArray(creatorData.arm_affiliations),
true,
"Arm affiliations should be array",
);
}
} catch (error: any) {
test("Creator discovery and profiles", false, error.message);
@ -242,7 +269,7 @@ async function runE2ETests() {
// FLOW 4: Application Submission & Tracking
console.log("\n✉ FLOW 4: Apply for Opportunity & Track Status");
console.log("=" .repeat(50));
console.log("=".repeat(50));
let applicationId: string | null = null;
@ -270,7 +297,7 @@ async function runE2ETests() {
"Submit application",
applyRes.status === 201,
`Status: ${applyRes.status}`,
applyDuration
applyDuration,
);
if (applyRes.ok) {
@ -295,13 +322,13 @@ async function runE2ETests() {
"Prevent duplicate applications",
dupRes.status === 400,
`Status: ${dupRes.status} (should be 400)`,
0
0,
);
// Get applications for creator
const getAppsStart = Date.now();
const appsRes = await fetch(
`${BASE_URL}/api/applications?user_id=${testUsers.creator2.id}`
`${BASE_URL}/api/applications?user_id=${testUsers.creator2.id}`,
);
const appsData = await appsRes.json();
const getAppsDuration = Date.now() - getAppsStart;
@ -310,13 +337,15 @@ async function runE2ETests() {
"Get creator's applications",
appsRes.ok && Array.isArray(appsData.data),
`Status: ${appsRes.status}, Found: ${appsData.data?.length || 0}`,
getAppsDuration
getAppsDuration,
);
// Update application status (as opportunity creator)
if (applicationId) {
const updateStart = Date.now();
const updateRes = await fetch(`${BASE_URL}/api/applications/${applicationId}`, {
const updateRes = await fetch(
`${BASE_URL}/api/applications/${applicationId}`,
{
method: "PUT",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
@ -324,14 +353,15 @@ async function runE2ETests() {
status: "accepted",
response_message: "Great! We'd love to have you on board.",
}),
});
},
);
const updateDuration = Date.now() - updateStart;
test(
"Update application status",
updateRes.ok,
`Status: ${updateRes.status}`,
updateDuration
updateDuration,
);
}
} catch (error: any) {
@ -340,7 +370,7 @@ async function runE2ETests() {
// FLOW 5: DevConnect Linking
console.log("\n<><6E> FLOW 5: DevConnect Account Linking");
console.log("=" .repeat(50));
console.log("=".repeat(50));
try {
// Link DevConnect account
@ -361,13 +391,13 @@ async function runE2ETests() {
"Link DevConnect account",
linkRes.status === 201 || linkRes.status === 200,
`Status: ${linkRes.status}`,
linkDuration
linkDuration,
);
// Get DevConnect link
const getLinkStart = Date.now();
const getLinkRes = await fetch(
`${BASE_URL}/api/devconnect/link?user_id=${testUsers.creator1.id}`
`${BASE_URL}/api/devconnect/link?user_id=${testUsers.creator1.id}`,
);
const getLinkData = await getLinkRes.json();
const getLinkDuration = Date.now() - getLinkStart;
@ -376,11 +406,15 @@ async function runE2ETests() {
"Get DevConnect link",
getLinkRes.ok && getLinkData.data,
`Status: ${getLinkRes.status}`,
getLinkDuration
getLinkDuration,
);
if (getLinkRes.ok && getLinkData.data) {
assertEquals(getLinkData.data.devconnect_username, "devconnect_user_1", "Username mismatch");
assertEquals(
getLinkData.data.devconnect_username,
"devconnect_user_1",
"Username mismatch",
);
}
// Unlink DevConnect account
@ -394,7 +428,7 @@ async function runE2ETests() {
"Unlink DevConnect account",
unlinkRes.ok,
`Status: ${unlinkRes.status}`,
0
0,
);
} catch (error: any) {
test("DevConnect linking", false, error.message);
@ -402,13 +436,13 @@ async function runE2ETests() {
// FLOW 6: Filtering and Search
console.log("\n🔍 FLOW 6: Advanced Filtering & Search");
console.log("=" .repeat(50));
console.log("=".repeat(50));
try {
// Test creator search
const searchStart = Date.now();
const searchRes = await fetch(
`${BASE_URL}/api/creators?search=${testUsers.creator1.username.substring(0, 5)}`
`${BASE_URL}/api/creators?search=${testUsers.creator1.username.substring(0, 5)}`,
);
const searchData = await searchRes.json();
const searchDuration = Date.now() - searchStart;
@ -417,13 +451,13 @@ async function runE2ETests() {
"Search creators by name",
searchRes.ok && Array.isArray(searchData.data),
`Status: ${searchRes.status}, Found: ${searchData.data?.length || 0}`,
searchDuration
searchDuration,
);
// Test opportunity filtering by experience level
const expFilterStart = Date.now();
const expRes = await fetch(
`${BASE_URL}/api/opportunities?experienceLevel=senior`
`${BASE_URL}/api/opportunities?experienceLevel=senior`,
);
const expData = await expRes.json();
const expFilterDuration = Date.now() - expFilterStart;
@ -432,7 +466,7 @@ async function runE2ETests() {
"Filter opportunities by experience level",
expRes.ok && Array.isArray(expData.data),
`Status: ${expRes.status}, Found: ${expData.data?.length || 0}`,
expFilterDuration
expFilterDuration,
);
// Test pagination
@ -445,10 +479,13 @@ async function runE2ETests() {
"Pagination - page 1",
page1Res.ok && page1Data.pagination?.page === 1,
`Page: ${page1Data.pagination?.page}, Limit: ${page1Data.pagination?.limit}`,
page1Duration
page1Duration,
);
assertExists(page1Data.pagination?.pages, "Total pages should be calculated");
assertExists(
page1Data.pagination?.pages,
"Total pages should be calculated",
);
} catch (error: any) {
test("Filtering and search", false, error.message);
}
@ -463,12 +500,16 @@ async function runE2ETests() {
console.log(` ✓ Passed: ${passed}`);
console.log(` ✗ Failed: ${failed}`);
console.log(` Total: ${results.length}`);
console.log(` Duration: ${totalDuration}ms (avg ${(totalDuration / results.length).toFixed(0)}ms per test)`);
console.log(
` Duration: ${totalDuration}ms (avg ${(totalDuration / results.length).toFixed(0)}ms per test)`,
);
console.log("\n" + "=".repeat(50));
if (failed > 0) {
console.log("\n❌ Failed Tests:");
results.filter((r) => !r.passed).forEach((r) => {
results
.filter((r) => !r.passed)
.forEach((r) => {
console.log(` - ${r.name}: ${r.message}`);
});
} else {

View file

@ -19,12 +19,12 @@ const test = (
passed: boolean,
expectedStatus: number,
actualStatus: number,
message: string
message: string,
) => {
results.push({ name, passed, expectedStatus, actualStatus, message });
const symbol = passed ? "✓" : "✗";
console.log(
`${symbol} ${name.padEnd(50)} | Expected: ${expectedStatus}, Got: ${actualStatus}`
`${symbol} ${name.padEnd(50)} | Expected: ${expectedStatus}, Got: ${actualStatus}`,
);
};
@ -46,7 +46,7 @@ async function runErrorTests() {
res.status === 400,
400,
res.status,
"Should require user_id"
"Should require user_id",
);
res = await fetch(`${BASE_URL}/api/creators`, {
@ -59,7 +59,7 @@ async function runErrorTests() {
res.status === 400,
400,
res.status,
"Should require username"
"Should require username",
);
res = await fetch(`${BASE_URL}/api/opportunities`, {
@ -72,7 +72,7 @@ async function runErrorTests() {
res.status === 400,
400,
res.status,
"Should require title"
"Should require title",
);
res = await fetch(`${BASE_URL}/api/applications`, {
@ -85,7 +85,7 @@ async function runErrorTests() {
res.status === 400,
400,
res.status,
"Should require opportunity_id"
"Should require opportunity_id",
);
// ERROR CATEGORY 2: Not Found Errors
@ -98,7 +98,7 @@ async function runErrorTests() {
res.status === 404,
404,
res.status,
"Should return 404 for non-existent creator"
"Should return 404 for non-existent creator",
);
res = await fetch(`${BASE_URL}/api/opportunities/fake-opp-id-99999`);
@ -107,7 +107,7 @@ async function runErrorTests() {
res.status === 404,
404,
res.status,
"Should return 404 for non-existent opportunity"
"Should return 404 for non-existent opportunity",
);
res = await fetch(`${BASE_URL}/api/applications?user_id=nonexistent-user`);
@ -116,7 +116,7 @@ async function runErrorTests() {
res.status === 404,
404,
res.status,
"Should return 404 when creator doesn't exist"
"Should return 404 when creator doesn't exist",
);
// ERROR CATEGORY 3: Authorization/Ownership Errors
@ -154,7 +154,7 @@ async function runErrorTests() {
res.status === 500 || res.status === 404,
404,
res.status,
"Should reject invalid creator ID"
"Should reject invalid creator ID",
);
}
@ -188,7 +188,7 @@ async function runErrorTests() {
res.status === 400,
400,
res.status,
"Should prevent duplicate usernames"
"Should prevent duplicate usernames",
);
// Test duplicate application (create opportunity first)
@ -260,7 +260,7 @@ async function runErrorTests() {
res.status === 400,
400,
res.status,
"Should prevent duplicate applications"
"Should prevent duplicate applications",
);
}
}
@ -283,7 +283,7 @@ async function runErrorTests() {
res.status === 404,
404,
res.status,
"Should require existing creator profile"
"Should require existing creator profile",
);
res = await fetch(`${BASE_URL}/api/applications`, {
@ -299,7 +299,7 @@ async function runErrorTests() {
res.status === 404,
404,
res.status,
"Should require existing creator profile"
"Should require existing creator profile",
);
// ERROR CATEGORY 6: Invalid Query Parameters
@ -313,7 +313,7 @@ async function runErrorTests() {
res.ok, // Should still work with default pagination
200,
res.status,
"Should handle invalid page gracefully"
"Should handle invalid page gracefully",
);
res = await fetch(`${BASE_URL}/api/opportunities?limit=999999`);
@ -322,7 +322,7 @@ async function runErrorTests() {
res.ok, // Should cap the limit
200,
res.status,
"Should cap maximum limit"
"Should cap maximum limit",
);
res = await fetch(`${BASE_URL}/api/creators?arm=invalid_arm`);
@ -332,7 +332,7 @@ async function runErrorTests() {
res.ok && Array.isArray(armData.data),
200,
res.status,
"Should return empty results or handle gracefully"
"Should return empty results or handle gracefully",
);
// ERROR CATEGORY 7: Empty/Null Values
@ -352,7 +352,7 @@ async function runErrorTests() {
res.status === 400,
400,
res.status,
"Should reject empty user_id"
"Should reject empty user_id",
);
res = await fetch(`${BASE_URL}/api/creators?search=`);
@ -361,7 +361,7 @@ async function runErrorTests() {
res.ok,
200,
res.status,
"Should handle empty search gracefully"
"Should handle empty search gracefully",
);
// ERROR CATEGORY 8: Missing DevConnect Parameters
@ -378,7 +378,7 @@ async function runErrorTests() {
res.status === 400,
400,
res.status,
"Should require user_id"
"Should require user_id",
);
res = await fetch(`${BASE_URL}/api/devconnect/link`, {
@ -391,7 +391,7 @@ async function runErrorTests() {
res.status === 400,
400,
res.status,
"Should require devconnect_username"
"Should require devconnect_username",
);
res = await fetch(`${BASE_URL}/api/devconnect/link`, {
@ -407,7 +407,7 @@ async function runErrorTests() {
res.status === 404,
404,
res.status,
"Should require existing creator profile"
"Should require existing creator profile",
);
// Summary
@ -426,7 +426,9 @@ async function runErrorTests() {
.filter((r) => !r.passed)
.forEach((r) => {
console.log(` - ${r.name}`);
console.log(` Expected ${r.expectedStatus}, got ${r.actualStatus}: ${r.message}`);
console.log(
` Expected ${r.expectedStatus}, got ${r.actualStatus}: ${r.message}`,
);
});
} else {
console.log("\n✅ All error handling tests passed!");

View file

@ -23,7 +23,7 @@ const metrics: PerformanceMetric[] = [];
async function measureRequest(
method: string,
endpoint: string,
body?: any
body?: any,
): Promise<number> {
const start = performance.now();
try {
@ -45,10 +45,10 @@ async function benchmarkEndpoint(
method: string,
endpoint: string,
numRequests: number = 50,
body?: any
body?: any,
): Promise<PerformanceMetric> {
console.log(
` Benchmarking ${method.padEnd(6)} ${endpoint.padEnd(35)} (${numRequests} requests)...`
` Benchmarking ${method.padEnd(6)} ${endpoint.padEnd(35)} (${numRequests} requests)...`,
);
const times: number[] = [];
@ -91,7 +91,11 @@ async function runPerformanceTests() {
console.log("\nFilter endpoints (filtered queries):");
await benchmarkEndpoint("GET", "/api/creators?arm=gameforge", 50);
await benchmarkEndpoint("GET", "/api/opportunities?arm=gameforge&sort=recent", 50);
await benchmarkEndpoint(
"GET",
"/api/opportunities?arm=gameforge&sort=recent",
50,
);
await benchmarkEndpoint("GET", "/api/creators?search=test", 40);
console.log("\nIndividual resource retrieval:");
@ -155,12 +159,12 @@ async function runPerformanceTests() {
await benchmarkEndpoint(
"GET",
"/api/creators?arm=gameforge&search=test&page=1&limit=50",
30
30,
);
await benchmarkEndpoint(
"GET",
"/api/opportunities?arm=labs&experienceLevel=senior&jobType=full-time&page=1&limit=50",
25
25,
);
console.log("\nMulti-page traversal:");
@ -182,10 +186,15 @@ async function runPerformanceTests() {
// Print detailed metrics
grouped.forEach((metricList, endpoint) => {
const avg = metricList.reduce((sum, m) => sum + m.avgTime, 0) / metricList.length;
const p95 = metricList.reduce((sum, m) => sum + m.p95Time, 0) / metricList.length;
const p99 = metricList.reduce((sum, m) => sum + m.p99Time, 0) / metricList.length;
const rps = metricList.reduce((sum, m) => sum + m.requestsPerSecond, 0) / metricList.length;
const avg =
metricList.reduce((sum, m) => sum + m.avgTime, 0) / metricList.length;
const p95 =
metricList.reduce((sum, m) => sum + m.p95Time, 0) / metricList.length;
const p99 =
metricList.reduce((sum, m) => sum + m.p99Time, 0) / metricList.length;
const rps =
metricList.reduce((sum, m) => sum + m.requestsPerSecond, 0) /
metricList.length;
console.log(`📍 ${endpoint}`);
console.log(` Avg: ${avg.toFixed(2)}ms`);
@ -210,21 +219,30 @@ async function runPerformanceTests() {
metrics.forEach((m) => {
if (m.method === "GET") {
targets["GET endpoints"].actual = Math.max(targets["GET endpoints"].actual, m.avgTime);
targets["GET endpoints"].actual = Math.max(
targets["GET endpoints"].actual,
m.avgTime,
);
} else if (m.method === "POST") {
targets["POST endpoints"].actual = Math.max(targets["POST endpoints"].actual, m.avgTime);
targets["POST endpoints"].actual = Math.max(
targets["POST endpoints"].actual,
m.avgTime,
);
} else if (m.method === "PUT") {
targets["PUT endpoints"].actual = Math.max(targets["PUT endpoints"].actual, m.avgTime);
targets["PUT endpoints"].actual = Math.max(
targets["PUT endpoints"].actual,
m.avgTime,
);
}
});
// Check complex queries
const complexQueries = metrics.filter(
(m) => m.endpoint.includes("?") && m.endpoint.split("&").length > 2
(m) => m.endpoint.includes("?") && m.endpoint.split("&").length > 2,
);
if (complexQueries.length > 0) {
targets["Complex queries"].actual = Math.max(
...complexQueries.map((m) => m.avgTime)
...complexQueries.map((m) => m.avgTime),
);
}
@ -234,7 +252,7 @@ async function runPerformanceTests() {
const passed = data.actual <= data.target;
const symbol = passed ? "✓" : "✗";
console.log(
`${symbol} ${category.padEnd(20)} ${data.actual.toFixed(2)}ms ${data.threshold}`
`${symbol} ${category.padEnd(20)} ${data.actual.toFixed(2)}ms ${data.threshold}`,
);
if (passed) targetsPassed++;
@ -247,24 +265,32 @@ async function runPerformanceTests() {
const allTimes = metrics.map((m) => m.avgTime);
const slowestEndpoint = metrics.reduce((a, b) =>
a.avgTime > b.avgTime ? a : b
a.avgTime > b.avgTime ? a : b,
);
const fastestEndpoint = metrics.reduce((a, b) =>
a.avgTime < b.avgTime ? a : b
a.avgTime < b.avgTime ? a : b,
);
console.log(`Total Endpoints Tested: ${metrics.length}`);
console.log(`Average Response Time: ${(allTimes.reduce((a, b) => a + b) / allTimes.length).toFixed(2)}ms`);
console.log(`Fastest: ${fastestEndpoint.method} ${fastestEndpoint.endpoint.split("?")[0]} (${fastestEndpoint.avgTime.toFixed(2)}ms)`);
console.log(`Slowest: ${slowestEndpoint.method} ${slowestEndpoint.endpoint.split("?")[0]} (${slowestEndpoint.avgTime.toFixed(2)}ms)`);
console.log(
`\nPerformance Targets: ${targetsPassed} passed, ${targetsFailed} failed`
`Average Response Time: ${(allTimes.reduce((a, b) => a + b) / allTimes.length).toFixed(2)}ms`,
);
console.log(
`Fastest: ${fastestEndpoint.method} ${fastestEndpoint.endpoint.split("?")[0]} (${fastestEndpoint.avgTime.toFixed(2)}ms)`,
);
console.log(
`Slowest: ${slowestEndpoint.method} ${slowestEndpoint.endpoint.split("?")[0]} (${slowestEndpoint.avgTime.toFixed(2)}ms)`,
);
console.log(
`\nPerformance Targets: ${targetsPassed} passed, ${targetsFailed} failed`,
);
if (targetsFailed === 0) {
console.log("\n✅ All performance targets met!");
} else {
console.log(`\n⚠ ${targetsFailed} performance targets not met. Optimization needed.`);
console.log(
`\n⚠ ${targetsFailed} performance targets not met. Optimization needed.`,
);
}
console.log("\n" + "=".repeat(70));