Prettier format pending files
This commit is contained in:
parent
57a1a68e3a
commit
fb33214954
13 changed files with 4145 additions and 3904 deletions
|
|
@ -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(),
|
||||||
|
|
|
||||||
|
|
@ -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]);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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"
|
||||||
|
|
|
||||||
|
|
@ -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
|
|
@ -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>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
|
@ -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>
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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>
|
</>
|
||||||
</>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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>
|
</>
|
||||||
</>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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 });
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue