Prettier format pending files

This commit is contained in:
Builder.io 2025-10-19 21:31:09 +00:00
parent 57a1a68e3a
commit fb33214954
13 changed files with 4145 additions and 3904 deletions

View file

@ -8,12 +8,15 @@ type Props = {
style?: string | null;
};
const ACCENTS: Record<string, {
grad: string;
glowRing: string;
pill: string;
icon: any;
}> = {
const ACCENTS: Record<
string,
{
grad: string;
glowRing: string;
pill: string;
icon: any;
}
> = {
quest: {
grad: "from-emerald-500/20 via-aethex-500/15 to-neon-blue/20",
glowRing:
@ -58,7 +61,9 @@ export function GamifiedBanner({ text, enabled, style }: Props) {
const parts = useMemo(() => {
// If user prefixed an emoji, use it
const m = /^([\p{Emoji}\p{Extended_Pictographic}]+)\s*(.*)$/u.exec(text || "");
const m = /^([\p{Emoji}\p{Extended_Pictographic}]+)\s*(.*)$/u.exec(
text || "",
);
return {
emoji: m?.[1] || "🎮",
body: (m?.[2] || text || "").trim(),

View file

@ -11,40 +11,83 @@ export type SEOProps = {
function upsertMeta(selector: string, attrs: Record<string, string>) {
let el = document.querySelector(selector) as HTMLElement | null;
if (!el) {
const tag = selector.startsWith('meta[') ? 'meta' : selector.startsWith('link[') ? 'link' : 'meta';
const tag = selector.startsWith("meta[")
? "meta"
: selector.startsWith("link[")
? "link"
: "meta";
el = document.createElement(tag);
document.head.appendChild(el);
}
Object.entries(attrs).forEach(([k, v]) => (el as any).setAttribute(k, v));
}
export default function SEO({ pageTitle, description, image, canonical, noIndex }: SEOProps) {
export default function SEO({
pageTitle,
description,
image,
canonical,
noIndex,
}: SEOProps) {
useEffect(() => {
const title = `AeThex | ${pageTitle}`;
document.title = title;
if (canonical) {
upsertMeta('link[rel="canonical"]', { rel: 'canonical', href: canonical });
upsertMeta('meta[property="og:url"]', { property: 'og:url', content: canonical });
upsertMeta('link[rel="canonical"]', {
rel: "canonical",
href: canonical,
});
upsertMeta('meta[property="og:url"]', {
property: "og:url",
content: canonical,
});
}
if (description) {
upsertMeta('meta[name="description"]', { name: 'description', content: description });
upsertMeta('meta[property="og:description"]', { property: 'og:description', content: description });
upsertMeta('meta[name="twitter:description"]', { name: 'twitter:description', content: description });
upsertMeta('meta[name="description"]', {
name: "description",
content: description,
});
upsertMeta('meta[property="og:description"]', {
property: "og:description",
content: description,
});
upsertMeta('meta[name="twitter:description"]', {
name: "twitter:description",
content: description,
});
}
upsertMeta('meta[property="og:title"]', { property: 'og:title', content: title });
upsertMeta('meta[name="twitter:title"]', { name: 'twitter:title', content: title });
upsertMeta('meta[property="og:title"]', {
property: "og:title",
content: title,
});
upsertMeta('meta[name="twitter:title"]', {
name: "twitter:title",
content: title,
});
if (image) {
upsertMeta('meta[property="og:image"]', { property: 'og:image', content: image });
upsertMeta('meta[name="twitter:image"]', { name: 'twitter:image', content: image });
upsertMeta('meta[property="og:image"]', {
property: "og:image",
content: image,
});
upsertMeta('meta[name="twitter:image"]', {
name: "twitter:image",
content: image,
});
}
if (noIndex) {
upsertMeta('meta[name="robots"]', { name: 'robots', content: 'noindex, nofollow' });
upsertMeta('meta[name="googlebot"]', { name: 'googlebot', content: 'noindex, nofollow' });
upsertMeta('meta[name="robots"]', {
name: "robots",
content: "noindex, nofollow",
});
upsertMeta('meta[name="googlebot"]', {
name: "googlebot",
content: "noindex, nofollow",
});
}
}, [pageTitle, description, image, canonical, noIndex]);

View file

@ -27,7 +27,10 @@ export default function BannerSettings() {
const resp = await fetch("/api/site-settings", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ key: "home_banner", value: { text, enabled, style } }),
body: JSON.stringify({
key: "home_banner",
value: { text, enabled, style },
}),
});
if (!resp.ok) throw new Error("Save failed");
} finally {
@ -38,13 +41,17 @@ export default function BannerSettings() {
return (
<div className="space-y-3">
<div className="grid gap-2 md:grid-cols-4">
<label className="text-sm text-muted-foreground md:col-span-1">Enabled</label>
<label className="text-sm text-muted-foreground md:col-span-1">
Enabled
</label>
<div className="md:col-span-3">
<Switch checked={enabled} onCheckedChange={setEnabled} />
</div>
</div>
<div className="grid gap-2 md:grid-cols-4">
<label className="text-sm text-muted-foreground md:col-span-1">Banner text</label>
<label className="text-sm text-muted-foreground md:col-span-1">
Banner text
</label>
<div className="md:col-span-3">
<input
className="w-full bg-background/50 border border-border/40 rounded px-2 py-1 text-sm"
@ -55,7 +62,9 @@ export default function BannerSettings() {
</div>
</div>
<div className="grid gap-2 md:grid-cols-4">
<label className="text-sm text-muted-foreground md:col-span-1">Style</label>
<label className="text-sm text-muted-foreground md:col-span-1">
Style
</label>
<div className="md:col-span-3">
<select
className="w-full bg-background/50 border border-border/40 rounded px-2 py-1 text-sm"

View file

@ -163,7 +163,10 @@ export const aethexCollabService = {
},
async deleteTask(taskId: string) {
const { error } = await supabase.from("project_tasks").delete().eq("id", taskId);
const { error } = await supabase
.from("project_tasks")
.delete()
.eq("id", taskId);
if (error) throw new Error(error.message || "Unable to delete task");
},

File diff suppressed because it is too large Load diff

View file

@ -208,137 +208,145 @@ const Blog = () => {
return (
<>
<SEO pageTitle="Blog" description="Insights and updates from AeThex: tutorials, platform news, and community highlights." canonical={typeof window!== 'undefined' ? window.location.href : undefined as any} />
<Layout>
<div className="bg-slate-950 text-foreground">
<BlogHero
featured={featuredPost}
totalCount={dataset.length}
search={searchQuery}
onSearchChange={setSearchQuery}
onViewAll={handleResetFilters}
/>
<SEO
pageTitle="Blog"
description="Insights and updates from AeThex: tutorials, platform news, and community highlights."
canonical={
typeof window !== "undefined"
? window.location.href
: (undefined as any)
}
/>
<Layout>
<div className="bg-slate-950 text-foreground">
<BlogHero
featured={featuredPost}
totalCount={dataset.length}
search={searchQuery}
onSearchChange={setSearchQuery}
onViewAll={handleResetFilters}
/>
<section className="border-b border-border/30 bg-background/60 py-12">
<div className="container mx-auto px-4">
<div className="flex flex-col gap-6 lg:flex-row lg:items-center lg:justify-between">
<div className="space-y-2">
<p className="text-xs uppercase tracking-[0.4em] text-muted-foreground">
Filter by track
</p>
<h2 className="text-2xl font-semibold text-white">
Navigate the AeThex knowledge graph
</h2>
</div>
<Button
variant="ghost"
size="sm"
onClick={handleResetFilters}
className="self-start lg:self-auto"
>
Reset filters
</Button>
</div>
<div className="mt-6">
<BlogCategoryChips
categories={categories}
selected={selectedCategory}
onSelect={setSelectedCategory}
/>
</div>
</div>
</section>
<BlogTrendingRail posts={trendingPosts} />
<section className="border-b border-border/30 bg-background/80 py-16">
<div className="container mx-auto grid gap-6 px-4 md:grid-cols-3">
{insights.map((insight) => (
<Card
key={insight.label}
className="border-border/40 bg-background/60 backdrop-blur transition hover:border-aethex-400/50"
>
<CardContent className="flex items-center gap-4 p-6">
<span className="flex h-12 w-12 items-center justify-center rounded-full border border-border/30 bg-background/70 text-aethex-200">
{insight.icon}
</span>
<div>
<p className="text-sm text-muted-foreground">
{insight.label}
</p>
<p className="text-2xl font-semibold text-white">
{insight.value}
</p>
<p className="text-xs text-muted-foreground">
{insight.helper}
</p>
</div>
</CardContent>
</Card>
))}
</div>
</section>
<section className="py-20">
<div className="container mx-auto space-y-12 px-4">
<div className="flex flex-col gap-4 sm:flex-row sm:items-end sm:justify-between">
<div className="space-y-2">
<p className="text-xs uppercase tracking-[0.4em] text-muted-foreground">
Latest updates
</p>
<h2 className="text-3xl font-semibold text-white">
Fresh from the AeThex ship room
</h2>
</div>
<Button
asChild
variant="outline"
className="self-start border-border/60 text-sm"
>
<Link to="/changelog">
View changelog
<ArrowRight className="ml-2 h-4 w-4" />
</Link>
</Button>
</div>
<BlogPostGrid posts={displayedPosts} />
</div>
</section>
<BlogNewsletterSection />
<section className="bg-background/70 py-16">
<div className="container mx-auto px-4">
<div className="rounded-2xl border border-border/40 bg-background/80 p-8">
<div className="flex flex-col gap-6 md:flex-row md:items-center md:justify-between">
<section className="border-b border-border/30 bg-background/60 py-12">
<div className="container mx-auto px-4">
<div className="flex flex-col gap-6 lg:flex-row lg:items-center lg:justify-between">
<div className="space-y-2">
<p className="text-xs uppercase tracking-[0.4em] text-muted-foreground">
Explore more
Filter by track
</p>
<h3 className="text-2xl font-semibold text-white">
Dive into AeThex documentation
</h3>
<p className="max-w-2xl text-sm text-muted-foreground">
Looking for implementation guides, deployment recipes, or
program onboarding materials? Visit our documentation hub
for developer tutorials, platform references, and community
playbooks.
<h2 className="text-2xl font-semibold text-white">
Navigate the AeThex knowledge graph
</h2>
</div>
<Button
variant="ghost"
size="sm"
onClick={handleResetFilters}
className="self-start lg:self-auto"
>
Reset filters
</Button>
</div>
<div className="mt-6">
<BlogCategoryChips
categories={categories}
selected={selectedCategory}
onSelect={setSelectedCategory}
/>
</div>
</div>
</section>
<BlogTrendingRail posts={trendingPosts} />
<section className="border-b border-border/30 bg-background/80 py-16">
<div className="container mx-auto grid gap-6 px-4 md:grid-cols-3">
{insights.map((insight) => (
<Card
key={insight.label}
className="border-border/40 bg-background/60 backdrop-blur transition hover:border-aethex-400/50"
>
<CardContent className="flex items-center gap-4 p-6">
<span className="flex h-12 w-12 items-center justify-center rounded-full border border-border/30 bg-background/70 text-aethex-200">
{insight.icon}
</span>
<div>
<p className="text-sm text-muted-foreground">
{insight.label}
</p>
<p className="text-2xl font-semibold text-white">
{insight.value}
</p>
<p className="text-xs text-muted-foreground">
{insight.helper}
</p>
</div>
</CardContent>
</Card>
))}
</div>
</section>
<section className="py-20">
<div className="container mx-auto space-y-12 px-4">
<div className="flex flex-col gap-4 sm:flex-row sm:items-end sm:justify-between">
<div className="space-y-2">
<p className="text-xs uppercase tracking-[0.4em] text-muted-foreground">
Latest updates
</p>
<h2 className="text-3xl font-semibold text-white">
Fresh from the AeThex ship room
</h2>
</div>
<Button
asChild
className="bg-gradient-to-r from-aethex-500 to-neon-blue"
variant="outline"
className="self-start border-border/60 text-sm"
>
<Link to="/docs">Open documentation hub</Link>
<Link to="/changelog">
View changelog
<ArrowRight className="ml-2 h-4 w-4" />
</Link>
</Button>
</div>
<BlogPostGrid posts={displayedPosts} />
</div>
</div>
</section>
</div>
</Layout>
</>
</section>
<BlogNewsletterSection />
<section className="bg-background/70 py-16">
<div className="container mx-auto px-4">
<div className="rounded-2xl border border-border/40 bg-background/80 p-8">
<div className="flex flex-col gap-6 md:flex-row md:items-center md:justify-between">
<div className="space-y-2">
<p className="text-xs uppercase tracking-[0.4em] text-muted-foreground">
Explore more
</p>
<h3 className="text-2xl font-semibold text-white">
Dive into AeThex documentation
</h3>
<p className="max-w-2xl text-sm text-muted-foreground">
Looking for implementation guides, deployment recipes, or
program onboarding materials? Visit our documentation hub
for developer tutorials, platform references, and
community playbooks.
</p>
</div>
<Button
asChild
className="bg-gradient-to-r from-aethex-500 to-neon-blue"
>
<Link to="/docs">Open documentation hub</Link>
</Button>
</div>
</div>
</div>
</section>
</div>
</Layout>
</>
);
};

View file

@ -72,59 +72,68 @@ export default function BlogPost() {
return (
<>
<SEO pageTitle={post?.title || "Blog Post"} description={post?.excerpt || undefined} image={post?.image || null} canonical={typeof window!== 'undefined' ? window.location.href : undefined as any} />
<Layout>
<div className="min-h-screen bg-aethex-gradient py-12">
<div className="container mx-auto px-4 max-w-3xl">
<Card className="overflow-hidden border-border/50 animate-scale-in">
{post.image && (
<img
src={post.image}
alt={post.title}
className="w-full h-64 object-cover"
/>
)}
<CardHeader>
{post.category && (
<Badge className="mb-4 bg-gradient-to-r from-aethex-500 to-neon-blue">
{post.category}
</Badge>
<SEO
pageTitle={post?.title || "Blog Post"}
description={post?.excerpt || undefined}
image={post?.image || null}
canonical={
typeof window !== "undefined"
? window.location.href
: (undefined as any)
}
/>
<Layout>
<div className="min-h-screen bg-aethex-gradient py-12">
<div className="container mx-auto px-4 max-w-3xl">
<Card className="overflow-hidden border-border/50 animate-scale-in">
{post.image && (
<img
src={post.image}
alt={post.title}
className="w-full h-64 object-cover"
/>
)}
<CardTitle className="text-3xl mt-2">{post.title}</CardTitle>
{post.excerpt && (
<CardDescription className="text-muted-foreground mt-2">
{post.excerpt}
</CardDescription>
)}
<div className="flex items-center gap-4 mt-4 text-sm text-muted-foreground">
{post.author && (
<div className="flex items-center gap-2">
<User className="h-4 w-4" /> <span>{post.author}</span>
</div>
<CardHeader>
{post.category && (
<Badge className="mb-4 bg-gradient-to-r from-aethex-500 to-neon-blue">
{post.category}
</Badge>
)}
{post.date && (
<div className="flex items-center gap-2">
<Calendar className="h-4 w-4" /> <span>{post.date}</span>
</div>
<CardTitle className="text-3xl mt-2">{post.title}</CardTitle>
{post.excerpt && (
<CardDescription className="text-muted-foreground mt-2">
{post.excerpt}
</CardDescription>
)}
</div>
</CardHeader>
<CardContent className="prose max-w-none mt-6">
{post.body ? (
<div dangerouslySetInnerHTML={{ __html: post.body }} />
) : (
<p>{post.excerpt}</p>
)}
<div className="pt-6">
<Link to="/blog" className="text-aethex-400 underline">
Back to Blog
</Link>
</div>
</CardContent>
</Card>
<div className="flex items-center gap-4 mt-4 text-sm text-muted-foreground">
{post.author && (
<div className="flex items-center gap-2">
<User className="h-4 w-4" /> <span>{post.author}</span>
</div>
)}
{post.date && (
<div className="flex items-center gap-2">
<Calendar className="h-4 w-4" /> <span>{post.date}</span>
</div>
)}
</div>
</CardHeader>
<CardContent className="prose max-w-none mt-6">
{post.body ? (
<div dangerouslySetInnerHTML={{ __html: post.body }} />
) : (
<p>{post.excerpt}</p>
)}
<div className="pt-6">
<Link to="/blog" className="text-aethex-400 underline">
Back to Blog
</Link>
</div>
</CardContent>
</Card>
</div>
</div>
</div>
</Layout>
</>
</Layout>
</>
);
}

File diff suppressed because it is too large Load diff

View file

@ -244,7 +244,11 @@ export default function Index() {
},
];
const [homeBanner, setHomeBanner] = useState<{ text: string; enabled?: boolean; style?: string } | null>(null);
const [homeBanner, setHomeBanner] = useState<{
text: string;
enabled?: boolean;
style?: string;
} | null>(null);
useEffect(() => {
fetch("/api/site-settings?key=home_banner")
@ -270,230 +274,236 @@ export default function Index() {
<SEO
pageTitle="Home"
description="AeThex: Where vision meets execution. Build, learn, and grow through design, development, and community."
canonical={typeof window !== 'undefined' ? window.location.href : undefined as any}
canonical={
typeof window !== "undefined"
? window.location.href
: (undefined as any)
}
/>
<Layout hideFooter>
{/* Top Banner (editable via Admin → Operations) */}
{homeBanner?.enabled !== false && (
<GamifiedBanner
text={homeBanner?.text || "ROBLOX AUTH SOON"}
enabled={homeBanner?.enabled !== false}
style={(homeBanner as any)?.style || null}
/>
)}
{/* Top Banner (editable via Admin → Operations) */}
{homeBanner?.enabled !== false && (
<GamifiedBanner
text={homeBanner?.text || "ROBLOX AUTH SOON"}
enabled={homeBanner?.enabled !== false}
style={(homeBanner as any)?.style || null}
/>
)}
{/* Hero Section - Geometric Design */}
<section className="relative min-h-screen flex items-start justify-center overflow-hidden pt-24 sm:pt-36">
{/* Geometric Background Pattern */}
<div className="absolute inset-0">
<div className="absolute inset-0 bg-gradient-to-br from-aethex-900/50 via-background to-aethex-800/50" />
{/* Hero Section - Geometric Design */}
<section className="relative min-h-screen flex items-start justify-center overflow-hidden pt-24 sm:pt-36">
{/* Geometric Background Pattern */}
<div className="absolute inset-0">
{/* Large Logo-inspired Geometric Shape */}
<div className="absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2">
<div className="relative w-96 h-96 opacity-5">
<img
src="https://cdn.builder.io/api/v1/image/assets%2Ffc53d607e21d497595ac97e0637001a1%2F3979ec9a8a28471d900a80e94e2c45fe?format=webp&width=800"
alt="Background"
className="w-full h-full animate-float"
<div className="absolute inset-0 bg-gradient-to-br from-aethex-900/50 via-background to-aethex-800/50" />
<div className="absolute inset-0">
{/* Large Logo-inspired Geometric Shape */}
<div className="absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2">
<div className="relative w-96 h-96 opacity-5">
<img
src="https://cdn.builder.io/api/v1/image/assets%2Ffc53d607e21d497595ac97e0637001a1%2F3979ec9a8a28471d900a80e94e2c45fe?format=webp&width=800"
alt="Background"
className="w-full h-full animate-float"
/>
</div>
</div>
{/* Floating Geometric Elements */}
{[...Array(20)].map((_, i) => (
<div
key={i}
className="absolute bg-aethex-400/20 animate-float"
style={{
width: `${10 + Math.random() * 20}px`,
height: `${10 + Math.random() * 20}px`,
left: `${Math.random() * 100}%`,
top: `${Math.random() * 100}%`,
animationDelay: `${Math.random() * 5}s`,
animationDuration: `${4 + Math.random() * 3}s`,
clipPath: "polygon(50% 0%, 100% 50%, 50% 100%, 0% 50%)",
}}
/>
</div>
))}
</div>
{/* Floating Geometric Elements */}
{[...Array(20)].map((_, i) => (
<div
key={i}
className="absolute bg-aethex-400/20 animate-float"
style={{
width: `${10 + Math.random() * 20}px`,
height: `${10 + Math.random() * 20}px`,
left: `${Math.random() * 100}%`,
top: `${Math.random() * 100}%`,
animationDelay: `${Math.random() * 5}s`,
animationDuration: `${4 + Math.random() * 3}s`,
clipPath: "polygon(50% 0%, 100% 50%, 50% 100%, 0% 50%)",
}}
/>
))}
</div>
</div>
{/* Main Content */}
<div className="container mx-auto px-4 relative z-10 pb-24 sm:pb-28">
<div className="text-center space-y-12">
{/* Title */}
<div className="space-y-6 animate-scale-in">
<div className="space-y-4">
<h1 className="text-4xl sm:text-5xl lg:text-7xl font-bold">
<span className="text-gradient-purple">AeThex</span>
</h1>
<h2 className="text-2xl lg:text-3xl text-gradient animate-fade-in">
Crafting Digital Realities
</h2>
<p className="text-lg text-muted-foreground max-w-2xl mx-auto animate-slide-up">
Where vision meets execution. We craft experiences through
design, development, and community.
</p>
{/* Main Content */}
<div className="container mx-auto px-4 relative z-10 pb-24 sm:pb-28">
<div className="text-center space-y-12">
{/* Title */}
<div className="space-y-6 animate-scale-in">
<div className="space-y-4">
<h1 className="text-4xl sm:text-5xl lg:text-7xl font-bold">
<span className="text-gradient-purple">AeThex</span>
</h1>
<h2 className="text-2xl lg:text-3xl text-gradient animate-fade-in">
Crafting Digital Realities
</h2>
<p className="text-lg text-muted-foreground max-w-2xl mx-auto animate-slide-up">
Where vision meets execution. We craft experiences through
design, development, and community.
</p>
</div>
</div>
</div>
{/* Interactive Features Grid (Services) */}
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-6 max-w-5xl mx-auto animate-slide-up">
{features.map((feature, index) => {
const Icon = feature.icon;
const isActive = activeSection === index;
return (
<Card
key={`old-${index}`}
className={`relative overflow-hidden rounded-xl border transition-all duration-500 group animate-fade-in ${
isActive
? "border-aethex-500/60 glow-blue"
: "border-border/30 hover:border-aethex-400/50"
} bg-card/60 backdrop-blur-sm hover:translate-y-[-2px] hover:shadow-[0_8px_30px_rgba(80,80,120,0.25)]`}
style={{ animationDelay: `${index * 0.08}s` }}
>
<div className="pointer-events-none absolute inset-0 opacity-0 group-hover:opacity-100 transition-opacity duration-500 bg-gradient-to-br from-white/6 via-transparent to-white/0" />
<CardContent className="p-5 sm:p-6 flex flex-col items-center text-center gap-3">
<div
className={`relative w-12 h-12 rounded-lg bg-gradient-to-r ${feature.color} grid place-items-center shadow-inner`}
>
<div className="absolute -inset-[2px] rounded-xl bg-gradient-to-r from-white/20 to-transparent blur-md opacity-0 group-hover:opacity-100 transition-opacity" />
<Icon className="h-6 w-6 text-white drop-shadow" />
</div>
<h3 className="font-semibold text-sm tracking-wide">
{feature.title}
</h3>
<div className="flex flex-wrap justify-center gap-2 min-h-[24px]">
{(feature.tags || []).slice(0, 2).map((tag, i) => (
<Badge
key={i}
variant="outline"
className="border-white/10 text-xs text-foreground/80"
>
{tag}
</Badge>
))}
</div>
<p className="text-xs text-muted-foreground line-clamp-2">
{feature.description}
</p>
<div
className={`mt-1 h-[2px] w-16 rounded-full bg-gradient-to-r ${feature.color} opacity-60 group-hover:opacity-100 transition-opacity`}
/>
{feature.link ? (
<div className="pt-1">
<Link
to={feature.link}
className="text-xs inline-flex items-center gap-1 text-aethex-300 hover:text-aethex-200"
>
Explore
<ArrowRight className="h-3 w-3" />
</Link>
{/* Interactive Features Grid (Services) */}
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-6 max-w-5xl mx-auto animate-slide-up">
{features.map((feature, index) => {
const Icon = feature.icon;
const isActive = activeSection === index;
return (
<Card
key={`old-${index}`}
className={`relative overflow-hidden rounded-xl border transition-all duration-500 group animate-fade-in ${
isActive
? "border-aethex-500/60 glow-blue"
: "border-border/30 hover:border-aethex-400/50"
} bg-card/60 backdrop-blur-sm hover:translate-y-[-2px] hover:shadow-[0_8px_30px_rgba(80,80,120,0.25)]`}
style={{ animationDelay: `${index * 0.08}s` }}
>
<div className="pointer-events-none absolute inset-0 opacity-0 group-hover:opacity-100 transition-opacity duration-500 bg-gradient-to-br from-white/6 via-transparent to-white/0" />
<CardContent className="p-5 sm:p-6 flex flex-col items-center text-center gap-3">
<div
className={`relative w-12 h-12 rounded-lg bg-gradient-to-r ${feature.color} grid place-items-center shadow-inner`}
>
<div className="absolute -inset-[2px] rounded-xl bg-gradient-to-r from-white/20 to-transparent blur-md opacity-0 group-hover:opacity-100 transition-opacity" />
<Icon className="h-6 w-6 text-white drop-shadow" />
</div>
) : null}
</CardContent>
</Card>
);
})}
</div>
{/* Platform Feature Cards */}
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-6 max-w-5xl mx-auto animate-slide-up mt-6">
{platformFeatures.map((feature, index) => {
const Icon = feature.icon;
const isActive = activeSection === index;
return (
<Card
key={`platform-${index}`}
className={`relative overflow-hidden rounded-xl border transition-all duration-500 group animate-fade-in ${
isActive
? "border-aethex-500/60 glow-blue"
: "border-border/30 hover:border-aethex-400/50"
} bg-card/60 backdrop-blur-sm hover:translate-y-[-2px] hover:shadow-[0_8px_30px_rgba(80,80,120,0.25)]`}
style={{ animationDelay: `${(index + 4) * 0.08}s` }}
>
<div className="pointer-events-none absolute inset-0 opacity-0 group-hover:opacity-100 transition-opacity duration-500 bg-gradient-to-br from-white/6 via-transparent to-white/0" />
<CardContent className="p-5 sm:p-6 flex flex-col items-center text-center gap-3">
<div
className={`relative w-12 h-12 rounded-lg bg-gradient-to-r ${feature.color} grid place-items-center shadow-inner`}
>
<div className="absolute -inset-[2px] rounded-xl bg-gradient-to-r from-white/20 to-transparent blur-md opacity-0 group-hover:opacity-100 transition-opacity" />
<Icon className="h-6 w-6 text-white drop-shadow" />
</div>
<h3 className="font-semibold text-sm tracking-wide">
{feature.title}
</h3>
<div className="flex flex-wrap justify-center gap-2 min-h-[24px]">
{(feature.tags || []).slice(0, 2).map((tag, i) => (
<Badge
key={i}
variant="outline"
className="border-white/10 text-xs text-foreground/80"
>
{tag}
</Badge>
))}
</div>
<p className="text-xs text-muted-foreground line-clamp-2">
{feature.description}
</p>
<div
className={`mt-1 h-[2px] w-16 rounded-full bg-gradient-to-r ${feature.color} opacity-60 group-hover:opacity-100 transition-opacity`}
/>
{feature.link ? (
<div className="pt-1">
<Link
to={feature.link}
className="text-xs inline-flex items-center gap-1 text-aethex-300 hover:text-aethex-200"
>
Explore
<ArrowRight className="h-3 w-3" />
</Link>
<h3 className="font-semibold text-sm tracking-wide">
{feature.title}
</h3>
<div className="flex flex-wrap justify-center gap-2 min-h-[24px]">
{(feature.tags || []).slice(0, 2).map((tag, i) => (
<Badge
key={i}
variant="outline"
className="border-white/10 text-xs text-foreground/80"
>
{tag}
</Badge>
))}
</div>
) : null}
</CardContent>
</Card>
);
})}
</div>
<p className="text-xs text-muted-foreground line-clamp-2">
{feature.description}
</p>
<div
className={`mt-1 h-[2px] w-16 rounded-full bg-gradient-to-r ${feature.color} opacity-60 group-hover:opacity-100 transition-opacity`}
/>
{feature.link ? (
<div className="pt-1">
<Link
to={feature.link}
className="text-xs inline-flex items-center gap-1 text-aethex-300 hover:text-aethex-200"
>
Explore
<ArrowRight className="h-3 w-3" />
</Link>
</div>
) : null}
</CardContent>
</Card>
);
})}
</div>
{/* Action Buttons */}
<div className="flex flex-col sm:flex-row justify-center gap-6 animate-slide-up mb-8 sm:mb-10">
<Button
asChild
size="lg"
className="bg-gradient-to-r from-aethex-500 to-neon-blue hover:from-aethex-600 hover:to-neon-blue/90 glow-blue hover-lift text-base sm:text-lg px-6 py-4 sm:px-8 sm:py-6"
>
<Link
to="/onboarding"
className="flex items-center space-x-2 group"
{/* Platform Feature Cards */}
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-6 max-w-5xl mx-auto animate-slide-up mt-6">
{platformFeatures.map((feature, index) => {
const Icon = feature.icon;
const isActive = activeSection === index;
return (
<Card
key={`platform-${index}`}
className={`relative overflow-hidden rounded-xl border transition-all duration-500 group animate-fade-in ${
isActive
? "border-aethex-500/60 glow-blue"
: "border-border/30 hover:border-aethex-400/50"
} bg-card/60 backdrop-blur-sm hover:translate-y-[-2px] hover:shadow-[0_8px_30px_rgba(80,80,120,0.25)]`}
style={{ animationDelay: `${(index + 4) * 0.08}s` }}
>
<div className="pointer-events-none absolute inset-0 opacity-0 group-hover:opacity-100 transition-opacity duration-500 bg-gradient-to-br from-white/6 via-transparent to-white/0" />
<CardContent className="p-5 sm:p-6 flex flex-col items-center text-center gap-3">
<div
className={`relative w-12 h-12 rounded-lg bg-gradient-to-r ${feature.color} grid place-items-center shadow-inner`}
>
<div className="absolute -inset-[2px] rounded-xl bg-gradient-to-r from-white/20 to-transparent blur-md opacity-0 group-hover:opacity-100 transition-opacity" />
<Icon className="h-6 w-6 text-white drop-shadow" />
</div>
<h3 className="font-semibold text-sm tracking-wide">
{feature.title}
</h3>
<div className="flex flex-wrap justify-center gap-2 min-h-[24px]">
{(feature.tags || []).slice(0, 2).map((tag, i) => (
<Badge
key={i}
variant="outline"
className="border-white/10 text-xs text-foreground/80"
>
{tag}
</Badge>
))}
</div>
<p className="text-xs text-muted-foreground line-clamp-2">
{feature.description}
</p>
<div
className={`mt-1 h-[2px] w-16 rounded-full bg-gradient-to-r ${feature.color} opacity-60 group-hover:opacity-100 transition-opacity`}
/>
{feature.link ? (
<div className="pt-1">
<Link
to={feature.link}
className="text-xs inline-flex items-center gap-1 text-aethex-300 hover:text-aethex-200"
>
Explore
<ArrowRight className="h-3 w-3" />
</Link>
</div>
) : null}
</CardContent>
</Card>
);
})}
</div>
{/* Action Buttons */}
<div className="flex flex-col sm:flex-row justify-center gap-6 animate-slide-up mb-8 sm:mb-10">
<Button
asChild
size="lg"
className="bg-gradient-to-r from-aethex-500 to-neon-blue hover:from-aethex-600 hover:to-neon-blue/90 glow-blue hover-lift text-base sm:text-lg px-6 py-4 sm:px-8 sm:py-6"
>
<Sparkles className="h-5 w-5" />
<span>Start Your Journey</span>
<ArrowRight className="h-5 w-5 transition-transform group-hover:translate-x-2" />
</Link>
</Button>
<Button
asChild
variant="outline"
size="lg"
className="border-aethex-400/50 hover:border-aethex-400 hover-lift text-base sm:text-lg px-6 py-4 sm:px-8 sm:py-6"
>
<Link to="/explore">Explore Platform</Link>
</Button>
<Button
asChild
variant="ghost"
size="lg"
className="hover-lift text-base sm:text-lg px-6 py-4 sm:px-8 sm:py-6"
>
<a href="https://aethex.net" target="_blank" rel="noreferrer">Visit aethex.net</a>
</Button>
<Link
to="/onboarding"
className="flex items-center space-x-2 group"
>
<Sparkles className="h-5 w-5" />
<span>Start Your Journey</span>
<ArrowRight className="h-5 w-5 transition-transform group-hover:translate-x-2" />
</Link>
</Button>
<Button
asChild
variant="outline"
size="lg"
className="border-aethex-400/50 hover:border-aethex-400 hover-lift text-base sm:text-lg px-6 py-4 sm:px-8 sm:py-6"
>
<Link to="/explore">Explore Platform</Link>
</Button>
<Button
asChild
variant="ghost"
size="lg"
className="hover-lift text-base sm:text-lg px-6 py-4 sm:px-8 sm:py-6"
>
<a href="https://aethex.net" target="_blank" rel="noreferrer">
Visit aethex.net
</a>
</Button>
</div>
</div>
</div>
</div>
</section>
</Layout>
</section>
</Layout>
</>
);
}

View file

@ -247,308 +247,321 @@ export default function Login() {
return (
<>
<SEO pageTitle="Login" description="Sign in to your AeThex account to access the dashboard and community." canonical={typeof window !== 'undefined' ? window.location.href : undefined as any} />
<Layout>
<div className="min-h-screen bg-aethex-gradient py-12 flex items-center justify-center">
<div className="container mx-auto px-4 max-w-md">
{/* Floating particles effect */}
<div className="absolute inset-0 pointer-events-none overflow-hidden opacity-10">
{[...Array(20)].map((_, i) => (
<div
key={i}
className="absolute w-1 h-1 bg-aethex-400 rounded-full animate-float"
style={{
left: `${Math.random() * 100}%`,
top: `${Math.random() * 100}%`,
animationDelay: `${Math.random() * 3}s`,
animationDuration: `${3 + Math.random() * 2}s`,
}}
/>
))}
</div>
<Card className="bg-card/50 backdrop-blur-sm border border-border/50 shadow-2xl animate-scale-in relative z-10">
<CardHeader className="text-center space-y-4">
<div className="flex justify-center">
<div className="p-4 rounded-full bg-gradient-to-r from-aethex-500/20 to-neon-blue/20 border border-aethex-400/20">
<Shield className="h-8 w-8 text-aethex-400 animate-pulse-glow" />
</div>
</div>
<div className="space-y-2">
<CardTitle className="text-2xl text-gradient-purple">
{isSignUp ? "Create Account" : "Welcome Back"}
</CardTitle>
<CardDescription>
{isSignUp
? "Create your AeThex account to get started"
: "Sign in to your AeThex account to access the dashboard"}
</CardDescription>
</div>
<Badge
variant="outline"
className="border-aethex-400/50 text-aethex-400"
>
<Sparkles className="h-3 w-3 mr-1" />
Secure Login
</Badge>
</CardHeader>
<CardContent className="space-y-6">
{manualVerificationLink ? (
<Alert className="border-aethex-400/30 bg-aethex-500/10 text-foreground">
<Info className="h-4 w-4 text-aethex-300" />
<AlertTitle>Manual verification required</AlertTitle>
<AlertDescription>
<p>
We couldn't send the verification email automatically. Use
the link below to confirm your account:
</p>
<p className="mt-2 break-all rounded bg-background/60 px-3 py-2 font-mono text-xs text-foreground/90">
{manualVerificationLink}
</p>
<Button
type="button"
size="sm"
variant="outline"
className="mt-3 border-aethex-400/40"
onClick={() =>
window.open(
manualVerificationLink,
"_blank",
"noopener",
)
}
>
Open verification link
</Button>
</AlertDescription>
</Alert>
) : null}
{/* Social Login Buttons */}
<div className="space-y-3">
<Button
variant="outline"
className="w-full hover-lift interactive-scale"
onClick={() => handleSocialLogin("github")}
>
<Github className="h-4 w-4 mr-2" />
Continue with GitHub
</Button>
<Button
variant="outline"
className="w-full hover-lift interactive-scale"
onClick={() => handleSocialLogin("google")}
>
<Mail className="h-4 w-4 mr-2" />
Continue with Google
</Button>
<Button
variant="outline"
className="w-full hover-lift interactive-scale"
onClick={() => {
const apiBase =
(import.meta as any)?.env?.VITE_API_BASE ||
window.location.origin;
const u = new URL("/api/roblox/oauth/start", apiBase);
const next = new URLSearchParams(
window.location.search,
).get("next");
if (next && next.startsWith("/"))
u.searchParams.set("state", next);
window.location.href = u.toString();
<SEO
pageTitle="Login"
description="Sign in to your AeThex account to access the dashboard and community."
canonical={
typeof window !== "undefined"
? window.location.href
: (undefined as any)
}
/>
<Layout>
<div className="min-h-screen bg-aethex-gradient py-12 flex items-center justify-center">
<div className="container mx-auto px-4 max-w-md">
{/* Floating particles effect */}
<div className="absolute inset-0 pointer-events-none overflow-hidden opacity-10">
{[...Array(20)].map((_, i) => (
<div
key={i}
className="absolute w-1 h-1 bg-aethex-400 rounded-full animate-float"
style={{
left: `${Math.random() * 100}%`,
top: `${Math.random() * 100}%`,
animationDelay: `${Math.random() * 3}s`,
animationDuration: `${3 + Math.random() * 2}s`,
}}
/>
))}
</div>
<Card className="bg-card/50 backdrop-blur-sm border border-border/50 shadow-2xl animate-scale-in relative z-10">
<CardHeader className="text-center space-y-4">
<div className="flex justify-center">
<div className="p-4 rounded-full bg-gradient-to-r from-aethex-500/20 to-neon-blue/20 border border-aethex-400/20">
<Shield className="h-8 w-8 text-aethex-400 animate-pulse-glow" />
</div>
</div>
<div className="space-y-2">
<CardTitle className="text-2xl text-gradient-purple">
{isSignUp ? "Create Account" : "Welcome Back"}
</CardTitle>
<CardDescription>
{isSignUp
? "Create your AeThex account to get started"
: "Sign in to your AeThex account to access the dashboard"}
</CardDescription>
</div>
<Badge
variant="outline"
className="border-aethex-400/50 text-aethex-400"
>
<Sparkles className="h-4 w-4 mr-2" />
Continue with Roblox
</Button>
</div>
<Sparkles className="h-3 w-3 mr-1" />
Secure Login
</Badge>
</CardHeader>
<div className="relative">
<div className="absolute inset-0 flex items-center">
<div className="w-full border-t border-border/50" />
<CardContent className="space-y-6">
{manualVerificationLink ? (
<Alert className="border-aethex-400/30 bg-aethex-500/10 text-foreground">
<Info className="h-4 w-4 text-aethex-300" />
<AlertTitle>Manual verification required</AlertTitle>
<AlertDescription>
<p>
We couldn't send the verification email automatically.
Use the link below to confirm your account:
</p>
<p className="mt-2 break-all rounded bg-background/60 px-3 py-2 font-mono text-xs text-foreground/90">
{manualVerificationLink}
</p>
<Button
type="button"
size="sm"
variant="outline"
className="mt-3 border-aethex-400/40"
onClick={() =>
window.open(
manualVerificationLink,
"_blank",
"noopener",
)
}
>
Open verification link
</Button>
</AlertDescription>
</Alert>
) : null}
{/* Social Login Buttons */}
<div className="space-y-3">
<Button
variant="outline"
className="w-full hover-lift interactive-scale"
onClick={() => handleSocialLogin("github")}
>
<Github className="h-4 w-4 mr-2" />
Continue with GitHub
</Button>
<Button
variant="outline"
className="w-full hover-lift interactive-scale"
onClick={() => handleSocialLogin("google")}
>
<Mail className="h-4 w-4 mr-2" />
Continue with Google
</Button>
<Button
variant="outline"
className="w-full hover-lift interactive-scale"
onClick={() => {
const apiBase =
(import.meta as any)?.env?.VITE_API_BASE ||
window.location.origin;
const u = new URL("/api/roblox/oauth/start", apiBase);
const next = new URLSearchParams(
window.location.search,
).get("next");
if (next && next.startsWith("/"))
u.searchParams.set("state", next);
window.location.href = u.toString();
}}
>
<Sparkles className="h-4 w-4 mr-2" />
Continue with Roblox
</Button>
</div>
<div className="relative flex justify-center text-xs uppercase">
<span className="bg-background px-2 text-muted-foreground">
Or continue with email
</span>
<div className="relative">
<div className="absolute inset-0 flex items-center">
<div className="w-full border-t border-border/50" />
</div>
<div className="relative flex justify-center text-xs uppercase">
<span className="bg-background px-2 text-muted-foreground">
Or continue with email
</span>
</div>
</div>
</div>
{/* Aethex Org Login (Magic Link) */}
<OrgLogin />
{/* Aethex Org Login (Magic Link) */}
<OrgLogin />
{/* Email/Password Form */}
<form onSubmit={handleSubmit} className="space-y-4">
{isSignUp && (
<div className="space-y-2">
<Label htmlFor="fullName" className="text-sm font-medium">
Full Name
</Label>
<div className="relative">
<User className="absolute left-3 top-1/2 transform -translate-y-1/2 h-4 w-4 text-muted-foreground" />
<Input
id="fullName"
type="text"
value={fullName}
onChange={(e) => setFullName(e.target.value)}
placeholder="Enter your full name"
className="pl-10 bg-background/50 border-border/50 focus:border-aethex-400"
required={isSignUp}
/>
</div>
</div>
)}
{/* Email/Password Form */}
<form onSubmit={handleSubmit} className="space-y-4">
{isSignUp && (
<div className="space-y-2">
<Label htmlFor="fullName" className="text-sm font-medium">
Full Name
<Label htmlFor="email" className="text-sm font-medium">
Email Address
</Label>
<div className="relative">
<User className="absolute left-3 top-1/2 transform -translate-y-1/2 h-4 w-4 text-muted-foreground" />
<Mail className="absolute left-3 top-1/2 transform -translate-y-1/2 h-4 w-4 text-muted-foreground" />
<Input
id="fullName"
type="text"
value={fullName}
onChange={(e) => setFullName(e.target.value)}
placeholder="Enter your full name"
id="email"
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder="Enter your email"
className="pl-10 bg-background/50 border-border/50 focus:border-aethex-400"
required={isSignUp}
required
/>
</div>
</div>
)}
<div className="space-y-2">
<Label htmlFor="email" className="text-sm font-medium">
Email Address
</Label>
<div className="relative">
<Mail className="absolute left-3 top-1/2 transform -translate-y-1/2 h-4 w-4 text-muted-foreground" />
<Input
id="email"
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder="Enter your email"
className="pl-10 bg-background/50 border-border/50 focus:border-aethex-400"
required
/>
</div>
</div>
<div className="space-y-2">
<Label htmlFor="password" className="text-sm font-medium">
Password
</Label>
<div className="relative">
<Lock className="absolute left-3 top-1/2 transform -translate-y-1/2 h-4 w-4 text-muted-foreground" />
<Input
id="password"
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
placeholder={
isSignUp ? "Create a password" : "Enter your password"
}
className="pl-10 bg-background/50 border-border/50 focus:border-aethex-400"
required
minLength={isSignUp ? 6 : undefined}
/>
</div>
{isSignUp && (
<p className="text-xs text-muted-foreground">
Password must be at least 6 characters long
</p>
)}
</div>
{!isSignUp && (
<div className="flex items-center justify-between text-sm">
<label className="flex items-center space-x-2 cursor-pointer">
<input
type="checkbox"
className="rounded border-border/50"
<div className="space-y-2">
<Label htmlFor="password" className="text-sm font-medium">
Password
</Label>
<div className="relative">
<Lock className="absolute left-3 top-1/2 transform -translate-y-1/2 h-4 w-4 text-muted-foreground" />
<Input
id="password"
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
placeholder={
isSignUp ? "Create a password" : "Enter your password"
}
className="pl-10 bg-background/50 border-border/50 focus:border-aethex-400"
required
minLength={isSignUp ? 6 : undefined}
/>
<span className="text-muted-foreground">Remember me</span>
</label>
<button
type="button"
className="text-aethex-400 hover:underline"
onClick={() => {
setResetEmail(email || "");
setShowReset(true);
}}
>
Forgot password?
</button>
</div>
{isSignUp && (
<p className="text-xs text-muted-foreground">
Password must be at least 6 characters long
</p>
)}
</div>
)}
<Button
type="submit"
className="w-full bg-gradient-to-r from-aethex-500 to-neon-blue hover:from-aethex-600 hover:to-neon-blue/90 hover-lift interactive-scale glow-blue"
disabled={
!email || !password || (isSignUp && !fullName) || isLoading
}
>
<LogIn className="h-4 w-4 mr-2" />
{isSignUp ? "Create Account" : "Sign In"}
<ArrowRight className="h-4 w-4 ml-2" />
</Button>
</form>
{!isSignUp && (
<div className="flex items-center justify-between text-sm">
<label className="flex items-center space-x-2 cursor-pointer">
<input
type="checkbox"
className="rounded border-border/50"
/>
<span className="text-muted-foreground">
Remember me
</span>
</label>
<button
type="button"
className="text-aethex-400 hover:underline"
onClick={() => {
setResetEmail(email || "");
setShowReset(true);
}}
>
Forgot password?
</button>
</div>
)}
<div className="text-center pt-4">
<p className="text-sm text-muted-foreground">
{isSignUp
? "Already have an account?"
: "Don't have an account?"}{" "}
<button
onClick={() => {
setIsSignUp((prev) => !prev);
setManualVerificationLink(null);
}}
className="text-aethex-400 hover:underline font-medium"
<Button
type="submit"
className="w-full bg-gradient-to-r from-aethex-500 to-neon-blue hover:from-aethex-600 hover:to-neon-blue/90 hover-lift interactive-scale glow-blue"
disabled={
!email ||
!password ||
(isSignUp && !fullName) ||
isLoading
}
>
{isSignUp ? "Sign In" : "Join AeThex"}
</button>
</p>
</div>
</CardContent>
</Card>
<LogIn className="h-4 w-4 mr-2" />
{isSignUp ? "Create Account" : "Sign In"}
<ArrowRight className="h-4 w-4 ml-2" />
</Button>
</form>
{/* Security Notice */}
<div className="mt-6 text-center animate-fade-in">
<p className="text-xs text-muted-foreground">
🔒 Your data is protected with enterprise-grade security
</p>
<div className="text-center pt-4">
<p className="text-sm text-muted-foreground">
{isSignUp
? "Already have an account?"
: "Don't have an account?"}{" "}
<button
onClick={() => {
setIsSignUp((prev) => !prev);
setManualVerificationLink(null);
}}
className="text-aethex-400 hover:underline font-medium"
>
{isSignUp ? "Sign In" : "Join AeThex"}
</button>
</p>
</div>
</CardContent>
</Card>
{/* Security Notice */}
<div className="mt-6 text-center animate-fade-in">
<p className="text-xs text-muted-foreground">
🔒 Your data is protected with enterprise-grade security
</p>
</div>
</div>
</div>
</div>
<Dialog open={showReset} onOpenChange={setShowReset}>
<DialogContent>
<DialogHeader>
<DialogTitle>Reset your password</DialogTitle>
<DialogDescription>
Enter the email associated with your account. We'll send a reset
link.
</DialogDescription>
</DialogHeader>
<div className="space-y-3 py-2">
<Label htmlFor="resetEmail" className="text-sm font-medium">
Email Address
</Label>
<Input
id="resetEmail"
type="email"
value={resetEmail}
onChange={(e) => setResetEmail(e.target.value)}
placeholder="you@example.com"
/>
</div>
<DialogFooter className="sm:justify-end">
<DialogClose asChild>
<Button variant="outline">Cancel</Button>
</DialogClose>
<Button
onClick={async () => {
if (!resetEmail) return;
setIsLoading(true);
try {
await requestPasswordReset(resetEmail);
setShowReset(false);
} catch {}
setIsLoading(false);
}}
disabled={!resetEmail || isLoading}
>
Send reset link
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
</Layout>
</>
<Dialog open={showReset} onOpenChange={setShowReset}>
<DialogContent>
<DialogHeader>
<DialogTitle>Reset your password</DialogTitle>
<DialogDescription>
Enter the email associated with your account. We'll send a reset
link.
</DialogDescription>
</DialogHeader>
<div className="space-y-3 py-2">
<Label htmlFor="resetEmail" className="text-sm font-medium">
Email Address
</Label>
<Input
id="resetEmail"
type="email"
value={resetEmail}
onChange={(e) => setResetEmail(e.target.value)}
placeholder="you@example.com"
/>
</div>
<DialogFooter className="sm:justify-end">
<DialogClose asChild>
<Button variant="outline">Cancel</Button>
</DialogClose>
<Button
onClick={async () => {
if (!resetEmail) return;
setIsLoading(true);
try {
await requestPasswordReset(resetEmail);
setShowReset(false);
} catch {}
setIsLoading(false);
}}
disabled={!resetEmail || isLoading}
>
Send reset link
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
</Layout>
</>
);
}

View file

@ -277,412 +277,424 @@ export default function MentorshipPrograms() {
return (
<>
<SEO pageTitle="Mentorship" description="AeThex mentorship programs: 1:1 guidance, workshops, and boot camps to accelerate your journey." canonical={typeof window!== 'undefined' ? window.location.href : undefined as any} />
<Layout>
<div className="min-h-screen bg-aethex-gradient">
{/* Hero Section */}
<section className="relative py-16 sm:py-24 lg:py-32 overflow-hidden">
<div className="absolute inset-0 opacity-10">
{[...Array(25)].map((_, i) => (
<div
key={i}
className="absolute text-emerald-400/80 animate-float"
style={{
left: `${Math.random() * 100}%`,
top: `${Math.random() * 100}%`,
animationDelay: `${Math.random() * 3}s`,
animationDuration: `${3 + Math.random() * 2}s`,
fontSize: `${8 + Math.random() * 6}px`,
}}
>
{"🎓📚💡🚀".charAt(Math.floor(Math.random() * 4))}
</div>
))}
</div>
<div className="container mx-auto px-4 text-center relative z-10">
<div className="max-w-4xl mx-auto space-y-8">
<Badge
variant="outline"
className="border-emerald-400/50 text-emerald-300 animate-bounce-gentle"
>
<GraduationCap className="h-3 w-3 mr-1" />
Mentorship & Education Division
</Badge>
<h1 className="text-4xl sm:text-5xl lg:text-6xl font-bold leading-tight">
<span className="bg-gradient-to-r from-emerald-300 via-aethex-400 to-neon-blue bg-clip-text text-transparent">
Accelerate Your Tech Journey
</span>
</h1>
<p className="text-xl text-muted-foreground max-w-3xl mx-auto">
Learn from industry experts through personalized mentorship,
hands-on workshops, and intensive boot camps designed to
fast-track your technology career.
</p>
<div className="flex flex-col sm:flex-row justify-center gap-4">
<Button
asChild
size="lg"
className="bg-gradient-to-r from-emerald-400 to-neon-blue shadow-[0_0_25px_rgba(16,185,129,0.35)] hover:from-emerald-500 hover:to-aethex-500 hover-lift"
<SEO
pageTitle="Mentorship"
description="AeThex mentorship programs: 1:1 guidance, workshops, and boot camps to accelerate your journey."
canonical={
typeof window !== "undefined"
? window.location.href
: (undefined as any)
}
/>
<Layout>
<div className="min-h-screen bg-aethex-gradient">
{/* Hero Section */}
<section className="relative py-16 sm:py-24 lg:py-32 overflow-hidden">
<div className="absolute inset-0 opacity-10">
{[...Array(25)].map((_, i) => (
<div
key={i}
className="absolute text-emerald-400/80 animate-float"
style={{
left: `${Math.random() * 100}%`,
top: `${Math.random() * 100}%`,
animationDelay: `${Math.random() * 3}s`,
animationDuration: `${3 + Math.random() * 2}s`,
fontSize: `${8 + Math.random() * 6}px`,
}}
>
<Link
to="/engage#mentorship"
className="flex items-center space-x-2"
>
<BookOpen className="h-5 w-5" />
<span>Apply Now</span>
<ArrowRight className="h-5 w-5" />
</Link>
</Button>
<Button
asChild
{"🎓📚💡🚀".charAt(Math.floor(Math.random() * 4))}
</div>
))}
</div>
<div className="container mx-auto px-4 text-center relative z-10">
<div className="max-w-4xl mx-auto space-y-8">
<Badge
variant="outline"
size="lg"
className="border-emerald-400/50 text-emerald-200 hover:border-emerald-400 hover:bg-emerald-500/10 hover-lift"
className="border-emerald-400/50 text-emerald-300 animate-bounce-gentle"
>
<Link to="/docs">Program Details</Link>
</Button>
<GraduationCap className="h-3 w-3 mr-1" />
Mentorship & Education Division
</Badge>
<h1 className="text-4xl sm:text-5xl lg:text-6xl font-bold leading-tight">
<span className="bg-gradient-to-r from-emerald-300 via-aethex-400 to-neon-blue bg-clip-text text-transparent">
Accelerate Your Tech Journey
</span>
</h1>
<p className="text-xl text-muted-foreground max-w-3xl mx-auto">
Learn from industry experts through personalized mentorship,
hands-on workshops, and intensive boot camps designed to
fast-track your technology career.
</p>
<div className="flex flex-col sm:flex-row justify-center gap-4">
<Button
asChild
size="lg"
className="bg-gradient-to-r from-emerald-400 to-neon-blue shadow-[0_0_25px_rgba(16,185,129,0.35)] hover:from-emerald-500 hover:to-aethex-500 hover-lift"
>
<Link
to="/engage#mentorship"
className="flex items-center space-x-2"
>
<BookOpen className="h-5 w-5" />
<span>Apply Now</span>
<ArrowRight className="h-5 w-5" />
</Link>
</Button>
<Button
asChild
variant="outline"
size="lg"
className="border-emerald-400/50 text-emerald-200 hover:border-emerald-400 hover:bg-emerald-500/10 hover-lift"
>
<Link to="/docs">Program Details</Link>
</Button>
</div>
</div>
</div>
</div>
</section>
</section>
{/* Programs Overview */}
<section className="py-16 sm:py-20 bg-background/30">
<div className="container mx-auto px-4">
<div className="text-center mb-16 animate-slide-up">
<h2 className="text-3xl lg:text-4xl font-bold text-gradient mb-4">
Mentorship Programs
</h2>
<p className="text-lg text-muted-foreground max-w-2xl mx-auto">
Choose the learning format that best fits your schedule and
goals
</p>
</div>
{/* Programs Overview */}
<section className="py-16 sm:py-20 bg-background/30">
<div className="container mx-auto px-4">
<div className="text-center mb-16 animate-slide-up">
<h2 className="text-3xl lg:text-4xl font-bold text-gradient mb-4">
Mentorship Programs
</h2>
<p className="text-lg text-muted-foreground max-w-2xl mx-auto">
Choose the learning format that best fits your schedule and
goals
</p>
</div>
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-2 gap-6 max-w-6xl mx-auto">
{programs.map((program, index) => {
const Icon = program.icon;
return (
<Card
key={index}
className="relative overflow-hidden border-border/50 hover:border-aethex-400/50 transition-all duration-500 hover-lift animate-scale-in"
style={{ animationDelay: `${index * 0.1}s` }}
>
<CardHeader>
<div className="flex items-start justify-between">
<div className="flex items-center space-x-4">
<div
className={`p-3 rounded-lg bg-gradient-to-r ${program.color}`}
>
<Icon className="h-6 w-6 text-white" />
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-2 gap-6 max-w-6xl mx-auto">
{programs.map((program, index) => {
const Icon = program.icon;
return (
<Card
key={index}
className="relative overflow-hidden border-border/50 hover:border-aethex-400/50 transition-all duration-500 hover-lift animate-scale-in"
style={{ animationDelay: `${index * 0.1}s` }}
>
<CardHeader>
<div className="flex items-start justify-between">
<div className="flex items-center space-x-4">
<div
className={`p-3 rounded-lg bg-gradient-to-r ${program.color}`}
>
<Icon className="h-6 w-6 text-white" />
</div>
<div>
<CardTitle className="text-xl">
{program.title}
</CardTitle>
<CardDescription className="mt-1">
{program.description}
</CardDescription>
</div>
</div>
<div>
<CardTitle className="text-xl">
{program.title}
</CardTitle>
<CardDescription className="mt-1">
{program.description}
</CardDescription>
<Badge variant="outline" className="shrink-0">
{program.participants}
</Badge>
</div>
</CardHeader>
<CardContent className="space-y-4">
<div className="grid grid-cols-1 sm:grid-cols-2 gap-2">
{program.features.map((feature, featureIndex) => (
<div
key={featureIndex}
className="flex items-center space-x-2 text-sm"
>
<CheckCircle className="h-3 w-3 text-emerald-400 flex-shrink-0" />
<span>{feature}</span>
</div>
))}
</div>
<div className="grid grid-cols-1 sm:grid-cols-3 gap-4 pt-4 border-t border-border/30">
<div className="text-center">
<Clock className="h-4 w-4 text-muted-foreground mx-auto mb-1" />
<div className="text-xs text-muted-foreground">
{program.duration}
</div>
</div>
<div className="text-center">
<Calendar className="h-4 w-4 text-muted-foreground mx-auto mb-1" />
<div className="text-xs text-muted-foreground">
{program.commitment}
</div>
</div>
<div className="text-center">
<span className="text-sm font-semibold text-emerald-400">
{program.price}
</span>
</div>
</div>
<Badge variant="outline" className="shrink-0">
{program.participants}
</Badge>
</CardContent>
</Card>
);
})}
</div>
</div>
</section>
{/* Learning Tracks */}
<section className="py-16 sm:py-20">
<div className="container mx-auto px-4">
<div className="text-center mb-16 animate-slide-up">
<h2 className="text-3xl lg:text-4xl font-bold text-gradient mb-4">
Specialized Learning Tracks
</h2>
<p className="text-lg text-muted-foreground">
Focused curricula designed by industry experts
</p>
</div>
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-2 lg:grid-cols-4 gap-6 max-w-6xl mx-auto">
{tracks.map((track, index) => {
const Icon = track.icon;
const isSelected = selectedTrack === index;
return (
<Card
key={index}
className={`cursor-pointer transition-all duration-300 hover-lift animate-scale-in ${
isSelected
? "border-emerald-500 shadow-[0_0_25px_rgba(16,185,129,0.35)] scale-105"
: "border-border/50 hover:border-emerald-400/60"
}`}
style={{ animationDelay: `${index * 0.1}s` }}
onClick={() => setSelectedTrack(index)}
>
<CardHeader className="text-center">
<Icon className="h-12 w-12 text-emerald-400 mx-auto mb-3" />
<CardTitle
className={`text-lg ${
isSelected
? "bg-gradient-to-r from-emerald-400 to-aethex-500 bg-clip-text text-transparent"
: ""
}`}
>
{track.name}
</CardTitle>
<CardDescription className="text-sm">
{track.description}
</CardDescription>
</CardHeader>
<CardContent className="space-y-3">
<div className="flex flex-wrap gap-1">
{track.skills.slice(0, 4).map((skill, skillIndex) => (
<Badge
key={skillIndex}
variant="secondary"
className="text-xs"
>
{skill}
</Badge>
))}
</div>
<div className="text-center space-y-1">
<div className="text-sm text-muted-foreground">
{track.level}
</div>
<div className="text-xs text-muted-foreground">
{track.duration} {track.projects} projects
</div>
</div>
</CardContent>
</Card>
);
})}
</div>
</div>
</section>
{/* Featured Mentors */}
<section className="py-16 sm:py-20 bg-background/30">
<div className="container mx-auto px-4">
<div className="text-center mb-16 animate-slide-up">
<h2 className="text-3xl lg:text-4xl font-bold text-gradient mb-4">
Meet Our Expert Mentors
</h2>
<p className="text-lg text-muted-foreground">
Learn from industry leaders at top technology companies
</p>
</div>
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-6 max-w-4xl mx-auto">
{mentors.map((mentor, index) => (
<Card
key={index}
className="border-border/50 hover:border-aethex-400/50 transition-all duration-300 hover-lift animate-scale-in"
style={{ animationDelay: `${index * 0.2}s` }}
>
<CardContent className="p-6 text-center">
<img
src={mentor.avatar}
alt={mentor.name}
className="w-24 h-24 rounded-full mx-auto mb-4 ring-4 ring-emerald-400/20"
/>
<h3 className="font-semibold text-lg bg-gradient-to-r from-emerald-400 to-aethex-500 bg-clip-text text-transparent">
{mentor.name}
</h3>
<p className="text-sm text-muted-foreground">
{mentor.title}
</p>
<p className="text-sm font-medium text-emerald-400">
{mentor.company}
</p>
<div className="mt-4 space-y-2">
<div className="flex justify-between text-sm">
<span>Experience:</span>
<span className="font-medium">
{mentor.experience}
</span>
</div>
<div className="flex justify-between text-sm">
<span>Students Mentored:</span>
<span className="font-medium">
{mentor.students}+
</span>
</div>
<div className="flex justify-between text-sm">
<span>Rating:</span>
<div className="flex items-center space-x-1">
<Star className="h-3 w-3 text-yellow-500 fill-current" />
<span className="font-medium">{mentor.rating}</span>
</div>
</div>
</div>
<Badge variant="outline" className="mt-3 text-xs">
{mentor.specialty}
</Badge>
</CardContent>
</Card>
))}
</div>
</div>
</section>
{/* Success Stories */}
<section className="py-16 sm:py-20">
<div className="container mx-auto px-4">
<div className="text-center mb-16 animate-slide-up">
<h2 className="text-3xl lg:text-4xl font-bold text-gradient mb-4">
Success Stories
</h2>
<p className="text-lg text-muted-foreground">
Real career transformations from our mentorship programs
</p>
</div>
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6 max-w-6xl mx-auto">
{testimonials.map((testimonial, index) => (
<Card
key={index}
className="border-border/50 hover:border-aethex-400/50 transition-all duration-300 hover-lift animate-slide-up"
style={{ animationDelay: `${index * 0.2}s` }}
>
<CardHeader>
<div className="flex items-center space-x-3">
<div className="w-12 h-12 rounded-full bg-gradient-to-r from-emerald-500 via-aethex-500 to-neon-blue flex items-center justify-center text-white font-semibold">
{testimonial.name.charAt(0)}
</div>
<div>
<h3 className="font-semibold">{testimonial.name}</h3>
<p className="text-sm text-muted-foreground">
{testimonial.role}
</p>
<Badge variant="outline" className="text-xs">
{testimonial.company}
</Badge>
</div>
</div>
</CardHeader>
<CardContent className="space-y-4">
<div className="grid grid-cols-1 sm:grid-cols-2 gap-2">
{program.features.map((feature, featureIndex) => (
<div
key={featureIndex}
className="flex items-center space-x-2 text-sm"
>
<CheckCircle className="h-3 w-3 text-emerald-400 flex-shrink-0" />
<span>{feature}</span>
</div>
))}
</div>
<div className="grid grid-cols-1 sm:grid-cols-3 gap-4 pt-4 border-t border-border/30">
<div className="text-center">
<Clock className="h-4 w-4 text-muted-foreground mx-auto mb-1" />
<div className="text-xs text-muted-foreground">
{program.duration}
</div>
</div>
<div className="text-center">
<Calendar className="h-4 w-4 text-muted-foreground mx-auto mb-1" />
<div className="text-xs text-muted-foreground">
{program.commitment}
</div>
</div>
<div className="text-center">
<span className="text-sm font-semibold text-emerald-400">
{program.price}
</span>
</div>
<blockquote className="text-sm italic text-muted-foreground">
"{testimonial.content}"
</blockquote>
<div className="flex justify-between text-xs text-muted-foreground">
<span>Program: {testimonial.program}</span>
<span>Mentor: {testimonial.mentor}</span>
</div>
</CardContent>
</Card>
);
})}
))}
</div>
</div>
</div>
</section>
</section>
{/* Learning Tracks */}
<section className="py-16 sm:py-20">
<div className="container mx-auto px-4">
<div className="text-center mb-16 animate-slide-up">
<h2 className="text-3xl lg:text-4xl font-bold text-gradient mb-4">
Specialized Learning Tracks
</h2>
<p className="text-lg text-muted-foreground">
Focused curricula designed by industry experts
</p>
</div>
{/* CTA Section */}
<section className="py-16 sm:py-20 bg-background/30">
<div className="container mx-auto px-4 text-center">
<div className="max-w-3xl mx-auto space-y-8 animate-scale-in">
<h2 className="text-3xl lg:text-4xl font-bold bg-gradient-to-r from-emerald-300 via-aethex-400 to-neon-blue bg-clip-text text-transparent">
Start Your Learning Journey Today
</h2>
<p className="text-xl text-muted-foreground">
Join thousands of developers who have accelerated their
careers through our mentorship programs.
</p>
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-2 lg:grid-cols-4 gap-6 max-w-6xl mx-auto">
{tracks.map((track, index) => {
const Icon = track.icon;
const isSelected = selectedTrack === index;
return (
<Card
key={index}
className={`cursor-pointer transition-all duration-300 hover-lift animate-scale-in ${
isSelected
? "border-emerald-500 shadow-[0_0_25px_rgba(16,185,129,0.35)] scale-105"
: "border-border/50 hover:border-emerald-400/60"
}`}
style={{ animationDelay: `${index * 0.1}s` }}
onClick={() => setSelectedTrack(index)}
<div className="flex flex-col sm:flex-row justify-center gap-4">
<Button
asChild
size="lg"
className="bg-gradient-to-r from-emerald-400 to-neon-blue shadow-[0_0_25px_rgba(16,185,129,0.35)] hover:from-emerald-500 hover:to-aethex-500 hover-lift text-base sm:text-lg px-6 py-4 sm:px-8 sm:py-6"
>
<CardHeader className="text-center">
<Icon className="h-12 w-12 text-emerald-400 mx-auto mb-3" />
<CardTitle
className={`text-lg ${
isSelected
? "bg-gradient-to-r from-emerald-400 to-aethex-500 bg-clip-text text-transparent"
: ""
}`}
>
{track.name}
</CardTitle>
<CardDescription className="text-sm">
{track.description}
</CardDescription>
</CardHeader>
<CardContent className="space-y-3">
<div className="flex flex-wrap gap-1">
{track.skills.slice(0, 4).map((skill, skillIndex) => (
<Badge
key={skillIndex}
variant="secondary"
className="text-xs"
>
{skill}
</Badge>
))}
</div>
<Link
to="/engage#mentorship"
className="flex items-center space-x-2"
>
<Heart className="h-5 w-5" />
<span>Apply for Mentorship</span>
<ArrowRight className="h-5 w-5" />
</Link>
</Button>
<Button
asChild
variant="outline"
size="lg"
className="border-emerald-400/60 text-emerald-200 hover:border-emerald-400 hover:bg-emerald-500/10 hover-lift text-base sm:text-lg px-6 py-4 sm:px-8 sm:py-6"
>
<Link to="/docs/curriculum">View Curriculum</Link>
</Button>
</div>
<div className="text-center space-y-1">
<div className="text-sm text-muted-foreground">
{track.level}
</div>
<div className="text-xs text-muted-foreground">
{track.duration} {track.projects} projects
</div>
</div>
</CardContent>
</Card>
);
})}
</div>
</div>
</section>
{/* Featured Mentors */}
<section className="py-16 sm:py-20 bg-background/30">
<div className="container mx-auto px-4">
<div className="text-center mb-16 animate-slide-up">
<h2 className="text-3xl lg:text-4xl font-bold text-gradient mb-4">
Meet Our Expert Mentors
</h2>
<p className="text-lg text-muted-foreground">
Learn from industry leaders at top technology companies
</p>
</div>
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-6 max-w-4xl mx-auto">
{mentors.map((mentor, index) => (
<Card
key={index}
className="border-border/50 hover:border-aethex-400/50 transition-all duration-300 hover-lift animate-scale-in"
style={{ animationDelay: `${index * 0.2}s` }}
>
<CardContent className="p-6 text-center">
<img
src={mentor.avatar}
alt={mentor.name}
className="w-24 h-24 rounded-full mx-auto mb-4 ring-4 ring-emerald-400/20"
/>
<h3 className="font-semibold text-lg bg-gradient-to-r from-emerald-400 to-aethex-500 bg-clip-text text-transparent">
{mentor.name}
</h3>
<div className="grid grid-cols-1 sm:grid-cols-3 gap-8 mt-12">
<div className="text-center">
<MessageCircle className="h-8 w-8 text-emerald-400 mx-auto mb-2" />
<h3 className="font-semibold">24/7 Support</h3>
<p className="text-sm text-muted-foreground">
{mentor.title}
Always available
</p>
<p className="text-sm font-medium text-emerald-400">
{mentor.company}
</div>
<div className="text-center">
<Video className="h-8 w-8 text-emerald-400 mx-auto mb-2" />
<h3 className="font-semibold">Live Sessions</h3>
<p className="text-sm text-muted-foreground">
Interactive learning
</p>
<div className="mt-4 space-y-2">
<div className="flex justify-between text-sm">
<span>Experience:</span>
<span className="font-medium">{mentor.experience}</span>
</div>
<div className="flex justify-between text-sm">
<span>Students Mentored:</span>
<span className="font-medium">{mentor.students}+</span>
</div>
<div className="flex justify-between text-sm">
<span>Rating:</span>
<div className="flex items-center space-x-1">
<Star className="h-3 w-3 text-yellow-500 fill-current" />
<span className="font-medium">{mentor.rating}</span>
</div>
</div>
</div>
<Badge variant="outline" className="mt-3 text-xs">
{mentor.specialty}
</Badge>
</CardContent>
</Card>
))}
</div>
</div>
</section>
{/* Success Stories */}
<section className="py-16 sm:py-20">
<div className="container mx-auto px-4">
<div className="text-center mb-16 animate-slide-up">
<h2 className="text-3xl lg:text-4xl font-bold text-gradient mb-4">
Success Stories
</h2>
<p className="text-lg text-muted-foreground">
Real career transformations from our mentorship programs
</p>
</div>
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6 max-w-6xl mx-auto">
{testimonials.map((testimonial, index) => (
<Card
key={index}
className="border-border/50 hover:border-aethex-400/50 transition-all duration-300 hover-lift animate-slide-up"
style={{ animationDelay: `${index * 0.2}s` }}
>
<CardHeader>
<div className="flex items-center space-x-3">
<div className="w-12 h-12 rounded-full bg-gradient-to-r from-emerald-500 via-aethex-500 to-neon-blue flex items-center justify-center text-white font-semibold">
{testimonial.name.charAt(0)}
</div>
<div>
<h3 className="font-semibold">{testimonial.name}</h3>
<p className="text-sm text-muted-foreground">
{testimonial.role}
</p>
<Badge variant="outline" className="text-xs">
{testimonial.company}
</Badge>
</div>
</div>
</CardHeader>
<CardContent className="space-y-4">
<blockquote className="text-sm italic text-muted-foreground">
"{testimonial.content}"
</blockquote>
<div className="flex justify-between text-xs text-muted-foreground">
<span>Program: {testimonial.program}</span>
<span>Mentor: {testimonial.mentor}</span>
</div>
</CardContent>
</Card>
))}
</div>
</div>
</section>
{/* CTA Section */}
<section className="py-16 sm:py-20 bg-background/30">
<div className="container mx-auto px-4 text-center">
<div className="max-w-3xl mx-auto space-y-8 animate-scale-in">
<h2 className="text-3xl lg:text-4xl font-bold bg-gradient-to-r from-emerald-300 via-aethex-400 to-neon-blue bg-clip-text text-transparent">
Start Your Learning Journey Today
</h2>
<p className="text-xl text-muted-foreground">
Join thousands of developers who have accelerated their careers
through our mentorship programs.
</p>
<div className="flex flex-col sm:flex-row justify-center gap-4">
<Button
asChild
size="lg"
className="bg-gradient-to-r from-emerald-400 to-neon-blue shadow-[0_0_25px_rgba(16,185,129,0.35)] hover:from-emerald-500 hover:to-aethex-500 hover-lift text-base sm:text-lg px-6 py-4 sm:px-8 sm:py-6"
>
<Link
to="/engage#mentorship"
className="flex items-center space-x-2"
>
<Heart className="h-5 w-5" />
<span>Apply for Mentorship</span>
<ArrowRight className="h-5 w-5" />
</Link>
</Button>
<Button
asChild
variant="outline"
size="lg"
className="border-emerald-400/60 text-emerald-200 hover:border-emerald-400 hover:bg-emerald-500/10 hover-lift text-base sm:text-lg px-6 py-4 sm:px-8 sm:py-6"
>
<Link to="/docs/curriculum">View Curriculum</Link>
</Button>
</div>
<div className="grid grid-cols-1 sm:grid-cols-3 gap-8 mt-12">
<div className="text-center">
<MessageCircle className="h-8 w-8 text-emerald-400 mx-auto mb-2" />
<h3 className="font-semibold">24/7 Support</h3>
<p className="text-sm text-muted-foreground">
Always available
</p>
</div>
<div className="text-center">
<Video className="h-8 w-8 text-emerald-400 mx-auto mb-2" />
<h3 className="font-semibold">Live Sessions</h3>
<p className="text-sm text-muted-foreground">
Interactive learning
</p>
</div>
<div className="text-center">
<Award className="h-8 w-8 text-emerald-400 mx-auto mb-2" />
<h3 className="font-semibold">Certification</h3>
<p className="text-sm text-muted-foreground">
Industry recognized
</p>
</div>
<div className="text-center">
<Award className="h-8 w-8 text-emerald-400 mx-auto mb-2" />
<h3 className="font-semibold">Certification</h3>
<p className="text-sm text-muted-foreground">
Industry recognized
</p>
</div>
</div>
</div>
</div>
</div>
</section>
</div>
</Layout>
</>
</section>
</div>
</Layout>
</>
);
}

View file

@ -12,7 +12,13 @@ import {
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Textarea } from "@/components/ui/textarea";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import { Badge } from "@/components/ui/badge";
import { aethexCollabService } from "@/lib/aethex-collab-service";
import LoadingScreen from "@/components/LoadingScreen";
@ -74,10 +80,19 @@ export default function ProjectBoard() {
blocked: [],
};
const normalized = tasks.filter((t) => {
if (q && !String(t.title || "").toLowerCase().includes(q.toLowerCase()) && !String(t.description || "").toLowerCase().includes(q.toLowerCase())) {
if (
q &&
!String(t.title || "")
.toLowerCase()
.includes(q.toLowerCase()) &&
!String(t.description || "")
.toLowerCase()
.includes(q.toLowerCase())
) {
return false;
}
if (filterAssignee && String(t.assignee_id || "") !== filterAssignee) return false;
if (filterAssignee && String(t.assignee_id || "") !== filterAssignee)
return false;
if (filterStatus && String(t.status || "") !== filterStatus) return false;
return true;
});
@ -125,172 +140,41 @@ export default function ProjectBoard() {
return (
<>
<SEO pageTitle="Project Board" description="Kanban task tracking for your AeThex project: statuses, assignees, and due dates." canonical={typeof window!== 'undefined' ? window.location.href : undefined as any} />
<Layout>
<div className="min-h-screen bg-[radial-gradient(circle_at_top,_rgba(110,141,255,0.12),transparent_60%)] py-10">
<div className="mx-auto w-full max-w-6xl px-4 lg:px-6 space-y-6">
<section className="rounded-3xl border border-border/40 bg-background/80 p-6 shadow-2xl backdrop-blur">
<h1 className="text-3xl font-semibold text-foreground">
Project Board
</h1>
<p className="mt-1 text-sm text-muted-foreground">
Track tasks by status. Filters, assignees, and due dates enabled.
</p>
<div className="mt-4 grid gap-3 md:grid-cols-4">
<Input
placeholder="Search tasks…"
value={q}
onChange={(e) => setQ(e.target.value)}
/>
<Select value={filterAssignee} onValueChange={setFilterAssignee}>
<SelectTrigger>
<SelectValue placeholder="Filter by assignee" />
</SelectTrigger>
<SelectContent>
<SelectItem value="">All assignees</SelectItem>
{members.map((m) => (
<SelectItem key={m.user_id} value={m.user_id}>
{m.user?.full_name || m.user?.username || m.user_id}
</SelectItem>
))}
</SelectContent>
</Select>
<Select value={filterStatus} onValueChange={setFilterStatus}>
<SelectTrigger>
<SelectValue placeholder="Filter by status" />
</SelectTrigger>
<SelectContent>
<SelectItem value="">All statuses</SelectItem>
<SelectItem value="todo">To do</SelectItem>
<SelectItem value="doing">In progress</SelectItem>
<SelectItem value="done">Done</SelectItem>
<SelectItem value="blocked">Blocked</SelectItem>
</SelectContent>
</Select>
<div className="flex gap-2">
<Button variant="outline" onClick={() => { setQ(""); setFilterAssignee(""); setFilterStatus(""); }}>
Reset filters
</Button>
</div>
</div>
</section>
<div className="grid gap-6 lg:grid-cols-[minmax(0,1fr)_minmax(0,1fr)_minmax(0,1fr)_minmax(0,1fr)]">
{columns.map((col) => (
<Card
key={col.key}
className="rounded-3xl border-border/40 bg-background/70 shadow-xl backdrop-blur-lg"
>
<CardHeader>
<CardTitle className="text-base flex items-center gap-2">
{col.title}
<Badge
variant="outline"
className="border-border/50 text-xs"
>
{grouped[col.key].length}
</Badge>
</CardTitle>
<CardDescription>{col.hint}</CardDescription>
</CardHeader>
<CardContent className="space-y-3">
{grouped[col.key].length === 0 ? (
<p className="text-sm text-muted-foreground">No tasks.</p>
) : (
grouped[col.key].map((t) => (
<div
key={t.id}
className="rounded-2xl border border-border/30 bg-background/60 p-3"
>
<div className="font-medium text-foreground">
{t.title}
</div>
{t.description ? (
<p className="text-xs text-muted-foreground mt-1">
{t.description}
</p>
) : null}
<div className="mt-2 flex flex-wrap items-center justify-between gap-2">
<div className="flex flex-wrap items-center gap-2 text-xs text-muted-foreground">
{t.assignee ? (
<span className="inline-flex items-center gap-1">
<span className="inline-block h-4 w-4 rounded-full bg-cover bg-center" style={{ backgroundImage: `url(${t.assignee.avatar_url || ""})` }} />
{t.assignee.full_name || t.assignee.username || "Assignee"}
</span>
) : (
<span>Unassigned</span>
)}
{t.due_date ? (
<span> Due {new Date(t.due_date).toLocaleDateString()}</span>
) : null}
</div>
<div className="flex flex-wrap gap-2">
{columns.map((k) => (
<Button
key={`${t.id}-${k.key}`}
size="xs"
variant="outline"
onClick={() => move(t.id, k.key)}
>
{k.title}
</Button>
))}
<Button
size="xs"
variant="ghost"
onClick={async () => {
if (!user?.id) return;
await aethexCollabService.updateTask(t.id, { assignee_id: user.id });
await load();
}}
>
Assign me
</Button>
<Button
size="xs"
variant="destructive"
onClick={async () => {
await aethexCollabService.deleteTask(t.id);
await load();
}}
>
Delete
</Button>
</div>
</div>
</div>
))
)}
</CardContent>
</Card>
))}
</div>
<Card className="rounded-3xl border-border/40 bg-background/70 shadow-xl backdrop-blur-lg">
<CardHeader>
<CardTitle className="text-lg">Add task</CardTitle>
<CardDescription>
Keep titles concise; details optional.
</CardDescription>
</CardHeader>
<CardContent className="space-y-3">
<Input
placeholder="Task title"
value={title}
onChange={(e) => setTitle(e.target.value)}
/>
<Textarea
placeholder="Description (optional)"
value={description}
onChange={(e) => setDescription(e.target.value)}
/>
<div className="grid gap-3 md:grid-cols-3">
<Select value={assigneeId} onValueChange={setAssigneeId}>
<SEO
pageTitle="Project Board"
description="Kanban task tracking for your AeThex project: statuses, assignees, and due dates."
canonical={
typeof window !== "undefined"
? window.location.href
: (undefined as any)
}
/>
<Layout>
<div className="min-h-screen bg-[radial-gradient(circle_at_top,_rgba(110,141,255,0.12),transparent_60%)] py-10">
<div className="mx-auto w-full max-w-6xl px-4 lg:px-6 space-y-6">
<section className="rounded-3xl border border-border/40 bg-background/80 p-6 shadow-2xl backdrop-blur">
<h1 className="text-3xl font-semibold text-foreground">
Project Board
</h1>
<p className="mt-1 text-sm text-muted-foreground">
Track tasks by status. Filters, assignees, and due dates
enabled.
</p>
<div className="mt-4 grid gap-3 md:grid-cols-4">
<Input
placeholder="Search tasks…"
value={q}
onChange={(e) => setQ(e.target.value)}
/>
<Select
value={filterAssignee}
onValueChange={setFilterAssignee}
>
<SelectTrigger>
<SelectValue placeholder="Assign to…" />
<SelectValue placeholder="Filter by assignee" />
</SelectTrigger>
<SelectContent>
<SelectItem value="">Unassigned</SelectItem>
<SelectItem value="">All assignees</SelectItem>
{members.map((m) => (
<SelectItem key={m.user_id} value={m.user_id}>
{m.user?.full_name || m.user?.username || m.user_id}
@ -298,26 +182,188 @@ export default function ProjectBoard() {
))}
</SelectContent>
</Select>
<Input
type="date"
value={dueDate}
onChange={(e) => setDueDate(e.target.value)}
/>
<div className="flex justify-end">
<Select value={filterStatus} onValueChange={setFilterStatus}>
<SelectTrigger>
<SelectValue placeholder="Filter by status" />
</SelectTrigger>
<SelectContent>
<SelectItem value="">All statuses</SelectItem>
<SelectItem value="todo">To do</SelectItem>
<SelectItem value="doing">In progress</SelectItem>
<SelectItem value="done">Done</SelectItem>
<SelectItem value="blocked">Blocked</SelectItem>
</SelectContent>
</Select>
<div className="flex gap-2">
<Button
onClick={handleCreate}
disabled={creating || !title.trim()}
className="rounded-full bg-gradient-to-r from-aethex-500 to-neon-blue text-white"
variant="outline"
onClick={() => {
setQ("");
setFilterAssignee("");
setFilterStatus("");
}}
>
{creating ? "Creating..." : "Create task"}
Reset filters
</Button>
</div>
</div>
</CardContent>
</Card>
</section>
<div className="grid gap-6 lg:grid-cols-[minmax(0,1fr)_minmax(0,1fr)_minmax(0,1fr)_minmax(0,1fr)]">
{columns.map((col) => (
<Card
key={col.key}
className="rounded-3xl border-border/40 bg-background/70 shadow-xl backdrop-blur-lg"
>
<CardHeader>
<CardTitle className="text-base flex items-center gap-2">
{col.title}
<Badge
variant="outline"
className="border-border/50 text-xs"
>
{grouped[col.key].length}
</Badge>
</CardTitle>
<CardDescription>{col.hint}</CardDescription>
</CardHeader>
<CardContent className="space-y-3">
{grouped[col.key].length === 0 ? (
<p className="text-sm text-muted-foreground">No tasks.</p>
) : (
grouped[col.key].map((t) => (
<div
key={t.id}
className="rounded-2xl border border-border/30 bg-background/60 p-3"
>
<div className="font-medium text-foreground">
{t.title}
</div>
{t.description ? (
<p className="text-xs text-muted-foreground mt-1">
{t.description}
</p>
) : null}
<div className="mt-2 flex flex-wrap items-center justify-between gap-2">
<div className="flex flex-wrap items-center gap-2 text-xs text-muted-foreground">
{t.assignee ? (
<span className="inline-flex items-center gap-1">
<span
className="inline-block h-4 w-4 rounded-full bg-cover bg-center"
style={{
backgroundImage: `url(${t.assignee.avatar_url || ""})`,
}}
/>
{t.assignee.full_name ||
t.assignee.username ||
"Assignee"}
</span>
) : (
<span>Unassigned</span>
)}
{t.due_date ? (
<span>
Due{" "}
{new Date(t.due_date).toLocaleDateString()}
</span>
) : null}
</div>
<div className="flex flex-wrap gap-2">
{columns.map((k) => (
<Button
key={`${t.id}-${k.key}`}
size="xs"
variant="outline"
onClick={() => move(t.id, k.key)}
>
{k.title}
</Button>
))}
<Button
size="xs"
variant="ghost"
onClick={async () => {
if (!user?.id) return;
await aethexCollabService.updateTask(t.id, {
assignee_id: user.id,
});
await load();
}}
>
Assign me
</Button>
<Button
size="xs"
variant="destructive"
onClick={async () => {
await aethexCollabService.deleteTask(t.id);
await load();
}}
>
Delete
</Button>
</div>
</div>
</div>
))
)}
</CardContent>
</Card>
))}
</div>
<Card className="rounded-3xl border-border/40 bg-background/70 shadow-xl backdrop-blur-lg">
<CardHeader>
<CardTitle className="text-lg">Add task</CardTitle>
<CardDescription>
Keep titles concise; details optional.
</CardDescription>
</CardHeader>
<CardContent className="space-y-3">
<Input
placeholder="Task title"
value={title}
onChange={(e) => setTitle(e.target.value)}
/>
<Textarea
placeholder="Description (optional)"
value={description}
onChange={(e) => setDescription(e.target.value)}
/>
<div className="grid gap-3 md:grid-cols-3">
<Select value={assigneeId} onValueChange={setAssigneeId}>
<SelectTrigger>
<SelectValue placeholder="Assign to…" />
</SelectTrigger>
<SelectContent>
<SelectItem value="">Unassigned</SelectItem>
{members.map((m) => (
<SelectItem key={m.user_id} value={m.user_id}>
{m.user?.full_name || m.user?.username || m.user_id}
</SelectItem>
))}
</SelectContent>
</Select>
<Input
type="date"
value={dueDate}
onChange={(e) => setDueDate(e.target.value)}
/>
<div className="flex justify-end">
<Button
onClick={handleCreate}
disabled={creating || !title.trim()}
className="rounded-full bg-gradient-to-r from-aethex-500 to-neon-blue text-white"
>
{creating ? "Creating..." : "Create task"}
</Button>
</div>
</div>
</CardContent>
</Card>
</div>
</div>
</div>
</Layout>
</>
</Layout>
</>
);
}

View file

@ -454,7 +454,8 @@ export function createServer() {
return res.status(500).json({ error: error.message });
}
const map: Record<string, any> = {};
for (const row of data || []) map[(row as any).key] = (row as any).value;
for (const row of data || [])
map[(row as any).key] = (row as any).value;
return res.json(map);
} catch (e: any) {
return res.status(500).json({ error: e?.message || String(e) });
@ -463,7 +464,10 @@ export function createServer() {
app.post("/api/site-settings", async (req, res) => {
try {
const { key, value } = (req.body || {}) as { key?: string; value?: any };
const { key, value } = (req.body || {}) as {
key?: string;
value?: any;
};
if (!key || typeof key !== "string") {
return res.status(400).json({ error: "key required" });
}
@ -472,7 +476,10 @@ export function createServer() {
.from("site_settings")
.upsert(payload, { onConflict: "key" as any });
if (error) {
if (isTableMissing(error)) return res.status(400).json({ error: "site_settings table missing" });
if (isTableMissing(error))
return res
.status(400)
.json({ error: "site_settings table missing" });
return res.status(500).json({ error: error.message });
}
return res.json({ ok: true });