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="/admin" element={<Admin />} />
<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="/profile"

View file

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

View file

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

View file

@ -1,20 +1,59 @@
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 { Button } from "@/components/ui/button";
import { Rocket, Cpu, Users, Shield, Zap, GitBranch } from "lucide-react";
export default function About() {
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: <Users className="h-5 w-5" />, title: "Partnership", desc: "We win with our customers, not at their expense." },
{
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: <Users className="h-5 w-5" />,
title: "Partnership",
desc: "We win with our customers, not at their expense.",
},
];
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: "Advisory & Enablement", points: ["Technical Strategy", "Codebase Modernization", "Team Upskilling"] },
{
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: "Advisory & Enablement",
points: [
"Technical Strategy",
"Codebase Modernization",
"Team Upskilling",
],
},
];
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="grid md:grid-cols-2 gap-8 items-start">
<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">
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>
<div className="flex flex-wrap gap-2">
<Badge variant="outline">TypeScript</Badge>
@ -42,21 +85,31 @@ export default function About() {
<Badge variant="outline">Edge</Badge>
</div>
<div className="flex gap-3 pt-2">
<Button asChild><a href="/contact">Start a project</a></Button>
<Button asChild variant="outline"><a href="/dashboard">Explore dashboard</a></Button>
<Button asChild>
<a href="/contact">Start a project</a>
</Button>
<Button asChild variant="outline">
<a href="/dashboard">Explore dashboard</a>
</Button>
</div>
</div>
<Card className="bg-card/50 border-border/50">
<CardHeader>
<CardTitle className="flex items-center gap-2"><Rocket className="h-5 w-5" /> Mission</CardTitle>
<CardDescription>Turn bold ideas into useful, loved software.</CardDescription>
<CardTitle className="flex items-center gap-2">
<Rocket className="h-5 w-5" /> Mission
</CardTitle>
<CardDescription>
Turn bold ideas into useful, loved software.
</CardDescription>
</CardHeader>
<CardContent className="grid sm:grid-cols-2 gap-4">
{capabilities.map((c) => (
<div key={c.title}>
<div className="font-medium mb-1">{c.title}</div>
<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>
</div>
))}
@ -66,10 +119,17 @@ export default function About() {
<div className="grid md:grid-cols-4 gap-4">
{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">
<div className="text-3xl font-bold text-gradient">{m.kpi}</div>
<div className="text-sm text-muted-foreground mt-1">{m.label}</div>
<div className="text-3xl font-bold text-gradient">
{m.kpi}
</div>
<div className="text-sm text-muted-foreground mt-1">
{m.label}
</div>
</CardContent>
</Card>
))}
@ -77,13 +137,23 @@ export default function About() {
<Card className="bg-card/50 border-border/50">
<CardHeader>
<CardTitle className="flex items-center gap-2"><Cpu className="h-5 w-5" /> Our Approach</CardTitle>
<CardDescription>Opinionated engineering, measurable outcomes.</CardDescription>
<CardTitle className="flex items-center gap-2">
<Cpu className="h-5 w-5" /> Our Approach
</CardTitle>
<CardDescription>
Opinionated engineering, measurable outcomes.
</CardDescription>
</CardHeader>
<CardContent className="grid md:grid-cols-3 gap-6">
{values.map((v) => (
<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>
<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>
</div>
))}
@ -92,22 +162,32 @@ export default function About() {
<Card className="bg-card/50 border-border/50">
<CardHeader>
<CardTitle className="flex items-center gap-2"><GitBranch className="h-5 w-5" /> Timeline</CardTitle>
<CardDescription>Highlights from recent builds and launches.</CardDescription>
<CardTitle className="flex items-center gap-2">
<GitBranch className="h-5 w-5" /> Timeline
</CardTitle>
<CardDescription>
Highlights from recent builds and launches.
</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
<div className="flex items-start gap-3">
<div className="h-2 w-2 rounded-full bg-aethex-400 mt-2" />
<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 className="flex items-start gap-3">
<div className="h-2 w-2 rounded-full bg-aethex-400 mt-2" />
<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>
</CardContent>

View file

@ -1,5 +1,11 @@
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 { Input } from "@/components/ui/input";
import { Textarea } from "@/components/ui/textarea";
@ -17,16 +23,27 @@ export default function Contact() {
const submit = async (e: React.FormEvent) => {
e.preventDefault();
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;
}
setIsSending(true);
try {
// 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." });
setName(""); setEmail(""); setMessage("");
aethexToast.success({
title: "Message sent",
description: "Well get back to you within 12 business days.",
});
setName("");
setEmail("");
setMessage("");
} 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 {
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="grid md:grid-cols-2 gap-8 items-start">
<div className="space-y-3">
<h1 className="text-4xl font-bold text-gradient-purple">Contact Us</h1>
<p className="text-muted-foreground">Have a project or question? We typically respond within 12 business days.</p>
<h1 className="text-4xl font-bold text-gradient-purple">
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">
<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"><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>
<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">
<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>
</Card>
</div>
@ -52,24 +80,49 @@ export default function Contact() {
<Card className="bg-card/50 border-border/50">
<CardHeader>
<CardTitle>Send a message</CardTitle>
<CardDescription>Tell us about your goals and timeline.</CardDescription>
<CardDescription>
Tell us about your goals and timeline.
</CardDescription>
</CardHeader>
<CardContent>
<form onSubmit={submit} className="space-y-4">
<div>
<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>
<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>
<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 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>
</form>
</CardContent>

View file

@ -137,7 +137,9 @@ export default function Dashboard() {
!!p?.banner_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);
};
@ -157,7 +159,9 @@ export default function Dashboard() {
// Check and award project-related achievements, then load achievements
try {
await aethexAchievementService.checkAndAwardProjectAchievements(user!.id);
await aethexAchievementService.checkAndAwardProjectAchievements(
user!.id,
);
} catch (e) {
console.warn("checkAndAwardProjectAchievements failed:", e);
}
@ -364,7 +368,11 @@ export default function Dashboard() {
<div className="flex space-x-2">
<Button
size="sm"
onClick={() => document.getElementById("settings")?.scrollIntoView({ behavior: "smooth" })}
onClick={() =>
document
.getElementById("settings")
?.scrollIntoView({ behavior: "smooth" })
}
className="bg-orange-600 hover:bg-orange-700"
>
Setup Profile
@ -398,7 +406,9 @@ export default function Dashboard() {
</p>
</div>
<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">
<Bell className="h-4 w-4 mr-2" />
Notifications
@ -407,7 +417,11 @@ export default function Dashboard() {
variant="outline"
size="sm"
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
@ -535,16 +549,25 @@ export default function Dashboard() {
</div>
{/* 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>
<CardTitle className="text-gradient">Account Settings</CardTitle>
<CardDescription>Manage your profile, notifications, and privacy</CardDescription>
<CardTitle className="text-gradient">
Account Settings
</CardTitle>
<CardDescription>
Manage your profile, notifications, and privacy
</CardDescription>
</CardHeader>
<CardContent>
<Tabs defaultValue="profile">
<TabsList className="mb-4">
<TabsTrigger value="profile">Profile</TabsTrigger>
<TabsTrigger value="notifications">Notifications</TabsTrigger>
<TabsTrigger value="notifications">
Notifications
</TabsTrigger>
<TabsTrigger value="privacy">Privacy</TabsTrigger>
</TabsList>
@ -552,97 +575,189 @@ export default function Dashboard() {
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<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>
<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 className="md:col-span-2">
<Label htmlFor="avatar">Profile Image</Label>
<Input id="avatar" type="file" accept="image/*" onChange={async (e) => {
const file = e.target.files?.[0];
if (!file || !user) return;
const storeDataUrl = () => new Promise<string>((resolve, reject) => {
const reader = new FileReader();
reader.onload = () => resolve(reader.result as string);
reader.onerror = () => reject(new Error("Failed to read file"));
reader.readAsDataURL(file);
});
try {
const path = `${user.id}/avatar-${Date.now()}-${file.name}`;
const { error } = await supabase.storage.from("avatars").upload(path, file, { upsert: true });
if (error) throw error;
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) {
<Input
id="avatar"
type="file"
accept="image/*"
onChange={async (e) => {
const file = e.target.files?.[0];
if (!file || !user) return;
const storeDataUrl = () =>
new Promise<string>((resolve, reject) => {
const reader = new FileReader();
reader.onload = () =>
resolve(reader.result as string);
reader.onerror = () =>
reject(new Error("Failed to read file"));
reader.readAsDataURL(file);
});
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" });
const path = `${user.id}/avatar-${Date.now()}-${file.name}`;
const { error } = await supabase.storage
.from("avatars")
.upload(path, file, { upsert: true });
if (error) throw error;
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 className="md:col-span-2">
<Label htmlFor="banner">Banner Image</Label>
<Input id="banner" type="file" accept="image/*" onChange={async (e) => {
const file = e.target.files?.[0];
if (!file || !user) return;
const storeDataUrl = () => new Promise<string>((resolve, reject) => {
const reader = new FileReader();
reader.onload = () => resolve(reader.result as string);
reader.onerror = () => reject(new Error("Failed to read file"));
reader.readAsDataURL(file);
});
try {
const path = `${user.id}/banner-${Date.now()}-${file.name}`;
const { error } = await supabase.storage.from("banners").upload(path, file, { upsert: true });
if (error) throw error;
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) {
<Input
id="banner"
type="file"
accept="image/*"
onChange={async (e) => {
const file = e.target.files?.[0];
if (!file || !user) return;
const storeDataUrl = () =>
new Promise<string>((resolve, reject) => {
const reader = new FileReader();
reader.onload = () =>
resolve(reader.result as string);
reader.onerror = () =>
reject(new Error("Failed to read file"));
reader.readAsDataURL(file);
});
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" });
const path = `${user.id}/banner-${Date.now()}-${file.name}`;
const { error } = await supabase.storage
.from("banners")
.upload(path, file, { upsert: true });
if (error) throw error;
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 className="md:col-span-2">
<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>
<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>
<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>
<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>
<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 className="flex justify-end">
<Button onClick={saveProfile} disabled={savingProfile} className="hover-lift">
<Button
onClick={saveProfile}
disabled={savingProfile}
className="hover-lift"
>
{savingProfile ? "Saving..." : "Save Changes"}
</Button>
</div>
@ -652,14 +767,18 @@ export default function Dashboard() {
<div className="flex items-center justify-between border rounded-lg p-3">
<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>
<Switch defaultChecked />
</div>
<div className="flex items-center justify-between border rounded-lg p-3">
<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>
<Switch />
</div>
@ -669,14 +788,18 @@ export default function Dashboard() {
<div className="flex items-center justify-between border rounded-lg p-3">
<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>
<Switch defaultChecked />
</div>
<div className="flex items-center justify-between border rounded-lg p-3">
<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>
<Switch />
</div>

View file

@ -7,7 +7,15 @@ import { Button } from "@/components/ui/button";
import { Card, CardContent } from "@/components/ui/card";
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
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 {
id: string;
@ -45,7 +53,11 @@ export default function Feed() {
authorAvatar: r.avatar_url,
caption: r.bio || "",
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,
comments: Math.floor(Math.random() * 30),
}));
@ -72,7 +84,11 @@ export default function Feed() {
if (!user && !loading) return <Navigate to="/login" replace />;
if (loading || isLoading) {
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="h-[calc(100vh-64px)] overflow-y-auto snap-y snap-mandatory no-scrollbar">
{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) => (
<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">
<CardContent className="w-full h-full p-0 relative">
{/* Media */}
{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 ? (
<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" />
)}
@ -101,16 +133,37 @@ export default function Feed() {
{/* Right rail actions */}
<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)}>
{muted ? <VolumeX className="h-5 w-5" /> : <Volume2 className="h-5 w-5" />}
<Button
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 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" />
</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" />
</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" />
</Button>
</div>
@ -120,15 +173,37 @@ export default function Feed() {
<div className="flex items-center gap-3">
<Avatar className="h-10 w-10">
<AvatarImage src={item.authorAvatar || undefined} />
<AvatarFallback>{item.authorName[0] || "U"}</AvatarFallback>
<AvatarFallback>
{item.authorName[0] || "U"}
</AvatarFallback>
</Avatar>
<div>
<div className="font-semibold text-white">{item.authorName}</div>
{item.caption && <div className="text-xs text-white/80 max-w-[60vw] line-clamp-2">{item.caption}</div>}
<div className="font-semibold text-white">
{item.authorName}
</div>
{item.caption && (
<div className="text-xs text-white/80 max-w-[60vw] line-clamp-2">
{item.caption}
</div>
)}
</div>
</div>
<Button 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
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>
</div>
</CardContent>

View file

@ -8,7 +8,10 @@ import { Textarea } from "@/components/ui/textarea";
import LoadingScreen from "@/components/LoadingScreen";
import { useAuth } from "@/contexts/AuthContext";
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() {
const navigate = useNavigate();
@ -18,7 +21,9 @@ export default function ProjectsNew() {
const [technologies, setTechnologies] = useState("");
const [githubUrl, setGithubUrl] = 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);
if (!user) {
@ -34,7 +39,10 @@ export default function ProjectsNew() {
const handleSubmit = async (e: any) => {
e.preventDefault();
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;
}
@ -58,16 +66,24 @@ export default function ProjectsNew() {
if (project) {
try {
await aethexAchievementService.checkAndAwardProjectAchievements(user.id);
await aethexAchievementService.checkAndAwardProjectAchievements(
user.id,
);
} 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");
} else {
throw new Error("Project creation failed");
}
} catch (err: any) {
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 {
setIsSubmitting(false);
}
@ -79,37 +95,72 @@ export default function ProjectsNew() {
<div className="container mx-auto px-4 max-w-3xl">
<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>
<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>
<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>
<Label htmlFor="technologies">Technologies (comma separated)</Label>
<Input id="technologies" value={technologies} onChange={(e) => setTechnologies(e.target.value)} placeholder="React, Node.js, Supabase, Typescript" />
<Label htmlFor="technologies">
Technologies (comma separated)
</Label>
<Input
id="technologies"
value={technologies}
onChange={(e) => setTechnologies(e.target.value)}
placeholder="React, Node.js, Supabase, Typescript"
/>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<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>
<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>
<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="in_progress">In Progress</option>
<option value="completed">Completed</option>
@ -118,10 +169,18 @@ export default function ProjectsNew() {
</div>
<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"}
</Button>
<Button type="button" variant="outline" onClick={() => navigate(-1)}>
<Button
type="button"
variant="outline"
onClick={() => navigate(-1)}
>
Cancel
</Button>
</div>

View file

@ -5,12 +5,21 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<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 name="keywords" content="AeThex, developer platform, projects, community, mentorship, research labs, consulting, tutorials" />
<meta
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="theme-color" content="#0a0aff" />
<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" />
<!-- Geo/Audience -->
@ -22,30 +31,66 @@
<link rel="canonical" href="/" />
<!-- 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 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="icon"
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" />
<meta name="msapplication-TileColor" content="#0a0aff" />
<!-- Open Graph -->
<meta property="og:site_name" content="AeThex" />
<meta property="og:type" content="website" />
<meta property="og:title" 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:title"
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:height" content="630" />
<meta property="og:url" content="/" />
<!-- Twitter -->
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:title" 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" />
<meta
name="twitter:title"
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 -->
<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 />
<!-- Structured Data + dynamic canonical/og:url -->
@ -53,8 +98,8 @@
(function () {
var origin = location.origin;
function addJSONLD(obj) {
var s = document.createElement('script');
s.type = 'application/ld+json';
var s = document.createElement("script");
s.type = "application/ld+json";
s.text = JSON.stringify(obj);
document.head.appendChild(s);
}
@ -62,47 +107,64 @@
addJSONLD({
"@context": "https://schema.org",
"@type": "Organization",
"name": "AeThex",
"url": origin,
"logo": "https://cdn.builder.io/api/v1/image/assets%2Ffc53d607e21d497595ac97e0637001a1%2F3979ec9a8a28471d900a80e94e2c45fe?format=png&width=512",
"areaServed": "Worldwide"
name: "AeThex",
url: origin,
logo: "https://cdn.builder.io/api/v1/image/assets%2Ffc53d607e21d497595ac97e0637001a1%2F3979ec9a8a28471d900a80e94e2c45fe?format=png&width=512",
areaServed: "Worldwide",
});
// Website
addJSONLD({
"@context": "https://schema.org",
"@type": "WebSite",
"name": "AeThex",
"url": origin
name: "AeThex",
url: origin,
});
// FAQ for AEO
addJSONLD({
"@context": "https://schema.org",
"@type": "FAQPage",
"mainEntity": [
mainEntity: [
{
"@type": "Question",
"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."}
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.",
},
},
{
"@type": "Question",
"name": "How do I get started?",
"acceptedAnswer": {"@type": "Answer", "text": "Visit the Get Started and Onboarding flows to create your profile and join projects."}
name: "How do I get started?",
acceptedAnswer: {
"@type": "Answer",
text: "Visit the Get Started and Onboarding flows to create your profile and join projects.",
},
},
{
"@type": "Question",
"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."}
}
]
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.",
},
},
],
});
// Set canonical and og:url to the current URL
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;
var ogUrl = document.querySelector('meta[property="og:url"]');
if (!ogUrl) { ogUrl = document.createElement('meta'); ogUrl.setAttribute('property','og:url'); document.head.appendChild(ogUrl); }
ogUrl.setAttribute('content', location.href);
if (!ogUrl) {
ogUrl = document.createElement("meta");
ogUrl.setAttribute("property", "og:url");
document.head.appendChild(ogUrl);
}
ogUrl.setAttribute("content", location.href);
})();
</script>
</head>