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

View file

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

View file

@ -27,7 +27,10 @@ export default function BannerSettings() {
const resp = await fetch("/api/site-settings", { const resp = await fetch("/api/site-settings", {
method: "POST", method: "POST",
headers: { "Content-Type": "application/json" }, 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"); if (!resp.ok) throw new Error("Save failed");
} finally { } finally {
@ -38,13 +41,17 @@ export default function BannerSettings() {
return ( return (
<div className="space-y-3"> <div className="space-y-3">
<div className="grid gap-2 md:grid-cols-4"> <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"> <div className="md:col-span-3">
<Switch checked={enabled} onCheckedChange={setEnabled} /> <Switch checked={enabled} onCheckedChange={setEnabled} />
</div> </div>
</div> </div>
<div className="grid gap-2 md:grid-cols-4"> <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"> <div className="md:col-span-3">
<input <input
className="w-full bg-background/50 border border-border/40 rounded px-2 py-1 text-sm" 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> </div>
<div className="grid gap-2 md:grid-cols-4"> <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"> <div className="md:col-span-3">
<select <select
className="w-full bg-background/50 border border-border/40 rounded px-2 py-1 text-sm" 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) { 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"); 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 ( 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} /> <SEO
<Layout> pageTitle="Blog"
<div className="bg-slate-950 text-foreground"> description="Insights and updates from AeThex: tutorials, platform news, and community highlights."
<BlogHero canonical={
featured={featuredPost} typeof window !== "undefined"
totalCount={dataset.length} ? window.location.href
search={searchQuery} : (undefined as any)
onSearchChange={setSearchQuery} }
onViewAll={handleResetFilters} />
/> <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"> <section className="border-b border-border/30 bg-background/60 py-12">
<div className="container mx-auto px-4"> <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="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">
<div className="space-y-2"> <div className="space-y-2">
<p className="text-xs uppercase tracking-[0.4em] text-muted-foreground"> <p className="text-xs uppercase tracking-[0.4em] text-muted-foreground">
Explore more Filter by track
</p> </p>
<h3 className="text-2xl font-semibold text-white"> <h2 className="text-2xl font-semibold text-white">
Dive into AeThex documentation Navigate the AeThex knowledge graph
</h3> </h2>
<p className="max-w-2xl text-sm text-muted-foreground"> </div>
Looking for implementation guides, deployment recipes, or <Button
program onboarding materials? Visit our documentation hub variant="ghost"
for developer tutorials, platform references, and community size="sm"
playbooks. 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> </p>
<h2 className="text-3xl font-semibold text-white">
Fresh from the AeThex ship room
</h2>
</div> </div>
<Button <Button
asChild 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> </Button>
</div> </div>
<BlogPostGrid posts={displayedPosts} />
</div> </div>
</div> </section>
</section>
</div> <BlogNewsletterSection />
</Layout>
</> <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 ( 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} /> <SEO
<Layout> pageTitle={post?.title || "Blog Post"}
<div className="min-h-screen bg-aethex-gradient py-12"> description={post?.excerpt || undefined}
<div className="container mx-auto px-4 max-w-3xl"> image={post?.image || null}
<Card className="overflow-hidden border-border/50 animate-scale-in"> canonical={
{post.image && ( typeof window !== "undefined"
<img ? window.location.href
src={post.image} : (undefined as any)
alt={post.title} }
className="w-full h-64 object-cover" />
/> <Layout>
)} <div className="min-h-screen bg-aethex-gradient py-12">
<CardHeader> <div className="container mx-auto px-4 max-w-3xl">
{post.category && ( <Card className="overflow-hidden border-border/50 animate-scale-in">
<Badge className="mb-4 bg-gradient-to-r from-aethex-500 to-neon-blue"> {post.image && (
{post.category} <img
</Badge> src={post.image}
alt={post.title}
className="w-full h-64 object-cover"
/>
)} )}
<CardTitle className="text-3xl mt-2">{post.title}</CardTitle> <CardHeader>
{post.excerpt && ( {post.category && (
<CardDescription className="text-muted-foreground mt-2"> <Badge className="mb-4 bg-gradient-to-r from-aethex-500 to-neon-blue">
{post.excerpt} {post.category}
</CardDescription> </Badge>
)}
<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 && ( <CardTitle className="text-3xl mt-2">{post.title}</CardTitle>
<div className="flex items-center gap-2"> {post.excerpt && (
<Calendar className="h-4 w-4" /> <span>{post.date}</span> <CardDescription className="text-muted-foreground mt-2">
</div> {post.excerpt}
</CardDescription>
)} )}
</div> <div className="flex items-center gap-4 mt-4 text-sm text-muted-foreground">
</CardHeader> {post.author && (
<CardContent className="prose max-w-none mt-6"> <div className="flex items-center gap-2">
{post.body ? ( <User className="h-4 w-4" /> <span>{post.author}</span>
<div dangerouslySetInnerHTML={{ __html: post.body }} /> </div>
) : ( )}
<p>{post.excerpt}</p> {post.date && (
)} <div className="flex items-center gap-2">
<div className="pt-6"> <Calendar className="h-4 w-4" /> <span>{post.date}</span>
<Link to="/blog" className="text-aethex-400 underline"> </div>
Back to Blog )}
</Link> </div>
</div> </CardHeader>
</CardContent> <CardContent className="prose max-w-none mt-6">
</Card> {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>
</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(() => { useEffect(() => {
fetch("/api/site-settings?key=home_banner") fetch("/api/site-settings?key=home_banner")
@ -270,230 +274,236 @@ export default function Index() {
<SEO <SEO
pageTitle="Home" pageTitle="Home"
description="AeThex: Where vision meets execution. Build, learn, and grow through design, development, and community." 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> <Layout hideFooter>
{/* Top Banner (editable via Admin → Operations) */} {/* Top Banner (editable via Admin → Operations) */}
{homeBanner?.enabled !== false && ( {homeBanner?.enabled !== false && (
<GamifiedBanner <GamifiedBanner
text={homeBanner?.text || "ROBLOX AUTH SOON"} text={homeBanner?.text || "ROBLOX AUTH SOON"}
enabled={homeBanner?.enabled !== false} enabled={homeBanner?.enabled !== false}
style={(homeBanner as any)?.style || null} style={(homeBanner as any)?.style || null}
/> />
)} )}
{/* Hero Section - Geometric Design */} {/* Hero Section - Geometric Design */}
<section className="relative min-h-screen flex items-start justify-center overflow-hidden pt-24 sm:pt-36"> <section className="relative min-h-screen flex items-start justify-center overflow-hidden pt-24 sm:pt-36">
{/* Geometric Background Pattern */} {/* 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" />
<div className="absolute inset-0"> <div className="absolute inset-0">
{/* Large Logo-inspired Geometric Shape */} <div className="absolute inset-0 bg-gradient-to-br from-aethex-900/50 via-background to-aethex-800/50" />
<div className="absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2"> <div className="absolute inset-0">
<div className="relative w-96 h-96 opacity-5"> {/* Large Logo-inspired Geometric Shape */}
<img <div className="absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2">
src="https://cdn.builder.io/api/v1/image/assets%2Ffc53d607e21d497595ac97e0637001a1%2F3979ec9a8a28471d900a80e94e2c45fe?format=webp&width=800" <div className="relative w-96 h-96 opacity-5">
alt="Background" <img
className="w-full h-full animate-float" 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> </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>
</div>
{/* Main Content */} {/* Main Content */}
<div className="container mx-auto px-4 relative z-10 pb-24 sm:pb-28"> <div className="container mx-auto px-4 relative z-10 pb-24 sm:pb-28">
<div className="text-center space-y-12"> <div className="text-center space-y-12">
{/* Title */} {/* Title */}
<div className="space-y-6 animate-scale-in"> <div className="space-y-6 animate-scale-in">
<div className="space-y-4"> <div className="space-y-4">
<h1 className="text-4xl sm:text-5xl lg:text-7xl font-bold"> <h1 className="text-4xl sm:text-5xl lg:text-7xl font-bold">
<span className="text-gradient-purple">AeThex</span> <span className="text-gradient-purple">AeThex</span>
</h1> </h1>
<h2 className="text-2xl lg:text-3xl text-gradient animate-fade-in"> <h2 className="text-2xl lg:text-3xl text-gradient animate-fade-in">
Crafting Digital Realities Crafting Digital Realities
</h2> </h2>
<p className="text-lg text-muted-foreground max-w-2xl mx-auto animate-slide-up"> <p className="text-lg text-muted-foreground max-w-2xl mx-auto animate-slide-up">
Where vision meets execution. We craft experiences through Where vision meets execution. We craft experiences through
design, development, and community. design, development, and community.
</p> </p>
</div>
</div> </div>
</div>
{/* Interactive Features Grid (Services) */} {/* 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"> <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) => { {features.map((feature, index) => {
const Icon = feature.icon; const Icon = feature.icon;
const isActive = activeSection === index; const isActive = activeSection === index;
return ( return (
<Card <Card
key={`old-${index}`} key={`old-${index}`}
className={`relative overflow-hidden rounded-xl border transition-all duration-500 group animate-fade-in ${ className={`relative overflow-hidden rounded-xl border transition-all duration-500 group animate-fade-in ${
isActive isActive
? "border-aethex-500/60 glow-blue" ? "border-aethex-500/60 glow-blue"
: "border-border/30 hover:border-aethex-400/50" : "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)]`} } 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` }} 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" /> <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"> <CardContent className="p-5 sm:p-6 flex flex-col items-center text-center gap-3">
<div <div
className={`relative w-12 h-12 rounded-lg bg-gradient-to-r ${feature.color} grid place-items-center shadow-inner`} 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" /> <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" /> <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> </div>
) : null} <h3 className="font-semibold text-sm tracking-wide">
</CardContent> {feature.title}
</Card> </h3>
); <div className="flex flex-wrap justify-center gap-2 min-h-[24px]">
})} {(feature.tags || []).slice(0, 2).map((tag, i) => (
</div> <Badge
key={i}
{/* Platform Feature Cards */} variant="outline"
<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"> className="border-white/10 text-xs text-foreground/80"
{platformFeatures.map((feature, index) => { >
const Icon = feature.icon; {tag}
const isActive = activeSection === index; </Badge>
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> </div>
) : null} <p className="text-xs text-muted-foreground line-clamp-2">
</CardContent> {feature.description}
</Card> </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`}
</div> />
{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 */} {/* Platform Feature Cards */}
<div className="flex flex-col sm:flex-row justify-center gap-6 animate-slide-up mb-8 sm:mb-10"> <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">
<Button {platformFeatures.map((feature, index) => {
asChild const Icon = feature.icon;
size="lg" const isActive = activeSection === index;
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" return (
> <Card
<Link key={`platform-${index}`}
to="/onboarding" className={`relative overflow-hidden rounded-xl border transition-all duration-500 group animate-fade-in ${
className="flex items-center space-x-2 group" 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" /> <Link
<span>Start Your Journey</span> to="/onboarding"
<ArrowRight className="h-5 w-5 transition-transform group-hover:translate-x-2" /> className="flex items-center space-x-2 group"
</Link> >
</Button> <Sparkles className="h-5 w-5" />
<Button <span>Start Your Journey</span>
asChild <ArrowRight className="h-5 w-5 transition-transform group-hover:translate-x-2" />
variant="outline" </Link>
size="lg" </Button>
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" <Button
> asChild
<Link to="/explore">Explore Platform</Link> variant="outline"
</Button> size="lg"
<Button 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"
asChild >
variant="ghost" <Link to="/explore">Explore Platform</Link>
size="lg" </Button>
className="hover-lift text-base sm:text-lg px-6 py-4 sm:px-8 sm:py-6" <Button
> asChild
<a href="https://aethex.net" target="_blank" rel="noreferrer">Visit aethex.net</a> variant="ghost"
</Button> 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> </div>
</div> </section>
</section> </Layout>
</Layout>
</> </>
); );
} }

View file

@ -247,308 +247,321 @@ export default function Login() {
return ( 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} /> <SEO
<Layout> pageTitle="Login"
<div className="min-h-screen bg-aethex-gradient py-12 flex items-center justify-center"> description="Sign in to your AeThex account to access the dashboard and community."
<div className="container mx-auto px-4 max-w-md"> canonical={
{/* Floating particles effect */} typeof window !== "undefined"
<div className="absolute inset-0 pointer-events-none overflow-hidden opacity-10"> ? window.location.href
{[...Array(20)].map((_, i) => ( : (undefined as any)
<div }
key={i} />
className="absolute w-1 h-1 bg-aethex-400 rounded-full animate-float" <Layout>
style={{ <div className="min-h-screen bg-aethex-gradient py-12 flex items-center justify-center">
left: `${Math.random() * 100}%`, <div className="container mx-auto px-4 max-w-md">
top: `${Math.random() * 100}%`, {/* Floating particles effect */}
animationDelay: `${Math.random() * 3}s`, <div className="absolute inset-0 pointer-events-none overflow-hidden opacity-10">
animationDuration: `${3 + Math.random() * 2}s`, {[...Array(20)].map((_, i) => (
}} <div
/> key={i}
))} className="absolute w-1 h-1 bg-aethex-400 rounded-full animate-float"
</div> style={{
left: `${Math.random() * 100}%`,
<Card className="bg-card/50 backdrop-blur-sm border border-border/50 shadow-2xl animate-scale-in relative z-10"> top: `${Math.random() * 100}%`,
<CardHeader className="text-center space-y-4"> animationDelay: `${Math.random() * 3}s`,
<div className="flex justify-center"> animationDuration: `${3 + Math.random() * 2}s`,
<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();
}} }}
/>
))}
</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" /> <Sparkles className="h-3 w-3 mr-1" />
Continue with Roblox Secure Login
</Button> </Badge>
</div> </CardHeader>
<div className="relative"> <CardContent className="space-y-6">
<div className="absolute inset-0 flex items-center"> {manualVerificationLink ? (
<div className="w-full border-t border-border/50" /> <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>
<div className="relative flex justify-center text-xs uppercase">
<span className="bg-background px-2 text-muted-foreground"> <div className="relative">
Or continue with email <div className="absolute inset-0 flex items-center">
</span> <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>
</div>
{/* Aethex Org Login (Magic Link) */} {/* Aethex Org Login (Magic Link) */}
<OrgLogin /> <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"> <div className="space-y-2">
<Label htmlFor="fullName" className="text-sm font-medium"> <Label htmlFor="email" className="text-sm font-medium">
Full Name Email Address
</Label> </Label>
<div className="relative"> <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 <Input
id="fullName" id="email"
type="text" type="email"
value={fullName} value={email}
onChange={(e) => setFullName(e.target.value)} onChange={(e) => setEmail(e.target.value)}
placeholder="Enter your full name" placeholder="Enter your email"
className="pl-10 bg-background/50 border-border/50 focus:border-aethex-400" className="pl-10 bg-background/50 border-border/50 focus:border-aethex-400"
required={isSignUp} required
/> />
</div> </div>
</div> </div>
)}
<div className="space-y-2"> <div className="space-y-2">
<Label htmlFor="email" className="text-sm font-medium"> <Label htmlFor="password" className="text-sm font-medium">
Email Address Password
</Label> </Label>
<div className="relative"> <div className="relative">
<Mail className="absolute left-3 top-1/2 transform -translate-y-1/2 h-4 w-4 text-muted-foreground" /> <Lock className="absolute left-3 top-1/2 transform -translate-y-1/2 h-4 w-4 text-muted-foreground" />
<Input <Input
id="email" id="password"
type="email" type="password"
value={email} value={password}
onChange={(e) => setEmail(e.target.value)} onChange={(e) => setPassword(e.target.value)}
placeholder="Enter your email" placeholder={
className="pl-10 bg-background/50 border-border/50 focus:border-aethex-400" isSignUp ? "Create a password" : "Enter your password"
required }
/> className="pl-10 bg-background/50 border-border/50 focus:border-aethex-400"
</div> required
</div> minLength={isSignUp ? 6 : undefined}
<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"
/> />
<span className="text-muted-foreground">Remember me</span> </div>
</label> {isSignUp && (
<button <p className="text-xs text-muted-foreground">
type="button" Password must be at least 6 characters long
className="text-aethex-400 hover:underline" </p>
onClick={() => { )}
setResetEmail(email || "");
setShowReset(true);
}}
>
Forgot password?
</button>
</div> </div>
)}
<Button {!isSignUp && (
type="submit" <div className="flex items-center justify-between text-sm">
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" <label className="flex items-center space-x-2 cursor-pointer">
disabled={ <input
!email || !password || (isSignUp && !fullName) || isLoading type="checkbox"
} className="rounded border-border/50"
> />
<LogIn className="h-4 w-4 mr-2" /> <span className="text-muted-foreground">
{isSignUp ? "Create Account" : "Sign In"} Remember me
<ArrowRight className="h-4 w-4 ml-2" /> </span>
</Button> </label>
</form> <button
type="button"
className="text-aethex-400 hover:underline"
onClick={() => {
setResetEmail(email || "");
setShowReset(true);
}}
>
Forgot password?
</button>
</div>
)}
<div className="text-center pt-4"> <Button
<p className="text-sm text-muted-foreground"> type="submit"
{isSignUp 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"
? "Already have an account?" disabled={
: "Don't have an account?"}{" "} !email ||
<button !password ||
onClick={() => { (isSignUp && !fullName) ||
setIsSignUp((prev) => !prev); isLoading
setManualVerificationLink(null); }
}}
className="text-aethex-400 hover:underline font-medium"
> >
{isSignUp ? "Sign In" : "Join AeThex"} <LogIn className="h-4 w-4 mr-2" />
</button> {isSignUp ? "Create Account" : "Sign In"}
</p> <ArrowRight className="h-4 w-4 ml-2" />
</div> </Button>
</CardContent> </form>
</Card>
{/* Security Notice */} <div className="text-center pt-4">
<div className="mt-6 text-center animate-fade-in"> <p className="text-sm text-muted-foreground">
<p className="text-xs text-muted-foreground"> {isSignUp
🔒 Your data is protected with enterprise-grade security ? "Already have an account?"
</p> : "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> </div>
</div>
<Dialog open={showReset} onOpenChange={setShowReset}> <Dialog open={showReset} onOpenChange={setShowReset}>
<DialogContent> <DialogContent>
<DialogHeader> <DialogHeader>
<DialogTitle>Reset your password</DialogTitle> <DialogTitle>Reset your password</DialogTitle>
<DialogDescription> <DialogDescription>
Enter the email associated with your account. We'll send a reset Enter the email associated with your account. We'll send a reset
link. link.
</DialogDescription> </DialogDescription>
</DialogHeader> </DialogHeader>
<div className="space-y-3 py-2"> <div className="space-y-3 py-2">
<Label htmlFor="resetEmail" className="text-sm font-medium"> <Label htmlFor="resetEmail" className="text-sm font-medium">
Email Address Email Address
</Label> </Label>
<Input <Input
id="resetEmail" id="resetEmail"
type="email" type="email"
value={resetEmail} value={resetEmail}
onChange={(e) => setResetEmail(e.target.value)} onChange={(e) => setResetEmail(e.target.value)}
placeholder="you@example.com" placeholder="you@example.com"
/> />
</div> </div>
<DialogFooter className="sm:justify-end"> <DialogFooter className="sm:justify-end">
<DialogClose asChild> <DialogClose asChild>
<Button variant="outline">Cancel</Button> <Button variant="outline">Cancel</Button>
</DialogClose> </DialogClose>
<Button <Button
onClick={async () => { onClick={async () => {
if (!resetEmail) return; if (!resetEmail) return;
setIsLoading(true); setIsLoading(true);
try { try {
await requestPasswordReset(resetEmail); await requestPasswordReset(resetEmail);
setShowReset(false); setShowReset(false);
} catch {} } catch {}
setIsLoading(false); setIsLoading(false);
}} }}
disabled={!resetEmail || isLoading} disabled={!resetEmail || isLoading}
> >
Send reset link Send reset link
</Button> </Button>
</DialogFooter> </DialogFooter>
</DialogContent> </DialogContent>
</Dialog> </Dialog>
</Layout> </Layout>
</> </>
); );
} }

View file

@ -277,412 +277,424 @@ export default function MentorshipPrograms() {
return ( 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} /> <SEO
<Layout> pageTitle="Mentorship"
<div className="min-h-screen bg-aethex-gradient"> description="AeThex mentorship programs: 1:1 guidance, workshops, and boot camps to accelerate your journey."
{/* Hero Section */} canonical={
<section className="relative py-16 sm:py-24 lg:py-32 overflow-hidden"> typeof window !== "undefined"
<div className="absolute inset-0 opacity-10"> ? window.location.href
{[...Array(25)].map((_, i) => ( : (undefined as any)
<div }
key={i} />
className="absolute text-emerald-400/80 animate-float" <Layout>
style={{ <div className="min-h-screen bg-aethex-gradient">
left: `${Math.random() * 100}%`, {/* Hero Section */}
top: `${Math.random() * 100}%`, <section className="relative py-16 sm:py-24 lg:py-32 overflow-hidden">
animationDelay: `${Math.random() * 3}s`, <div className="absolute inset-0 opacity-10">
animationDuration: `${3 + Math.random() * 2}s`, {[...Array(25)].map((_, i) => (
fontSize: `${8 + Math.random() * 6}px`, <div
}} key={i}
> className="absolute text-emerald-400/80 animate-float"
{"🎓📚💡🚀".charAt(Math.floor(Math.random() * 4))} style={{
</div> left: `${Math.random() * 100}%`,
))} top: `${Math.random() * 100}%`,
</div> animationDelay: `${Math.random() * 3}s`,
animationDuration: `${3 + Math.random() * 2}s`,
<div className="container mx-auto px-4 text-center relative z-10"> fontSize: `${8 + Math.random() * 6}px`,
<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"
> >
<Link {"🎓📚💡🚀".charAt(Math.floor(Math.random() * 4))}
to="/engage#mentorship" </div>
className="flex items-center space-x-2" ))}
> </div>
<BookOpen className="h-5 w-5" />
<span>Apply Now</span> <div className="container mx-auto px-4 text-center relative z-10">
<ArrowRight className="h-5 w-5" /> <div className="max-w-4xl mx-auto space-y-8">
</Link> <Badge
</Button>
<Button
asChild
variant="outline" variant="outline"
size="lg" className="border-emerald-400/50 text-emerald-300 animate-bounce-gentle"
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> <GraduationCap className="h-3 w-3 mr-1" />
</Button> 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> </div>
</div> </section>
</section>
{/* Programs Overview */} {/* Programs Overview */}
<section className="py-16 sm:py-20 bg-background/30"> <section className="py-16 sm:py-20 bg-background/30">
<div className="container mx-auto px-4"> <div className="container mx-auto px-4">
<div className="text-center mb-16 animate-slide-up"> <div className="text-center mb-16 animate-slide-up">
<h2 className="text-3xl lg:text-4xl font-bold text-gradient mb-4"> <h2 className="text-3xl lg:text-4xl font-bold text-gradient mb-4">
Mentorship Programs Mentorship Programs
</h2> </h2>
<p className="text-lg text-muted-foreground max-w-2xl mx-auto"> <p className="text-lg text-muted-foreground max-w-2xl mx-auto">
Choose the learning format that best fits your schedule and Choose the learning format that best fits your schedule and
goals goals
</p> </p>
</div> </div>
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-2 gap-6 max-w-6xl mx-auto"> <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) => { {programs.map((program, index) => {
const Icon = program.icon; const Icon = program.icon;
return ( return (
<Card <Card
key={index} key={index}
className="relative overflow-hidden border-border/50 hover:border-aethex-400/50 transition-all duration-500 hover-lift animate-scale-in" 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` }} style={{ animationDelay: `${index * 0.1}s` }}
> >
<CardHeader> <CardHeader>
<div className="flex items-start justify-between"> <div className="flex items-start justify-between">
<div className="flex items-center space-x-4"> <div className="flex items-center space-x-4">
<div <div
className={`p-3 rounded-lg bg-gradient-to-r ${program.color}`} className={`p-3 rounded-lg bg-gradient-to-r ${program.color}`}
> >
<Icon className="h-6 w-6 text-white" /> <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>
<div> <Badge variant="outline" className="shrink-0">
<CardTitle className="text-xl"> {program.participants}
{program.title} </Badge>
</CardTitle> </div>
<CardDescription className="mt-1"> </CardHeader>
{program.description} <CardContent className="space-y-4">
</CardDescription> <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>
</div> </div>
<Badge variant="outline" className="shrink-0"> </CardContent>
{program.participants} </Card>
</Badge> );
})}
</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> </div>
</CardHeader> </CardHeader>
<CardContent className="space-y-4"> <CardContent className="space-y-4">
<div className="grid grid-cols-1 sm:grid-cols-2 gap-2"> <blockquote className="text-sm italic text-muted-foreground">
{program.features.map((feature, featureIndex) => ( "{testimonial.content}"
<div </blockquote>
key={featureIndex} <div className="flex justify-between text-xs text-muted-foreground">
className="flex items-center space-x-2 text-sm" <span>Program: {testimonial.program}</span>
> <span>Mentor: {testimonial.mentor}</span>
<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> </div>
</CardContent> </CardContent>
</Card> </Card>
); ))}
})} </div>
</div> </div>
</div> </section>
</section>
{/* Learning Tracks */} {/* CTA Section */}
<section className="py-16 sm:py-20"> <section className="py-16 sm:py-20 bg-background/30">
<div className="container mx-auto px-4"> <div className="container mx-auto px-4 text-center">
<div className="text-center mb-16 animate-slide-up"> <div className="max-w-3xl mx-auto space-y-8 animate-scale-in">
<h2 className="text-3xl lg:text-4xl font-bold text-gradient mb-4"> <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">
Specialized Learning Tracks Start Your Learning Journey Today
</h2> </h2>
<p className="text-lg text-muted-foreground"> <p className="text-xl text-muted-foreground">
Focused curricula designed by industry experts Join thousands of developers who have accelerated their
</p> careers through our mentorship programs.
</div> </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"> <div className="flex flex-col sm:flex-row justify-center gap-4">
{tracks.map((track, index) => { <Button
const Icon = track.icon; asChild
const isSelected = selectedTrack === index; size="lg"
return ( 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"
<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"> <Link
<Icon className="h-12 w-12 text-emerald-400 mx-auto mb-3" /> to="/engage#mentorship"
<CardTitle className="flex items-center space-x-2"
className={`text-lg ${ >
isSelected <Heart className="h-5 w-5" />
? "bg-gradient-to-r from-emerald-400 to-aethex-500 bg-clip-text text-transparent" <span>Apply for Mentorship</span>
: "" <ArrowRight className="h-5 w-5" />
}`} </Link>
> </Button>
{track.name} <Button
</CardTitle> asChild
<CardDescription className="text-sm"> variant="outline"
{track.description} size="lg"
</CardDescription> 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"
</CardHeader> >
<CardContent className="space-y-3"> <Link to="/docs/curriculum">View Curriculum</Link>
<div className="flex flex-wrap gap-1"> </Button>
{track.skills.slice(0, 4).map((skill, skillIndex) => ( </div>
<Badge
key={skillIndex}
variant="secondary"
className="text-xs"
>
{skill}
</Badge>
))}
</div>
<div className="text-center space-y-1"> <div className="grid grid-cols-1 sm:grid-cols-3 gap-8 mt-12">
<div className="text-sm text-muted-foreground"> <div className="text-center">
{track.level} <MessageCircle className="h-8 w-8 text-emerald-400 mx-auto mb-2" />
</div> <h3 className="font-semibold">24/7 Support</h3>
<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"> <p className="text-sm text-muted-foreground">
{mentor.title} Always available
</p> </p>
<p className="text-sm font-medium text-emerald-400"> </div>
{mentor.company} <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> </p>
</div>
<div className="mt-4 space-y-2"> <div className="text-center">
<div className="flex justify-between text-sm"> <Award className="h-8 w-8 text-emerald-400 mx-auto mb-2" />
<span>Experience:</span> <h3 className="font-semibold">Certification</h3>
<span className="font-medium">{mentor.experience}</span> <p className="text-sm text-muted-foreground">
</div> Industry recognized
<div className="flex justify-between text-sm"> </p>
<span>Students Mentored:</span> </div>
<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>
</div> </div>
</div> </div>
</div> </section>
</section> </div>
</div> </Layout>
</Layout> </>
</>
); );
} }

View file

@ -12,7 +12,13 @@ import {
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input"; import { Input } from "@/components/ui/input";
import { Textarea } from "@/components/ui/textarea"; import { Textarea } from "@/components/ui/textarea";
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 { Badge } from "@/components/ui/badge";
import { aethexCollabService } from "@/lib/aethex-collab-service"; import { aethexCollabService } from "@/lib/aethex-collab-service";
import LoadingScreen from "@/components/LoadingScreen"; import LoadingScreen from "@/components/LoadingScreen";
@ -74,10 +80,19 @@ export default function ProjectBoard() {
blocked: [], blocked: [],
}; };
const normalized = tasks.filter((t) => { 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; 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; if (filterStatus && String(t.status || "") !== filterStatus) return false;
return true; return true;
}); });
@ -125,172 +140,41 @@ export default function ProjectBoard() {
return ( 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} /> <SEO
<Layout> pageTitle="Project Board"
<div className="min-h-screen bg-[radial-gradient(circle_at_top,_rgba(110,141,255,0.12),transparent_60%)] py-10"> description="Kanban task tracking for your AeThex project: statuses, assignees, and due dates."
<div className="mx-auto w-full max-w-6xl px-4 lg:px-6 space-y-6"> canonical={
<section className="rounded-3xl border border-border/40 bg-background/80 p-6 shadow-2xl backdrop-blur"> typeof window !== "undefined"
<h1 className="text-3xl font-semibold text-foreground"> ? window.location.href
Project Board : (undefined as any)
</h1> }
<p className="mt-1 text-sm text-muted-foreground"> />
Track tasks by status. Filters, assignees, and due dates enabled. <Layout>
</p> <div className="min-h-screen bg-[radial-gradient(circle_at_top,_rgba(110,141,255,0.12),transparent_60%)] py-10">
<div className="mt-4 grid gap-3 md:grid-cols-4"> <div className="mx-auto w-full max-w-6xl px-4 lg:px-6 space-y-6">
<Input <section className="rounded-3xl border border-border/40 bg-background/80 p-6 shadow-2xl backdrop-blur">
placeholder="Search tasks…" <h1 className="text-3xl font-semibold text-foreground">
value={q} Project Board
onChange={(e) => setQ(e.target.value)} </h1>
/> <p className="mt-1 text-sm text-muted-foreground">
<Select value={filterAssignee} onValueChange={setFilterAssignee}> Track tasks by status. Filters, assignees, and due dates
<SelectTrigger> enabled.
<SelectValue placeholder="Filter by assignee" /> </p>
</SelectTrigger> <div className="mt-4 grid gap-3 md:grid-cols-4">
<SelectContent> <Input
<SelectItem value="">All assignees</SelectItem> placeholder="Search tasks…"
{members.map((m) => ( value={q}
<SelectItem key={m.user_id} value={m.user_id}> onChange={(e) => setQ(e.target.value)}
{m.user?.full_name || m.user?.username || m.user_id} />
</SelectItem> <Select
))} value={filterAssignee}
</SelectContent> onValueChange={setFilterAssignee}
</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}>
<SelectTrigger> <SelectTrigger>
<SelectValue placeholder="Assign to…" /> <SelectValue placeholder="Filter by assignee" />
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>
<SelectItem value="">Unassigned</SelectItem> <SelectItem value="">All assignees</SelectItem>
{members.map((m) => ( {members.map((m) => (
<SelectItem key={m.user_id} value={m.user_id}> <SelectItem key={m.user_id} value={m.user_id}>
{m.user?.full_name || m.user?.username || m.user_id} {m.user?.full_name || m.user?.username || m.user_id}
@ -298,26 +182,188 @@ export default function ProjectBoard() {
))} ))}
</SelectContent> </SelectContent>
</Select> </Select>
<Input <Select value={filterStatus} onValueChange={setFilterStatus}>
type="date" <SelectTrigger>
value={dueDate} <SelectValue placeholder="Filter by status" />
onChange={(e) => setDueDate(e.target.value)} </SelectTrigger>
/> <SelectContent>
<div className="flex justify-end"> <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 <Button
onClick={handleCreate} variant="outline"
disabled={creating || !title.trim()} onClick={() => {
className="rounded-full bg-gradient-to-r from-aethex-500 to-neon-blue text-white" setQ("");
setFilterAssignee("");
setFilterStatus("");
}}
> >
{creating ? "Creating..." : "Create task"} Reset filters
</Button> </Button>
</div> </div>
</div> </div>
</CardContent> </section>
</Card>
<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>
</div> </Layout>
</Layout> </>
</>
); );
} }

View file

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