Prettier format pending files

This commit is contained in:
Builder.io 2025-09-27 23:26:32 +00:00
parent fd79f442c2
commit cd41527a58
9 changed files with 663 additions and 191 deletions

View file

@ -53,7 +53,10 @@ const App = () => (
<Route path="/dashboard" element={<Dashboard />} /> <Route path="/dashboard" element={<Dashboard />} />
<Route path="/admin" element={<Admin />} /> <Route path="/admin" element={<Admin />} />
<Route path="/feed" element={<Feed />} /> <Route path="/feed" element={<Feed />} />
<Route path="/network" element={<Navigate to="/feed" replace />} /> <Route
path="/network"
element={<Navigate to="/feed" replace />}
/>
<Route path="/projects/new" element={<ProjectsNew />} /> <Route path="/projects/new" element={<ProjectsNew />} />
<Route <Route
path="/profile" path="/profile"

View file

@ -235,13 +235,20 @@ export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({
user.id, user.id,
updates, updates,
); );
setProfile((prev) => ({ ...(prev || {} as any), ...(updatedProfile || {} as any), ...updates } as any)); setProfile(
(prev) =>
({
...(prev || ({} as any)),
...(updatedProfile || ({} as any)),
...updates,
}) as any,
);
aethexToast.success({ aethexToast.success({
title: "Profile updated", title: "Profile updated",
description: "Your profile has been updated successfully", description: "Your profile has been updated successfully",
}); });
} catch (error: any) { } catch (error: any) {
setProfile((prev) => ({ ...(prev || {} as any), ...updates } as any)); setProfile((prev) => ({ ...(prev || ({} as any)), ...updates }) as any);
aethexToast.error({ aethexToast.error({
title: "Update failed", title: "Update failed",
description: error.message, description: error.message,

View file

@ -81,13 +81,17 @@ export const aethexUserService = {
// If table missing, fall back to mock for local dev only // If table missing, fall back to mock for local dev only
if (isTableMissing(error)) { if (isTableMissing(error)) {
const mock = await mockAuth.getUserProfile(user.id as any); const mock = await mockAuth.getUserProfile(user.id as any);
if (mock) return { ...(mock as any), email: user.email } as AethexUserProfile; if (mock)
const created = await mockAuth.updateProfile(user.id as any, { return { ...(mock as any), email: user.email } as AethexUserProfile;
username: user.email?.split("@")[0] || "user", const created = await mockAuth.updateProfile(
email: user.email || "", user.id as any,
role: "member", {
onboarded: true, username: user.email?.split("@")[0] || "user",
} as any); email: user.email || "",
role: "member",
onboarded: true,
} as any,
);
return { ...(created as any), email: user.email } as AethexUserProfile; return { ...(created as any), email: user.email } as AethexUserProfile;
} }
// If no row, create initial DB profile instead of mock // If no row, create initial DB profile instead of mock
@ -132,13 +136,19 @@ export const aethexUserService = {
if (error) { if (error) {
if (isTableMissing(error)) { if (isTableMissing(error)) {
const mock = await mockAuth.updateProfile(userId as any, updates as any); const mock = await mockAuth.updateProfile(
userId as any,
updates as any,
);
return mock as unknown as AethexUserProfile; return mock as unknown as AethexUserProfile;
} }
if ((error as any)?.code === "PGRST116") { if ((error as any)?.code === "PGRST116") {
const { data: upserted, error: upsertError } = await supabase const { data: upserted, error: upsertError } = await supabase
.from("user_profiles") .from("user_profiles")
.upsert({ id: userId, user_type: "community_member", ...updates } as any, { onConflict: "id" }) .upsert(
{ id: userId, user_type: "community_member", ...updates } as any,
{ onConflict: "id" },
)
.select() .select()
.single(); .single();
if (upsertError) throw upsertError; if (upsertError) throw upsertError;

View file

@ -1,20 +1,59 @@
import Layout from "@/components/Layout"; import Layout from "@/components/Layout";
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card"; import {
Card,
CardContent,
CardHeader,
CardTitle,
CardDescription,
} from "@/components/ui/card";
import { Badge } from "@/components/ui/badge"; import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Rocket, Cpu, Users, Shield, Zap, GitBranch } from "lucide-react"; import { Rocket, Cpu, Users, Shield, Zap, GitBranch } from "lucide-react";
export default function About() { export default function About() {
const values = [ const values = [
{ icon: <Shield className="h-5 w-5" />, title: "Integrity", desc: "Transparent processes, honest communication, dependable delivery." }, {
{ icon: <Zap className="h-5 w-5" />, title: "Excellence", desc: "Relentless attention to quality, performance, and user experience." }, icon: <Shield className="h-5 w-5" />,
{ icon: <Users className="h-5 w-5" />, title: "Partnership", desc: "We win with our customers, not at their expense." }, title: "Integrity",
desc: "Transparent processes, honest communication, dependable delivery.",
},
{
icon: <Zap className="h-5 w-5" />,
title: "Excellence",
desc: "Relentless attention to quality, performance, and user experience.",
},
{
icon: <Users className="h-5 w-5" />,
title: "Partnership",
desc: "We win with our customers, not at their expense.",
},
]; ];
const capabilities = [ const capabilities = [
{ title: "Product Engineering", points: ["Web & Mobile Apps", "Realtime & AI Systems", "3D & Game Experiences"] }, {
{ title: "Platform & Infra", points: ["Cloud-native Architecture", "DevOps & Observability", "Security & Compliance"] }, title: "Product Engineering",
{ title: "Advisory & Enablement", points: ["Technical Strategy", "Codebase Modernization", "Team Upskilling"] }, points: [
"Web & Mobile Apps",
"Realtime & AI Systems",
"3D & Game Experiences",
],
},
{
title: "Platform & Infra",
points: [
"Cloud-native Architecture",
"DevOps & Observability",
"Security & Compliance",
],
},
{
title: "Advisory & Enablement",
points: [
"Technical Strategy",
"Codebase Modernization",
"Team Upskilling",
],
},
]; ];
const milestones = [ const milestones = [
@ -30,9 +69,13 @@ export default function About() {
<div className="container mx-auto px-4 max-w-6xl space-y-10"> <div className="container mx-auto px-4 max-w-6xl space-y-10">
<div className="grid md:grid-cols-2 gap-8 items-start"> <div className="grid md:grid-cols-2 gap-8 items-start">
<div className="space-y-4"> <div className="space-y-4">
<h1 className="text-4xl font-bold text-gradient-purple">About AeThex</h1> <h1 className="text-4xl font-bold text-gradient-purple">
About AeThex
</h1>
<p className="text-muted-foreground text-lg"> <p className="text-muted-foreground text-lg">
We craft reliable, scalable softwareshipping fast without compromising quality. From prototypes to global platforms, we partner end-to-end: strategy, design, engineering, and growth. We craft reliable, scalable softwareshipping fast without
compromising quality. From prototypes to global platforms, we
partner end-to-end: strategy, design, engineering, and growth.
</p> </p>
<div className="flex flex-wrap gap-2"> <div className="flex flex-wrap gap-2">
<Badge variant="outline">TypeScript</Badge> <Badge variant="outline">TypeScript</Badge>
@ -42,21 +85,31 @@ export default function About() {
<Badge variant="outline">Edge</Badge> <Badge variant="outline">Edge</Badge>
</div> </div>
<div className="flex gap-3 pt-2"> <div className="flex gap-3 pt-2">
<Button asChild><a href="/contact">Start a project</a></Button> <Button asChild>
<Button asChild variant="outline"><a href="/dashboard">Explore dashboard</a></Button> <a href="/contact">Start a project</a>
</Button>
<Button asChild variant="outline">
<a href="/dashboard">Explore dashboard</a>
</Button>
</div> </div>
</div> </div>
<Card className="bg-card/50 border-border/50"> <Card className="bg-card/50 border-border/50">
<CardHeader> <CardHeader>
<CardTitle className="flex items-center gap-2"><Rocket className="h-5 w-5" /> Mission</CardTitle> <CardTitle className="flex items-center gap-2">
<CardDescription>Turn bold ideas into useful, loved software.</CardDescription> <Rocket className="h-5 w-5" /> Mission
</CardTitle>
<CardDescription>
Turn bold ideas into useful, loved software.
</CardDescription>
</CardHeader> </CardHeader>
<CardContent className="grid sm:grid-cols-2 gap-4"> <CardContent className="grid sm:grid-cols-2 gap-4">
{capabilities.map((c) => ( {capabilities.map((c) => (
<div key={c.title}> <div key={c.title}>
<div className="font-medium mb-1">{c.title}</div> <div className="font-medium mb-1">{c.title}</div>
<ul className="text-sm text-muted-foreground list-disc list-inside space-y-1"> <ul className="text-sm text-muted-foreground list-disc list-inside space-y-1">
{c.points.map((p) => (<li key={p}>{p}</li>))} {c.points.map((p) => (
<li key={p}>{p}</li>
))}
</ul> </ul>
</div> </div>
))} ))}
@ -66,10 +119,17 @@ export default function About() {
<div className="grid md:grid-cols-4 gap-4"> <div className="grid md:grid-cols-4 gap-4">
{milestones.map((m) => ( {milestones.map((m) => (
<Card key={m.label} className="bg-card/50 border-border/50 text-center"> <Card
key={m.label}
className="bg-card/50 border-border/50 text-center"
>
<CardContent className="p-6"> <CardContent className="p-6">
<div className="text-3xl font-bold text-gradient">{m.kpi}</div> <div className="text-3xl font-bold text-gradient">
<div className="text-sm text-muted-foreground mt-1">{m.label}</div> {m.kpi}
</div>
<div className="text-sm text-muted-foreground mt-1">
{m.label}
</div>
</CardContent> </CardContent>
</Card> </Card>
))} ))}
@ -77,13 +137,23 @@ export default function About() {
<Card className="bg-card/50 border-border/50"> <Card className="bg-card/50 border-border/50">
<CardHeader> <CardHeader>
<CardTitle className="flex items-center gap-2"><Cpu className="h-5 w-5" /> Our Approach</CardTitle> <CardTitle className="flex items-center gap-2">
<CardDescription>Opinionated engineering, measurable outcomes.</CardDescription> <Cpu className="h-5 w-5" /> Our Approach
</CardTitle>
<CardDescription>
Opinionated engineering, measurable outcomes.
</CardDescription>
</CardHeader> </CardHeader>
<CardContent className="grid md:grid-cols-3 gap-6"> <CardContent className="grid md:grid-cols-3 gap-6">
{values.map((v) => ( {values.map((v) => (
<div key={v.title} className="p-4 rounded-lg border border-border/50"> <div
<div className="flex items-center gap-2 font-semibold">{v.icon}{v.title}</div> key={v.title}
className="p-4 rounded-lg border border-border/50"
>
<div className="flex items-center gap-2 font-semibold">
{v.icon}
{v.title}
</div>
<p className="text-sm text-muted-foreground mt-1">{v.desc}</p> <p className="text-sm text-muted-foreground mt-1">{v.desc}</p>
</div> </div>
))} ))}
@ -92,22 +162,32 @@ export default function About() {
<Card className="bg-card/50 border-border/50"> <Card className="bg-card/50 border-border/50">
<CardHeader> <CardHeader>
<CardTitle className="flex items-center gap-2"><GitBranch className="h-5 w-5" /> Timeline</CardTitle> <CardTitle className="flex items-center gap-2">
<CardDescription>Highlights from recent builds and launches.</CardDescription> <GitBranch className="h-5 w-5" /> Timeline
</CardTitle>
<CardDescription>
Highlights from recent builds and launches.
</CardDescription>
</CardHeader> </CardHeader>
<CardContent className="space-y-4"> <CardContent className="space-y-4">
<div className="flex items-start gap-3"> <div className="flex items-start gap-3">
<div className="h-2 w-2 rounded-full bg-aethex-400 mt-2" /> <div className="h-2 w-2 rounded-full bg-aethex-400 mt-2" />
<div> <div>
<div className="font-medium">2024 Network + Dashboard</div> <div className="font-medium">2024 Network + Dashboard</div>
<div className="text-sm text-muted-foreground">Shipped a creator-centric dashboard and social graph foundations.</div> <div className="text-sm text-muted-foreground">
Shipped a creator-centric dashboard and social graph
foundations.
</div>
</div> </div>
</div> </div>
<div className="flex items-start gap-3"> <div className="flex items-start gap-3">
<div className="h-2 w-2 rounded-full bg-aethex-400 mt-2" /> <div className="h-2 w-2 rounded-full bg-aethex-400 mt-2" />
<div> <div>
<div className="font-medium">2025 Realtime Feed</div> <div className="font-medium">2025 Realtime Feed</div>
<div className="text-sm text-muted-foreground">Vertical feed with follow, reactions, and frictionless profile onboarding.</div> <div className="text-sm text-muted-foreground">
Vertical feed with follow, reactions, and frictionless
profile onboarding.
</div>
</div> </div>
</div> </div>
</CardContent> </CardContent>

View file

@ -1,5 +1,11 @@
import Layout from "@/components/Layout"; import Layout from "@/components/Layout";
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card"; import {
Card,
CardContent,
CardHeader,
CardTitle,
CardDescription,
} from "@/components/ui/card";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input"; import { Input } from "@/components/ui/input";
import { Textarea } from "@/components/ui/textarea"; import { Textarea } from "@/components/ui/textarea";
@ -17,16 +23,27 @@ export default function Contact() {
const submit = async (e: React.FormEvent) => { const submit = async (e: React.FormEvent) => {
e.preventDefault(); e.preventDefault();
if (!name.trim() || !email.trim() || !message.trim()) { if (!name.trim() || !email.trim() || !message.trim()) {
aethexToast.error({ title: "Missing info", description: "Please fill out all fields." }); aethexToast.error({
title: "Missing info",
description: "Please fill out all fields.",
});
return; return;
} }
setIsSending(true); setIsSending(true);
try { try {
// In production, send to your backend or a function endpoint // In production, send to your backend or a function endpoint
aethexToast.success({ title: "Message sent", description: "Well get back to you within 12 business days." }); aethexToast.success({
setName(""); setEmail(""); setMessage(""); title: "Message sent",
description: "Well get back to you within 12 business days.",
});
setName("");
setEmail("");
setMessage("");
} catch (err: any) { } catch (err: any) {
aethexToast.error({ title: "Failed to send", description: err?.message || "Try again." }); aethexToast.error({
title: "Failed to send",
description: err?.message || "Try again.",
});
} finally { } finally {
setIsSending(false); setIsSending(false);
} }
@ -38,13 +55,24 @@ export default function Contact() {
<div className="container mx-auto px-4 max-w-5xl space-y-10"> <div className="container mx-auto px-4 max-w-5xl space-y-10">
<div className="grid md:grid-cols-2 gap-8 items-start"> <div className="grid md:grid-cols-2 gap-8 items-start">
<div className="space-y-3"> <div className="space-y-3">
<h1 className="text-4xl font-bold text-gradient-purple">Contact Us</h1> <h1 className="text-4xl font-bold text-gradient-purple">
<p className="text-muted-foreground">Have a project or question? We typically respond within 12 business days.</p> Contact Us
</h1>
<p className="text-muted-foreground">
Have a project or question? We typically respond within 12
business days.
</p>
<Card className="bg-card/50 border-border/50"> <Card className="bg-card/50 border-border/50">
<CardContent className="p-6 space-y-3"> <CardContent className="p-6 space-y-3">
<div className="flex items-center gap-2 text-sm"><Mail className="h-4 w-4" /> support@aethex.biz</div> <div className="flex items-center gap-2 text-sm">
<div className="flex items-center gap-2 text-sm"><Phone className="h-4 w-4" /> (530) 784-1287</div> <Mail className="h-4 w-4" /> support@aethex.biz
<div className="flex items-center gap-2 text-sm"><MessageSquare className="h-4 w-4" /> Community hub</div> </div>
<div className="flex items-center gap-2 text-sm">
<Phone className="h-4 w-4" /> (530) 784-1287
</div>
<div className="flex items-center gap-2 text-sm">
<MessageSquare className="h-4 w-4" /> Community hub
</div>
</CardContent> </CardContent>
</Card> </Card>
</div> </div>
@ -52,24 +80,49 @@ export default function Contact() {
<Card className="bg-card/50 border-border/50"> <Card className="bg-card/50 border-border/50">
<CardHeader> <CardHeader>
<CardTitle>Send a message</CardTitle> <CardTitle>Send a message</CardTitle>
<CardDescription>Tell us about your goals and timeline.</CardDescription> <CardDescription>
Tell us about your goals and timeline.
</CardDescription>
</CardHeader> </CardHeader>
<CardContent> <CardContent>
<form onSubmit={submit} className="space-y-4"> <form onSubmit={submit} className="space-y-4">
<div> <div>
<Label htmlFor="name">Name</Label> <Label htmlFor="name">Name</Label>
<Input id="name" value={name} onChange={(e) => setName(e.target.value)} placeholder="Your name" /> <Input
id="name"
value={name}
onChange={(e) => setName(e.target.value)}
placeholder="Your name"
/>
</div> </div>
<div> <div>
<Label htmlFor="email">Email</Label> <Label htmlFor="email">Email</Label>
<Input id="email" type="email" value={email} onChange={(e) => setEmail(e.target.value)} placeholder="you@example.com" /> <Input
id="email"
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder="you@example.com"
/>
</div> </div>
<div> <div>
<Label htmlFor="message">Message</Label> <Label htmlFor="message">Message</Label>
<Textarea id="message" rows={6} value={message} onChange={(e) => setMessage(e.target.value)} placeholder="What can we help you build?" /> <Textarea
id="message"
rows={6}
value={message}
onChange={(e) => setMessage(e.target.value)}
placeholder="What can we help you build?"
/>
</div> </div>
<div className="flex justify-end"> <div className="flex justify-end">
<Button type="submit" disabled={isSending} className="hover-lift">{isSending ? "Sending..." : "Send"}</Button> <Button
type="submit"
disabled={isSending}
className="hover-lift"
>
{isSending ? "Sending..." : "Send"}
</Button>
</div> </div>
</form> </form>
</CardContent> </CardContent>

View file

@ -137,7 +137,9 @@ export default function Dashboard() {
!!p?.banner_url, !!p?.banner_url,
!!(p?.website_url || p?.github_url || p?.linkedin_url || p?.twitter_url), !!(p?.website_url || p?.github_url || p?.linkedin_url || p?.twitter_url),
]; ];
const pct = Math.round((checks.filter(Boolean).length / checks.length) * 100); const pct = Math.round(
(checks.filter(Boolean).length / checks.length) * 100,
);
setProfileCompletion(pct); setProfileCompletion(pct);
}; };
@ -157,7 +159,9 @@ export default function Dashboard() {
// Check and award project-related achievements, then load achievements // Check and award project-related achievements, then load achievements
try { try {
await aethexAchievementService.checkAndAwardProjectAchievements(user!.id); await aethexAchievementService.checkAndAwardProjectAchievements(
user!.id,
);
} catch (e) { } catch (e) {
console.warn("checkAndAwardProjectAchievements failed:", e); console.warn("checkAndAwardProjectAchievements failed:", e);
} }
@ -364,7 +368,11 @@ export default function Dashboard() {
<div className="flex space-x-2"> <div className="flex space-x-2">
<Button <Button
size="sm" size="sm"
onClick={() => document.getElementById("settings")?.scrollIntoView({ behavior: "smooth" })} onClick={() =>
document
.getElementById("settings")
?.scrollIntoView({ behavior: "smooth" })
}
className="bg-orange-600 hover:bg-orange-700" className="bg-orange-600 hover:bg-orange-700"
> >
Setup Profile Setup Profile
@ -398,7 +406,9 @@ export default function Dashboard() {
</p> </p>
</div> </div>
<div className="flex items-center space-x-4"> <div className="flex items-center space-x-4">
<div className="text-sm text-muted-foreground">Profile {profileCompletion}% complete</div> <div className="text-sm text-muted-foreground">
Profile {profileCompletion}% complete
</div>
<Button variant="outline" size="sm" className="hover-lift"> <Button variant="outline" size="sm" className="hover-lift">
<Bell className="h-4 w-4 mr-2" /> <Bell className="h-4 w-4 mr-2" />
Notifications Notifications
@ -407,7 +417,11 @@ export default function Dashboard() {
variant="outline" variant="outline"
size="sm" size="sm"
className="hover-lift" className="hover-lift"
onClick={() => document.getElementById("settings")?.scrollIntoView({ behavior: "smooth" })} onClick={() =>
document
.getElementById("settings")
?.scrollIntoView({ behavior: "smooth" })
}
> >
<Settings className="h-4 w-4 mr-2" /> <Settings className="h-4 w-4 mr-2" />
Settings Settings
@ -535,16 +549,25 @@ export default function Dashboard() {
</div> </div>
{/* Settings Section */} {/* Settings Section */}
<Card className="bg-card/50 border-border/50 animate-fade-in" id="settings"> <Card
className="bg-card/50 border-border/50 animate-fade-in"
id="settings"
>
<CardHeader> <CardHeader>
<CardTitle className="text-gradient">Account Settings</CardTitle> <CardTitle className="text-gradient">
<CardDescription>Manage your profile, notifications, and privacy</CardDescription> Account Settings
</CardTitle>
<CardDescription>
Manage your profile, notifications, and privacy
</CardDescription>
</CardHeader> </CardHeader>
<CardContent> <CardContent>
<Tabs defaultValue="profile"> <Tabs defaultValue="profile">
<TabsList className="mb-4"> <TabsList className="mb-4">
<TabsTrigger value="profile">Profile</TabsTrigger> <TabsTrigger value="profile">Profile</TabsTrigger>
<TabsTrigger value="notifications">Notifications</TabsTrigger> <TabsTrigger value="notifications">
Notifications
</TabsTrigger>
<TabsTrigger value="privacy">Privacy</TabsTrigger> <TabsTrigger value="privacy">Privacy</TabsTrigger>
</TabsList> </TabsList>
@ -552,97 +575,189 @@ export default function Dashboard() {
<div className="grid grid-cols-1 md:grid-cols-2 gap-4"> <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div> <div>
<Label htmlFor="displayName">Display Name</Label> <Label htmlFor="displayName">Display Name</Label>
<Input id="displayName" value={displayName} onChange={(e) => setDisplayName(e.target.value)} /> <Input
id="displayName"
value={displayName}
onChange={(e) => setDisplayName(e.target.value)}
/>
</div> </div>
<div> <div>
<Label htmlFor="location">Location</Label> <Label htmlFor="location">Location</Label>
<Input id="location" value={locationInput} onChange={(e) => setLocationInput(e.target.value)} /> <Input
id="location"
value={locationInput}
onChange={(e) => setLocationInput(e.target.value)}
/>
</div> </div>
<div className="md:col-span-2"> <div className="md:col-span-2">
<Label htmlFor="avatar">Profile Image</Label> <Label htmlFor="avatar">Profile Image</Label>
<Input id="avatar" type="file" accept="image/*" onChange={async (e) => { <Input
const file = e.target.files?.[0]; id="avatar"
if (!file || !user) return; type="file"
const storeDataUrl = () => new Promise<string>((resolve, reject) => { accept="image/*"
const reader = new FileReader(); onChange={async (e) => {
reader.onload = () => resolve(reader.result as string); const file = e.target.files?.[0];
reader.onerror = () => reject(new Error("Failed to read file")); if (!file || !user) return;
reader.readAsDataURL(file); const storeDataUrl = () =>
}); new Promise<string>((resolve, reject) => {
try { const reader = new FileReader();
const path = `${user.id}/avatar-${Date.now()}-${file.name}`; reader.onload = () =>
const { error } = await supabase.storage.from("avatars").upload(path, file, { upsert: true }); resolve(reader.result as string);
if (error) throw error; reader.onerror = () =>
const { data } = supabase.storage.from("avatars").getPublicUrl(path); reject(new Error("Failed to read file"));
await updateProfile({ avatar_url: data.publicUrl } as any); reader.readAsDataURL(file);
computeProfileCompletion({ ...(profile as any), avatar_url: data.publicUrl }); });
aethexToast.success({ title: "Avatar updated" });
} catch (err: any) {
try { try {
const dataUrl = await storeDataUrl(); const path = `${user.id}/avatar-${Date.now()}-${file.name}`;
await updateProfile({ avatar_url: dataUrl } as any); const { error } = await supabase.storage
computeProfileCompletion({ ...(profile as any), avatar_url: dataUrl }); .from("avatars")
aethexToast.success({ title: "Avatar saved (local)" }); .upload(path, file, { upsert: true });
} catch (e:any) { if (error) throw error;
aethexToast.error({ title: "Upload failed", description: err?.message || "Unable to upload image" }); const { data } = supabase.storage
.from("avatars")
.getPublicUrl(path);
await updateProfile({
avatar_url: data.publicUrl,
} as any);
computeProfileCompletion({
...(profile as any),
avatar_url: data.publicUrl,
});
aethexToast.success({
title: "Avatar updated",
});
} catch (err: any) {
try {
const dataUrl = await storeDataUrl();
await updateProfile({
avatar_url: dataUrl,
} as any);
computeProfileCompletion({
...(profile as any),
avatar_url: dataUrl,
});
aethexToast.success({
title: "Avatar saved (local)",
});
} catch (e: any) {
aethexToast.error({
title: "Upload failed",
description:
err?.message || "Unable to upload image",
});
}
} }
} }}
}} /> />
</div> </div>
<div className="md:col-span-2"> <div className="md:col-span-2">
<Label htmlFor="banner">Banner Image</Label> <Label htmlFor="banner">Banner Image</Label>
<Input id="banner" type="file" accept="image/*" onChange={async (e) => { <Input
const file = e.target.files?.[0]; id="banner"
if (!file || !user) return; type="file"
const storeDataUrl = () => new Promise<string>((resolve, reject) => { accept="image/*"
const reader = new FileReader(); onChange={async (e) => {
reader.onload = () => resolve(reader.result as string); const file = e.target.files?.[0];
reader.onerror = () => reject(new Error("Failed to read file")); if (!file || !user) return;
reader.readAsDataURL(file); const storeDataUrl = () =>
}); new Promise<string>((resolve, reject) => {
try { const reader = new FileReader();
const path = `${user.id}/banner-${Date.now()}-${file.name}`; reader.onload = () =>
const { error } = await supabase.storage.from("banners").upload(path, file, { upsert: true }); resolve(reader.result as string);
if (error) throw error; reader.onerror = () =>
const { data } = supabase.storage.from("banners").getPublicUrl(path); reject(new Error("Failed to read file"));
await updateProfile({ banner_url: data.publicUrl } as any); reader.readAsDataURL(file);
computeProfileCompletion({ ...(profile as any), banner_url: data.publicUrl }); });
aethexToast.success({ title: "Banner updated" });
} catch (err: any) {
try { try {
const dataUrl = await storeDataUrl(); const path = `${user.id}/banner-${Date.now()}-${file.name}`;
await updateProfile({ banner_url: dataUrl } as any); const { error } = await supabase.storage
computeProfileCompletion({ ...(profile as any), banner_url: dataUrl }); .from("banners")
aethexToast.success({ title: "Banner saved (local)" }); .upload(path, file, { upsert: true });
} catch (e:any) { if (error) throw error;
aethexToast.error({ title: "Upload failed", description: err?.message || "Unable to upload image" }); const { data } = supabase.storage
.from("banners")
.getPublicUrl(path);
await updateProfile({
banner_url: data.publicUrl,
} as any);
computeProfileCompletion({
...(profile as any),
banner_url: data.publicUrl,
});
aethexToast.success({
title: "Banner updated",
});
} catch (err: any) {
try {
const dataUrl = await storeDataUrl();
await updateProfile({
banner_url: dataUrl,
} as any);
computeProfileCompletion({
...(profile as any),
banner_url: dataUrl,
});
aethexToast.success({
title: "Banner saved (local)",
});
} catch (e: any) {
aethexToast.error({
title: "Upload failed",
description:
err?.message || "Unable to upload image",
});
}
} }
} }}
}} /> />
</div> </div>
<div className="md:col-span-2"> <div className="md:col-span-2">
<Label htmlFor="bio">Bio</Label> <Label htmlFor="bio">Bio</Label>
<Textarea id="bio" value={bio} onChange={(e) => setBio(e.target.value)} /> <Textarea
id="bio"
value={bio}
onChange={(e) => setBio(e.target.value)}
/>
</div> </div>
<div> <div>
<Label htmlFor="website">Website</Label> <Label htmlFor="website">Website</Label>
<Input id="website" value={website} onChange={(e) => setWebsite(e.target.value)} /> <Input
id="website"
value={website}
onChange={(e) => setWebsite(e.target.value)}
/>
</div> </div>
<div> <div>
<Label htmlFor="linkedin">LinkedIn URL</Label> <Label htmlFor="linkedin">LinkedIn URL</Label>
<Input id="linkedin" value={linkedin} onChange={(e) => setLinkedin(e.target.value)} /> <Input
id="linkedin"
value={linkedin}
onChange={(e) => setLinkedin(e.target.value)}
/>
</div> </div>
<div> <div>
<Label htmlFor="github">GitHub URL</Label> <Label htmlFor="github">GitHub URL</Label>
<Input id="github" value={github} onChange={(e) => setGithub(e.target.value)} /> <Input
id="github"
value={github}
onChange={(e) => setGithub(e.target.value)}
/>
</div> </div>
<div> <div>
<Label htmlFor="twitter">Twitter URL</Label> <Label htmlFor="twitter">Twitter URL</Label>
<Input id="twitter" value={twitter} onChange={(e) => setTwitter(e.target.value)} /> <Input
id="twitter"
value={twitter}
onChange={(e) => setTwitter(e.target.value)}
/>
</div> </div>
</div> </div>
<div className="flex justify-end"> <div className="flex justify-end">
<Button onClick={saveProfile} disabled={savingProfile} className="hover-lift"> <Button
onClick={saveProfile}
disabled={savingProfile}
className="hover-lift"
>
{savingProfile ? "Saving..." : "Save Changes"} {savingProfile ? "Saving..." : "Save Changes"}
</Button> </Button>
</div> </div>
@ -652,14 +767,18 @@ export default function Dashboard() {
<div className="flex items-center justify-between border rounded-lg p-3"> <div className="flex items-center justify-between border rounded-lg p-3">
<div> <div>
<div className="font-medium">Email notifications</div> <div className="font-medium">Email notifications</div>
<div className="text-sm text-muted-foreground">Get updates in your inbox</div> <div className="text-sm text-muted-foreground">
Get updates in your inbox
</div>
</div> </div>
<Switch defaultChecked /> <Switch defaultChecked />
</div> </div>
<div className="flex items-center justify-between border rounded-lg p-3"> <div className="flex items-center justify-between border rounded-lg p-3">
<div> <div>
<div className="font-medium">Push notifications</div> <div className="font-medium">Push notifications</div>
<div className="text-sm text-muted-foreground">Receive alerts in the app</div> <div className="text-sm text-muted-foreground">
Receive alerts in the app
</div>
</div> </div>
<Switch /> <Switch />
</div> </div>
@ -669,14 +788,18 @@ export default function Dashboard() {
<div className="flex items-center justify-between border rounded-lg p-3"> <div className="flex items-center justify-between border rounded-lg p-3">
<div> <div>
<div className="font-medium">Public profile</div> <div className="font-medium">Public profile</div>
<div className="text-sm text-muted-foreground">Show your profile to everyone</div> <div className="text-sm text-muted-foreground">
Show your profile to everyone
</div>
</div> </div>
<Switch defaultChecked /> <Switch defaultChecked />
</div> </div>
<div className="flex items-center justify-between border rounded-lg p-3"> <div className="flex items-center justify-between border rounded-lg p-3">
<div> <div>
<div className="font-medium">Show email</div> <div className="font-medium">Show email</div>
<div className="text-sm text-muted-foreground">Display email on your profile</div> <div className="text-sm text-muted-foreground">
Display email on your profile
</div>
</div> </div>
<Switch /> <Switch />
</div> </div>

View file

@ -7,7 +7,15 @@ import { Button } from "@/components/ui/button";
import { Card, CardContent } from "@/components/ui/card"; import { Card, CardContent } from "@/components/ui/card";
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"; import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
import { aethexSocialService } from "@/lib/aethex-social-service"; import { aethexSocialService } from "@/lib/aethex-social-service";
import { Heart, MessageCircle, Share2, UserPlus, UserCheck, Volume2, VolumeX } from "lucide-react"; import {
Heart,
MessageCircle,
Share2,
UserPlus,
UserCheck,
Volume2,
VolumeX,
} from "lucide-react";
interface FeedItem { interface FeedItem {
id: string; id: string;
@ -45,7 +53,11 @@ export default function Feed() {
authorAvatar: r.avatar_url, authorAvatar: r.avatar_url,
caption: r.bio || "", caption: r.bio || "",
mediaUrl: r.banner_url || r.avatar_url || null, mediaUrl: r.banner_url || r.avatar_url || null,
mediaType: r.banner_url?.match(/\.(mp4|webm|mov)(\?.*)?$/i) ? "video" : r.banner_url || r.avatar_url ? "image" : "none", mediaType: r.banner_url?.match(/\.(mp4|webm|mov)(\?.*)?$/i)
? "video"
: r.banner_url || r.avatar_url
? "image"
: "none",
likes: Math.floor(Math.random() * 200) + 5, likes: Math.floor(Math.random() * 200) + 5,
comments: Math.floor(Math.random() * 30), comments: Math.floor(Math.random() * 30),
})); }));
@ -72,7 +84,11 @@ export default function Feed() {
if (!user && !loading) return <Navigate to="/login" replace />; if (!user && !loading) return <Navigate to="/login" replace />;
if (loading || isLoading) { if (loading || isLoading) {
return ( return (
<LoadingScreen message="Loading your feed..." showProgress duration={1000} /> <LoadingScreen
message="Loading your feed..."
showProgress
duration={1000}
/>
); );
} }
@ -81,17 +97,33 @@ export default function Feed() {
<div className="min-h-screen bg-aethex-gradient"> <div className="min-h-screen bg-aethex-gradient">
<div className="h-[calc(100vh-64px)] overflow-y-auto snap-y snap-mandatory no-scrollbar"> <div className="h-[calc(100vh-64px)] overflow-y-auto snap-y snap-mandatory no-scrollbar">
{items.length === 0 && ( {items.length === 0 && (
<div className="flex items-center justify-center h-full text-muted-foreground">No posts yet. Follow people to populate your feed.</div> <div className="flex items-center justify-center h-full text-muted-foreground">
No posts yet. Follow people to populate your feed.
</div>
)} )}
{items.map((item) => ( {items.map((item) => (
<section key={item.id} className="snap-start h-[calc(100vh-64px)] relative flex items-center justify-center"> <section
key={item.id}
className="snap-start h-[calc(100vh-64px)] relative flex items-center justify-center"
>
<Card className="w-full h-full bg-black/60 border-border/30 overflow-hidden"> <Card className="w-full h-full bg-black/60 border-border/30 overflow-hidden">
<CardContent className="w-full h-full p-0 relative"> <CardContent className="w-full h-full p-0 relative">
{/* Media */} {/* Media */}
{item.mediaType === "video" && item.mediaUrl ? ( {item.mediaType === "video" && item.mediaUrl ? (
<video src={item.mediaUrl} className="w-full h-full object-cover" autoPlay loop muted={muted} playsInline /> <video
src={item.mediaUrl}
className="w-full h-full object-cover"
autoPlay
loop
muted={muted}
playsInline
/>
) : item.mediaType === "image" && item.mediaUrl ? ( ) : item.mediaType === "image" && item.mediaUrl ? (
<img src={item.mediaUrl} alt={item.caption || item.authorName} className="w-full h-full object-cover" /> <img
src={item.mediaUrl}
alt={item.caption || item.authorName}
className="w-full h-full object-cover"
/>
) : ( ) : (
<div className="w-full h-full bg-gradient-to-br from-aethex-500/20 to-neon-blue/20" /> <div className="w-full h-full bg-gradient-to-br from-aethex-500/20 to-neon-blue/20" />
)} )}
@ -101,16 +133,37 @@ export default function Feed() {
{/* Right rail actions */} {/* Right rail actions */}
<div className="absolute right-4 bottom-24 flex flex-col items-center gap-4"> <div className="absolute right-4 bottom-24 flex flex-col items-center gap-4">
<Button size="icon" variant="secondary" className="rounded-full bg-white/20 hover:bg-white/30" onClick={() => setMuted((m) => !m)}> <Button
{muted ? <VolumeX className="h-5 w-5" /> : <Volume2 className="h-5 w-5" />} size="icon"
variant="secondary"
className="rounded-full bg-white/20 hover:bg-white/30"
onClick={() => setMuted((m) => !m)}
>
{muted ? (
<VolumeX className="h-5 w-5" />
) : (
<Volume2 className="h-5 w-5" />
)}
</Button> </Button>
<Button size="icon" variant="secondary" className="rounded-full bg-white/20 hover:bg-white/30"> <Button
size="icon"
variant="secondary"
className="rounded-full bg-white/20 hover:bg-white/30"
>
<Heart className="h-5 w-5" /> <Heart className="h-5 w-5" />
</Button> </Button>
<Button size="icon" variant="secondary" className="rounded-full bg-white/20 hover:bg-white/30"> <Button
size="icon"
variant="secondary"
className="rounded-full bg-white/20 hover:bg-white/30"
>
<MessageCircle className="h-5 w-5" /> <MessageCircle className="h-5 w-5" />
</Button> </Button>
<Button size="icon" variant="secondary" className="rounded-full bg-white/20 hover:bg-white/30"> <Button
size="icon"
variant="secondary"
className="rounded-full bg-white/20 hover:bg-white/30"
>
<Share2 className="h-5 w-5" /> <Share2 className="h-5 w-5" />
</Button> </Button>
</div> </div>
@ -120,15 +173,37 @@ export default function Feed() {
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<Avatar className="h-10 w-10"> <Avatar className="h-10 w-10">
<AvatarImage src={item.authorAvatar || undefined} /> <AvatarImage src={item.authorAvatar || undefined} />
<AvatarFallback>{item.authorName[0] || "U"}</AvatarFallback> <AvatarFallback>
{item.authorName[0] || "U"}
</AvatarFallback>
</Avatar> </Avatar>
<div> <div>
<div className="font-semibold text-white">{item.authorName}</div> <div className="font-semibold text-white">
{item.caption && <div className="text-xs text-white/80 max-w-[60vw] line-clamp-2">{item.caption}</div>} {item.authorName}
</div>
{item.caption && (
<div className="text-xs text-white/80 max-w-[60vw] line-clamp-2">
{item.caption}
</div>
)}
</div> </div>
</div> </div>
<Button size="sm" variant={isFollowingAuthor(item.authorId) ? "outline" : "default"} onClick={() => toggleFollow(item.authorId)}> <Button
{isFollowingAuthor(item.authorId) ? (<span className="flex items-center gap-1"><UserCheck className="h-4 w-4" /> Following</span>) : (<span className="flex items-center gap-1"><UserPlus className="h-4 w-4" /> Follow</span>)} size="sm"
variant={
isFollowingAuthor(item.authorId) ? "outline" : "default"
}
onClick={() => toggleFollow(item.authorId)}
>
{isFollowingAuthor(item.authorId) ? (
<span className="flex items-center gap-1">
<UserCheck className="h-4 w-4" /> Following
</span>
) : (
<span className="flex items-center gap-1">
<UserPlus className="h-4 w-4" /> Follow
</span>
)}
</Button> </Button>
</div> </div>
</CardContent> </CardContent>

View file

@ -8,7 +8,10 @@ import { Textarea } from "@/components/ui/textarea";
import LoadingScreen from "@/components/LoadingScreen"; import LoadingScreen from "@/components/LoadingScreen";
import { useAuth } from "@/contexts/AuthContext"; import { useAuth } from "@/contexts/AuthContext";
import { aethexToast } from "@/lib/aethex-toast"; import { aethexToast } from "@/lib/aethex-toast";
import { aethexProjectService, aethexAchievementService } from "@/lib/aethex-database-adapter"; import {
aethexProjectService,
aethexAchievementService,
} from "@/lib/aethex-database-adapter";
export default function ProjectsNew() { export default function ProjectsNew() {
const navigate = useNavigate(); const navigate = useNavigate();
@ -18,7 +21,9 @@ export default function ProjectsNew() {
const [technologies, setTechnologies] = useState(""); const [technologies, setTechnologies] = useState("");
const [githubUrl, setGithubUrl] = useState(""); const [githubUrl, setGithubUrl] = useState("");
const [liveUrl, setLiveUrl] = useState(""); const [liveUrl, setLiveUrl] = useState("");
const [status, setStatus] = useState<"planning" | "in_progress" | "completed" | "on_hold">("planning"); const [status, setStatus] = useState<
"planning" | "in_progress" | "completed" | "on_hold"
>("planning");
const [isSubmitting, setIsSubmitting] = useState(false); const [isSubmitting, setIsSubmitting] = useState(false);
if (!user) { if (!user) {
@ -34,7 +39,10 @@ export default function ProjectsNew() {
const handleSubmit = async (e: any) => { const handleSubmit = async (e: any) => {
e.preventDefault(); e.preventDefault();
if (!title.trim()) { if (!title.trim()) {
aethexToast.error({ title: "Title required", description: "Please provide a project title." }); aethexToast.error({
title: "Title required",
description: "Please provide a project title.",
});
return; return;
} }
@ -58,16 +66,24 @@ export default function ProjectsNew() {
if (project) { if (project) {
try { try {
await aethexAchievementService.checkAndAwardProjectAchievements(user.id); await aethexAchievementService.checkAndAwardProjectAchievements(
user.id,
);
} catch {} } catch {}
aethexToast.success({ title: "Project created", description: "Your project has been created." }); aethexToast.success({
title: "Project created",
description: "Your project has been created.",
});
navigate("/dashboard"); navigate("/dashboard");
} else { } else {
throw new Error("Project creation failed"); throw new Error("Project creation failed");
} }
} catch (err: any) { } catch (err: any) {
console.error("Error creating project:", err); console.error("Error creating project:", err);
aethexToast.error({ title: "Failed to create project", description: err?.message || "Please try again." }); aethexToast.error({
title: "Failed to create project",
description: err?.message || "Please try again.",
});
} finally { } finally {
setIsSubmitting(false); setIsSubmitting(false);
} }
@ -79,37 +95,72 @@ export default function ProjectsNew() {
<div className="container mx-auto px-4 max-w-3xl"> <div className="container mx-auto px-4 max-w-3xl">
<h1 className="text-2xl font-bold mb-4">Start a New Project</h1> <h1 className="text-2xl font-bold mb-4">Start a New Project</h1>
<form onSubmit={handleSubmit} className="space-y-4 bg-card/50 border-border/50 p-6 rounded-lg"> <form
onSubmit={handleSubmit}
className="space-y-4 bg-card/50 border-border/50 p-6 rounded-lg"
>
<div> <div>
<Label htmlFor="title">Project Title</Label> <Label htmlFor="title">Project Title</Label>
<Input id="title" value={title} onChange={(e) => setTitle(e.target.value)} placeholder="e.g. Decentralized Chat App" /> <Input
id="title"
value={title}
onChange={(e) => setTitle(e.target.value)}
placeholder="e.g. Decentralized Chat App"
/>
</div> </div>
<div> <div>
<Label htmlFor="description">Description</Label> <Label htmlFor="description">Description</Label>
<Textarea id="description" value={description} onChange={(e) => setDescription(e.target.value)} placeholder="Short description of the project" /> <Textarea
id="description"
value={description}
onChange={(e) => setDescription(e.target.value)}
placeholder="Short description of the project"
/>
</div> </div>
<div> <div>
<Label htmlFor="technologies">Technologies (comma separated)</Label> <Label htmlFor="technologies">
<Input id="technologies" value={technologies} onChange={(e) => setTechnologies(e.target.value)} placeholder="React, Node.js, Supabase, Typescript" /> Technologies (comma separated)
</Label>
<Input
id="technologies"
value={technologies}
onChange={(e) => setTechnologies(e.target.value)}
placeholder="React, Node.js, Supabase, Typescript"
/>
</div> </div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4"> <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div> <div>
<Label htmlFor="github">GitHub URL</Label> <Label htmlFor="github">GitHub URL</Label>
<Input id="github" value={githubUrl} onChange={(e) => setGithubUrl(e.target.value)} placeholder="https://github.com/your/repo" /> <Input
id="github"
value={githubUrl}
onChange={(e) => setGithubUrl(e.target.value)}
placeholder="https://github.com/your/repo"
/>
</div> </div>
<div> <div>
<Label htmlFor="live">Live URL</Label> <Label htmlFor="live">Live URL</Label>
<Input id="live" value={liveUrl} onChange={(e) => setLiveUrl(e.target.value)} placeholder="https://example.com" /> <Input
id="live"
value={liveUrl}
onChange={(e) => setLiveUrl(e.target.value)}
placeholder="https://example.com"
/>
</div> </div>
</div> </div>
<div> <div>
<Label htmlFor="status">Status</Label> <Label htmlFor="status">Status</Label>
<select id="status" value={status} onChange={(e) => setStatus(e.target.value as any)} className="w-full p-2 rounded border border-border/30 bg-background"> <select
id="status"
value={status}
onChange={(e) => setStatus(e.target.value as any)}
className="w-full p-2 rounded border border-border/30 bg-background"
>
<option value="planning">Planning</option> <option value="planning">Planning</option>
<option value="in_progress">In Progress</option> <option value="in_progress">In Progress</option>
<option value="completed">Completed</option> <option value="completed">Completed</option>
@ -118,10 +169,18 @@ export default function ProjectsNew() {
</div> </div>
<div className="flex items-center space-x-3"> <div className="flex items-center space-x-3">
<Button type="submit" disabled={isSubmitting} className="hover-lift"> <Button
type="submit"
disabled={isSubmitting}
className="hover-lift"
>
{isSubmitting ? "Creating..." : "Create Project"} {isSubmitting ? "Creating..." : "Create Project"}
</Button> </Button>
<Button type="button" variant="outline" onClick={() => navigate(-1)}> <Button
type="button"
variant="outline"
onClick={() => navigate(-1)}
>
Cancel Cancel
</Button> </Button>
</div> </div>

View file

@ -5,12 +5,21 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>AeThex — Developer Platform, Projects, Community</title> <title>AeThex — Developer Platform, Projects, Community</title>
<meta name="description" content="AeThex: an advanced development platform and community for builders. Collaborate on projects, learn, and ship innovation." /> <meta
<meta name="keywords" content="AeThex, developer platform, projects, community, mentorship, research labs, consulting, tutorials" /> name="description"
content="AeThex: an advanced development platform and community for builders. Collaborate on projects, learn, and ship innovation."
/>
<meta
name="keywords"
content="AeThex, developer platform, projects, community, mentorship, research labs, consulting, tutorials"
/>
<meta name="application-name" content="AeThex" /> <meta name="application-name" content="AeThex" />
<meta name="theme-color" content="#0a0aff" /> <meta name="theme-color" content="#0a0aff" />
<meta name="color-scheme" content="dark light" /> <meta name="color-scheme" content="dark light" />
<meta name="robots" content="index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1" /> <meta
name="robots"
content="index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1"
/>
<meta name="googlebot" content="index, follow" /> <meta name="googlebot" content="index, follow" />
<!-- Geo/Audience --> <!-- Geo/Audience -->
@ -22,30 +31,66 @@
<link rel="canonical" href="/" /> <link rel="canonical" href="/" />
<!-- Favicons / Icons --> <!-- Favicons / Icons -->
<link rel="icon" type="image/png" sizes="32x32" href="https://cdn.builder.io/api/v1/image/assets%2Ffc53d607e21d497595ac97e0637001a1%2F3979ec9a8a28471d900a80e94e2c45fe?format=png&width=32" /> <link
<link rel="icon" type="image/png" sizes="192x192" href="https://cdn.builder.io/api/v1/image/assets%2Ffc53d607e21d497595ac97e0637001a1%2F3979ec9a8a28471d900a80e94e2c45fe?format=png&width=192" /> rel="icon"
<link rel="apple-touch-icon" sizes="180x180" href="https://cdn.builder.io/api/v1/image/assets%2Ffc53d607e21d497595ac97e0637001a1%2F3979ec9a8a28471d900a80e94e2c45fe?format=png&width=180" /> type="image/png"
sizes="32x32"
href="https://cdn.builder.io/api/v1/image/assets%2Ffc53d607e21d497595ac97e0637001a1%2F3979ec9a8a28471d900a80e94e2c45fe?format=png&width=32"
/>
<link
rel="icon"
type="image/png"
sizes="192x192"
href="https://cdn.builder.io/api/v1/image/assets%2Ffc53d607e21d497595ac97e0637001a1%2F3979ec9a8a28471d900a80e94e2c45fe?format=png&width=192"
/>
<link
rel="apple-touch-icon"
sizes="180x180"
href="https://cdn.builder.io/api/v1/image/assets%2Ffc53d607e21d497595ac97e0637001a1%2F3979ec9a8a28471d900a80e94e2c45fe?format=png&width=180"
/>
<link rel="manifest" href="/site.webmanifest" /> <link rel="manifest" href="/site.webmanifest" />
<meta name="msapplication-TileColor" content="#0a0aff" /> <meta name="msapplication-TileColor" content="#0a0aff" />
<!-- Open Graph --> <!-- Open Graph -->
<meta property="og:site_name" content="AeThex" /> <meta property="og:site_name" content="AeThex" />
<meta property="og:type" content="website" /> <meta property="og:type" content="website" />
<meta property="og:title" content="AeThex — Developer Platform, Projects, Community" /> <meta
<meta property="og:description" content="Join AeThex to build, learn, and connect. Tutorials, mentorship, research labs, and a thriving developer community." /> property="og:title"
<meta property="og:image" content="https://cdn.builder.io/api/v1/image/assets%2Ffc53d607e21d497595ac97e0637001a1%2F3979ec9a8a28471d900a80e94e2c45fe?format=png&width=1200" /> content="AeThex — Developer Platform, Projects, Community"
/>
<meta
property="og:description"
content="Join AeThex to build, learn, and connect. Tutorials, mentorship, research labs, and a thriving developer community."
/>
<meta
property="og:image"
content="https://cdn.builder.io/api/v1/image/assets%2Ffc53d607e21d497595ac97e0637001a1%2F3979ec9a8a28471d900a80e94e2c45fe?format=png&width=1200"
/>
<meta property="og:image:width" content="1200" /> <meta property="og:image:width" content="1200" />
<meta property="og:image:height" content="630" /> <meta property="og:image:height" content="630" />
<meta property="og:url" content="/" /> <meta property="og:url" content="/" />
<!-- Twitter --> <!-- Twitter -->
<meta name="twitter:card" content="summary_large_image" /> <meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:title" content="AeThex — Developer Platform, Projects, Community" /> <meta
<meta name="twitter:description" content="Build and innovate with AeThex. Projects, mentorship, research labs, and tutorials for modern developers." /> name="twitter:title"
<meta name="twitter:image" content="https://cdn.builder.io/api/v1/image/assets%2Ffc53d607e21d497595ac97e0637001a1%2F3979ec9a8a28471d900a80e94e2c45fe?format=png&width=1200" /> content="AeThex — Developer Platform, Projects, Community"
/>
<meta
name="twitter:description"
content="Build and innovate with AeThex. Projects, mentorship, research labs, and tutorials for modern developers."
/>
<meta
name="twitter:image"
content="https://cdn.builder.io/api/v1/image/assets%2Ffc53d607e21d497595ac97e0637001a1%2F3979ec9a8a28471d900a80e94e2c45fe?format=png&width=1200"
/>
<!-- Preconnects --> <!-- Preconnects -->
<link rel="preconnect" href="https://kmdeisowhtsalsekkzqd.supabase.co" crossorigin /> <link
rel="preconnect"
href="https://kmdeisowhtsalsekkzqd.supabase.co"
crossorigin
/>
<link rel="preconnect" href="https://cdn.builder.io" crossorigin /> <link rel="preconnect" href="https://cdn.builder.io" crossorigin />
<!-- Structured Data + dynamic canonical/og:url --> <!-- Structured Data + dynamic canonical/og:url -->
@ -53,8 +98,8 @@
(function () { (function () {
var origin = location.origin; var origin = location.origin;
function addJSONLD(obj) { function addJSONLD(obj) {
var s = document.createElement('script'); var s = document.createElement("script");
s.type = 'application/ld+json'; s.type = "application/ld+json";
s.text = JSON.stringify(obj); s.text = JSON.stringify(obj);
document.head.appendChild(s); document.head.appendChild(s);
} }
@ -62,47 +107,64 @@
addJSONLD({ addJSONLD({
"@context": "https://schema.org", "@context": "https://schema.org",
"@type": "Organization", "@type": "Organization",
"name": "AeThex", name: "AeThex",
"url": origin, url: origin,
"logo": "https://cdn.builder.io/api/v1/image/assets%2Ffc53d607e21d497595ac97e0637001a1%2F3979ec9a8a28471d900a80e94e2c45fe?format=png&width=512", logo: "https://cdn.builder.io/api/v1/image/assets%2Ffc53d607e21d497595ac97e0637001a1%2F3979ec9a8a28471d900a80e94e2c45fe?format=png&width=512",
"areaServed": "Worldwide" areaServed: "Worldwide",
}); });
// Website // Website
addJSONLD({ addJSONLD({
"@context": "https://schema.org", "@context": "https://schema.org",
"@type": "WebSite", "@type": "WebSite",
"name": "AeThex", name: "AeThex",
"url": origin url: origin,
}); });
// FAQ for AEO // FAQ for AEO
addJSONLD({ addJSONLD({
"@context": "https://schema.org", "@context": "https://schema.org",
"@type": "FAQPage", "@type": "FAQPage",
"mainEntity": [ mainEntity: [
{ {
"@type": "Question", "@type": "Question",
"name": "What is AeThex?", name: "What is AeThex?",
"acceptedAnswer": {"@type": "Answer", "text": "AeThex is an advanced development platform and community where developers collaborate on projects, learn through tutorials, and access mentorship and research labs."} acceptedAnswer: {
"@type": "Answer",
text: "AeThex is an advanced development platform and community where developers collaborate on projects, learn through tutorials, and access mentorship and research labs.",
},
}, },
{ {
"@type": "Question", "@type": "Question",
"name": "How do I get started?", name: "How do I get started?",
"acceptedAnswer": {"@type": "Answer", "text": "Visit the Get Started and Onboarding flows to create your profile and join projects."} acceptedAnswer: {
"@type": "Answer",
text: "Visit the Get Started and Onboarding flows to create your profile and join projects.",
},
}, },
{ {
"@type": "Question", "@type": "Question",
"name": "Does AeThex offer mentorship programs?", name: "Does AeThex offer mentorship programs?",
"acceptedAnswer": {"@type": "Answer", "text": "Yes. AeThex provides mentorship programs and a community feed to help you grow and connect."} acceptedAnswer: {
} "@type": "Answer",
] text: "Yes. AeThex provides mentorship programs and a community feed to help you grow and connect.",
},
},
],
}); });
// Set canonical and og:url to the current URL // Set canonical and og:url to the current URL
var link = document.querySelector('link[rel="canonical"]'); var link = document.querySelector('link[rel="canonical"]');
if (!link) { link = document.createElement('link'); link.rel = 'canonical'; document.head.appendChild(link); } if (!link) {
link = document.createElement("link");
link.rel = "canonical";
document.head.appendChild(link);
}
link.href = location.href; link.href = location.href;
var ogUrl = document.querySelector('meta[property="og:url"]'); var ogUrl = document.querySelector('meta[property="og:url"]');
if (!ogUrl) { ogUrl = document.createElement('meta'); ogUrl.setAttribute('property','og:url'); document.head.appendChild(ogUrl); } if (!ogUrl) {
ogUrl.setAttribute('content', location.href); ogUrl = document.createElement("meta");
ogUrl.setAttribute("property", "og:url");
document.head.appendChild(ogUrl);
}
ogUrl.setAttribute("content", location.href);
})(); })();
</script> </script>
</head> </head>