Prettier format pending files

This commit is contained in:
Builder.io 2025-10-14 07:17:34 +00:00
parent eb9291a7c2
commit c1749d74b0
14 changed files with 467 additions and 209 deletions

View file

@ -1,4 +1,10 @@
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@/components/ui/card";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { formatDistanceToNow } from "date-fns";
@ -84,8 +90,13 @@ export default function AdminChangelogDigest({
Version {entry.version} {entry.author}
</p>
</div>
<Badge variant="outline" className="border-border/40 text-[11px]">
{formatDistanceToNow(new Date(entry.date), { addSuffix: true })}
<Badge
variant="outline"
className="border-border/40 text-[11px]"
>
{formatDistanceToNow(new Date(entry.date), {
addSuffix: true,
})}
</Badge>
</div>
<p className="text-xs text-muted-foreground leading-relaxed">
@ -100,10 +111,14 @@ export default function AdminChangelogDigest({
key={`${entry.id}-change-${idx}`}
className="flex items-start gap-2"
>
<ChangeIcon className={`mt-0.5 h-4 w-4 ${changeAccent[change.type]}`} />
<ChangeIcon
className={`mt-0.5 h-4 w-4 ${changeAccent[change.type]}`}
/>
<span className="leading-relaxed">
<span className="font-medium text-foreground/80">
{change.type.charAt(0).toUpperCase() + change.type.slice(1)}:
{change.type.charAt(0).toUpperCase() +
change.type.slice(1)}
:
</span>{" "}
{change.description}
</span>

View file

@ -1,4 +1,10 @@
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@/components/ui/card";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import type { LucideIcon } from "lucide-react";
@ -25,8 +31,7 @@ interface AdminStatusOverviewProps {
}
const statusBadgeClass: Record<AdminServiceStatus["status"], string> = {
operational:
"border-emerald-500/30 bg-emerald-500/10 text-emerald-200",
operational: "border-emerald-500/30 bg-emerald-500/10 text-emerald-200",
degraded: "border-yellow-500/30 bg-yellow-500/10 text-yellow-200",
outage: "border-red-500/40 bg-red-500/10 text-red-200",
};
@ -71,7 +76,9 @@ export default function AdminStatusOverview({
<CardHeader className="space-y-4">
<div className="flex flex-wrap items-center justify-between gap-4">
<div className="flex items-center gap-3">
<div className={`${overall.badgeClass} flex h-10 w-10 items-center justify-center rounded-full border`}>
<div
className={`${overall.badgeClass} flex h-10 w-10 items-center justify-center rounded-full border`}
>
<OverallIcon className="h-5 w-5" />
</div>
<div>
@ -113,9 +120,7 @@ export default function AdminStatusOverview({
Healthy services
</p>
<p className="text-xl font-semibold text-foreground">
{services.length
? `${healthyServices}/${services.length}`
: "—"}
{services.length ? `${healthyServices}/${services.length}` : "—"}
</p>
</div>
</div>
@ -132,13 +137,18 @@ export default function AdminStatusOverview({
<ServiceIcon className="h-5 w-5 text-aethex-300" />
</div>
<div>
<p className="font-medium text-foreground">{service.name}</p>
<p className="font-medium text-foreground">
{service.name}
</p>
<p className="text-xs text-muted-foreground">
{service.responseTime} ms {service.uptime} uptime
</p>
</div>
</div>
<Badge className={statusBadgeClass[service.status]} variant="outline">
<Badge
className={statusBadgeClass[service.status]}
variant="outline"
>
{service.status}
</Badge>
</div>

View file

@ -7,7 +7,11 @@ interface BlogCategoryChipsProps {
onSelect: (id: string) => void;
}
const BlogCategoryChips = ({ categories, selected, onSelect }: BlogCategoryChipsProps) => {
const BlogCategoryChips = ({
categories,
selected,
onSelect,
}: BlogCategoryChipsProps) => {
if (!categories.length) return null;
return (

View file

@ -14,7 +14,13 @@ interface BlogHeroProps {
onViewAll?: () => void;
}
const BlogHero = ({ featured, totalCount, search, onSearchChange, onViewAll }: BlogHeroProps) => {
const BlogHero = ({
featured,
totalCount,
search,
onSearchChange,
onViewAll,
}: BlogHeroProps) => {
return (
<section className="relative overflow-hidden border-b border-border/40 bg-gradient-to-b from-slate-950 via-slate-900/80 to-slate-950 pb-20 pt-24 text-foreground">
<div className="absolute inset-x-0 top-0 h-[480px] bg-[radial-gradient(circle_at_top,_rgba(96,165,250,0.15),_transparent_60%)]" />
@ -32,7 +38,9 @@ const BlogHero = ({ featured, totalCount, search, onSearchChange, onViewAll }: B
Ideas, updates, and behind-the-scenes craft from the AeThex team
</h1>
<p className="max-w-2xl text-lg text-muted-foreground">
Explore engineering deep dives, platform updates, community spotlights, and changelog summaries. We publish what we learn building AeThex across games, cloud, and creator ecosystems.
Explore engineering deep dives, platform updates, community
spotlights, and changelog summaries. We publish what we learn
building AeThex across games, cloud, and creator ecosystems.
</p>
</div>
<div className="grid gap-4 sm:grid-cols-[minmax(0,2fr)_minmax(0,1fr)]">
@ -75,7 +83,9 @@ const BlogHero = ({ featured, totalCount, search, onSearchChange, onViewAll }: B
>
{featured.title}
</Link>
<p className="text-sm text-muted-foreground">{featured.excerpt}</p>
<p className="text-sm text-muted-foreground">
{featured.excerpt}
</p>
</div>
<div className="flex items-center justify-between text-sm text-muted-foreground">
<span>{featured.author || "AeThex Team"}</span>
@ -93,13 +103,16 @@ const BlogHero = ({ featured, totalCount, search, onSearchChange, onViewAll }: B
</span>
</div>
<Button asChild className="w-full">
<Link to={`/blog/${featured.slug}`}>Read the full story</Link>
<Link to={`/blog/${featured.slug}`}>
Read the full story
</Link>
</Button>
</div>
) : (
<div className="space-y-4 text-muted-foreground">
<p className="text-base">
We are preparing our latest feature article. Check back soon for fresh insights straight from the AeThex ship room.
We are preparing our latest feature article. Check back soon
for fresh insights straight from the AeThex ship room.
</p>
<div className="flex items-center gap-3 text-xs">
<span className="rounded-full border border-border/40 px-3 py-1">

View file

@ -15,9 +15,13 @@ const BlogNewsletterSection = () => {
<BellRing className="h-5 w-5" />
</div>
<div className="space-y-4">
<h2 className="text-3xl font-semibold text-white">Stay in the AeThex signal</h2>
<h2 className="text-3xl font-semibold text-white">
Stay in the AeThex signal
</h2>
<p className="mx-auto max-w-2xl text-base text-muted-foreground">
Subscribe for release notes, engineering write-ups, and community highlights. Expect one curated update every weekonly the essentials.
Subscribe for release notes, engineering write-ups, and
community highlights. Expect one curated update every
weekonly the essentials.
</p>
</div>
<form className="mx-auto flex w-full max-w-xl flex-col gap-4 sm:flex-row">
@ -27,13 +31,17 @@ const BlogNewsletterSection = () => {
placeholder="your@email.com"
className="h-12 flex-1 rounded-full border-border/50 bg-background/70 px-6 text-sm"
/>
<Button type="submit" className="h-12 rounded-full bg-gradient-to-r from-aethex-500 to-neon-blue">
<Button
type="submit"
className="h-12 rounded-full bg-gradient-to-r from-aethex-500 to-neon-blue"
>
Subscribe
<Send className="ml-2 h-4 w-4" />
</Button>
</form>
<p className="text-xs text-muted-foreground">
By subscribing you agree to receive emails from AeThex. Unsubscribe anytime in a single click.
By subscribing you agree to receive emails from AeThex.
Unsubscribe anytime in a single click.
</p>
</CardContent>
</Card>

View file

@ -11,7 +11,11 @@ interface BlogPostGridProps {
emptyState?: React.ReactNode;
}
const BlogPostGrid = ({ posts, placeholderImage = "/placeholder.svg", emptyState }: BlogPostGridProps) => {
const BlogPostGrid = ({
posts,
placeholderImage = "/placeholder.svg",
emptyState,
}: BlogPostGridProps) => {
if (!posts.length) {
return (
<div className="rounded-2xl border border-border/40 bg-background/70 p-12 text-center text-muted-foreground">
@ -38,25 +42,39 @@ const BlogPostGrid = ({ posts, placeholderImage = "/placeholder.svg", emptyState
</div>
) : (
<div className="flex h-48 w-full items-center justify-center bg-muted/10">
<img src={placeholderImage} alt="Placeholder" className="h-16 w-16 opacity-50" />
<img
src={placeholderImage}
alt="Placeholder"
className="h-16 w-16 opacity-50"
/>
</div>
)}
<CardHeader className="space-y-3">
<div className="flex items-center justify-between">
<Badge variant="outline" className="text-xs uppercase tracking-wide">
<Badge
variant="outline"
className="text-xs uppercase tracking-wide"
>
{post.category || "General"}
</Badge>
{post.readTime ? (
<span className="text-xs text-muted-foreground">{post.readTime}</span>
<span className="text-xs text-muted-foreground">
{post.readTime}
</span>
) : null}
</div>
<CardTitle className="text-xl leading-tight text-white">
<Link to={`/blog/${post.slug}`} className="transition hover:text-aethex-200">
<Link
to={`/blog/${post.slug}`}
className="transition hover:text-aethex-200"
>
{post.title}
</Link>
</CardTitle>
<p className="text-sm text-muted-foreground line-clamp-3">{post.excerpt}</p>
<p className="text-sm text-muted-foreground line-clamp-3">
{post.excerpt}
</p>
</CardHeader>
<CardContent className="mt-auto space-y-6">
@ -81,7 +99,10 @@ const BlogPostGrid = ({ posts, placeholderImage = "/placeholder.svg", emptyState
{post.comments?.toLocaleString() ?? 0}
</span>
<Button asChild size="sm" variant="outline" className="ml-auto">
<Link to={`/blog/${post.slug}`} className="inline-flex items-center gap-2 text-xs">
<Link
to={`/blog/${post.slug}`}
className="inline-flex items-center gap-2 text-xs"
>
Read article
<Share2 className="h-3.5 w-3.5" />
</Link>

View file

@ -16,8 +16,12 @@ const BlogTrendingRail = ({ posts }: BlogTrendingRailProps) => {
<div className="container mx-auto space-y-6 px-4">
<div className="flex items-center justify-between">
<div>
<p className="text-xs uppercase tracking-[0.4em] text-muted-foreground">Trending now</p>
<h2 className="text-2xl font-semibold text-white">High-signal reads across AeThex</h2>
<p className="text-xs uppercase tracking-[0.4em] text-muted-foreground">
Trending now
</p>
<h2 className="text-2xl font-semibold text-white">
High-signal reads across AeThex
</h2>
</div>
<Badge className="hidden lg:inline-flex items-center gap-1 bg-gradient-to-r from-orange-500 to-rose-500 text-xs uppercase tracking-widest">
<Flame className="h-3.5 w-3.5" /> Hot topics
@ -40,7 +44,9 @@ const BlogTrendingRail = ({ posts }: BlogTrendingRailProps) => {
>
{post.title}
</Link>
<p className="text-sm text-muted-foreground line-clamp-3">{post.excerpt}</p>
<p className="text-sm text-muted-foreground line-clamp-3">
{post.excerpt}
</p>
<div className="flex items-center justify-between text-xs text-muted-foreground">
<span>{post.author || "AeThex Team"}</span>
<div className="flex items-center gap-3">

View file

@ -38,7 +38,10 @@ interface AethexNotification {
read: boolean | null;
}
const typeIconMap: Record<string, React.ComponentType<{ className?: string }>> = {
const typeIconMap: Record<
string,
React.ComponentType<{ className?: string }>
> = {
success: CheckCircle2,
warning: AlertTriangle,
error: XCircle,
@ -54,7 +57,11 @@ const typeAccentMap: Record<string, string> = {
default: "text-aethex-300",
};
export default function NotificationBell({ className }: { className?: string }) {
export default function NotificationBell({
className,
}: {
className?: string;
}) {
const { user } = useAuth();
const [notifications, setNotifications] = useState<AethexNotification[]>([]);
const [loading, setLoading] = useState(false);
@ -73,7 +80,9 @@ export default function NotificationBell({ className }: { className?: string })
.getUserNotifications(user.id)
.then((data) => {
if (!isActive) return;
setNotifications(Array.isArray(data) ? (data as AethexNotification[]) : []);
setNotifications(
Array.isArray(data) ? (data as AethexNotification[]) : [],
);
})
.catch(() => {
if (!isActive) return;
@ -88,7 +97,9 @@ export default function NotificationBell({ className }: { className?: string })
user.id,
(payload: any) => {
if (!isActive) return;
const next = (payload?.new ?? payload) as AethexNotification | undefined;
const next = (payload?.new ?? payload) as
| AethexNotification
| undefined;
if (!next?.id) return;
setNotifications((prev) => {
@ -139,7 +150,9 @@ export default function NotificationBell({ className }: { className?: string })
}
setNotifications((prev) => prev.map((n) => ({ ...n, read: true })));
try {
await Promise.all(ids.map((id) => aethexNotificationService.markAsRead(id)));
await Promise.all(
ids.map((id) => aethexNotificationService.markAsRead(id)),
);
} catch {
// Soft fail silently
} finally {
@ -222,7 +235,9 @@ export default function NotificationBell({ className }: { className?: string })
className="w-80 border-border/40 bg-background/95 backdrop-blur"
>
<DropdownMenuLabel className="flex items-center justify-between">
<span className="text-sm font-semibold text-foreground">Notifications</span>
<span className="text-sm font-semibold text-foreground">
Notifications
</span>
{unreadCount > 0 ? (
<span className="text-xs text-muted-foreground">
{unreadCount} unread
@ -248,11 +263,14 @@ export default function NotificationBell({ className }: { className?: string })
<ScrollArea className="max-h-80">
{loading ? (
<div className="flex items-center justify-center py-8 text-sm text-muted-foreground">
<Loader2 className="mr-2 h-4 w-4 animate-spin" /> Loading notifications
<Loader2 className="mr-2 h-4 w-4 animate-spin" /> Loading
notifications
</div>
) : notifications.length ? (
<div className="py-1 space-y-1">
{notifications.map((notification) => renderNotification(notification))}
{notifications.map((notification) =>
renderNotification(notification),
)}
</div>
) : (
<div className="py-8 text-center text-sm text-muted-foreground">

View file

@ -461,7 +461,9 @@ export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({
const payload = await response.json().catch(() => ({}));
if (!response.ok) {
throw new Error(payload?.error || "Failed to queue verification email");
throw new Error(
payload?.error || "Failed to queue verification email",
);
}
emailSent = Boolean(payload?.sent);

View file

@ -348,7 +348,8 @@ export default function Admin() {
}, [statusSnapshot]);
const blogReach = useMemo(
() => resolvedBlogPosts.reduce((total, post) => total + (post.likes ?? 0), 0),
() =>
resolvedBlogPosts.reduce((total, post) => total + (post.likes ?? 0), 0),
[resolvedBlogPosts],
);
@ -430,8 +431,8 @@ export default function Admin() {
trend: loadingPosts
? "Refreshing content…"
: blogHighlights.length
? `Latest: ${blogHighlights[0].title}`
: "Curate new stories",
? `Latest: ${blogHighlights[0].title}`
: "Curate new stories",
icon: PenTool,
tone: "purple" as const,
},

View file

@ -14,7 +14,8 @@ import { Button } from "@/components/ui/button";
import { ArrowRight, Layers, ListFilter, Newspaper } from "lucide-react";
import type { BlogCategory, BlogPost } from "@/components/blog/types";
const buildSlug = (post: BlogPost): string => post.slug || post.id?.toString() || "article";
const buildSlug = (post: BlogPost): string =>
post.slug || post.id?.toString() || "article";
const normalizeCategory = (value?: string | null) =>
(value || "general")
@ -43,10 +44,17 @@ const Blog = () => {
data = await res.json();
}
} catch (error) {
console.warn("Failed to parse blog API response, falling back to Supabase", error);
console.warn(
"Failed to parse blog API response, falling back to Supabase",
error,
);
}
if ((!Array.isArray(data) || !data.length) && import.meta.env.VITE_SUPABASE_URL && import.meta.env.VITE_SUPABASE_ANON_KEY) {
if (
(!Array.isArray(data) || !data.length) &&
import.meta.env.VITE_SUPABASE_URL &&
import.meta.env.VITE_SUPABASE_ANON_KEY
) {
try {
const sbUrl = import.meta.env.VITE_SUPABASE_URL.replace(/\/$/, "");
const url = `${sbUrl}/rest/v1/blog_posts?select=slug,title,excerpt,author,date,read_time,category,image,likes,comments,published_at&order=published_at.desc&limit=50`;
@ -76,8 +84,11 @@ const Blog = () => {
category: record.category ?? "General",
image: record.image ?? null,
likes: typeof record.likes === "number" ? record.likes : null,
comments: typeof record.comments === "number" ? record.comments : null,
trending: Boolean(record.trending) || (typeof record.likes === "number" && record.likes > 250),
comments:
typeof record.comments === "number" ? record.comments : null,
trending:
Boolean(record.trending) ||
(typeof record.likes === "number" && record.likes > 250),
body: record.body_html ?? record.body ?? null,
}));
setPosts(mapped);
@ -103,7 +114,8 @@ const Blog = () => {
const query = searchQuery.trim().toLowerCase();
return dataset.filter((post) => {
const matchesCategory =
selectedCategory === "all" || normalizeCategory(post.category) === selectedCategory;
selectedCategory === "all" ||
normalizeCategory(post.category) === selectedCategory;
if (!matchesCategory) return false;
if (!query) return true;
@ -119,12 +131,16 @@ const Blog = () => {
if (!filteredPosts.length) {
return dataset.find((post) => post.trending) ?? dataset[0] ?? null;
}
return filteredPosts.find((post) => post.trending) ?? filteredPosts[0] ?? null;
return (
filteredPosts.find((post) => post.trending) ?? filteredPosts[0] ?? null
);
}, [dataset, filteredPosts]);
const displayedPosts = useMemo(() => {
if (!featuredPost) return filteredPosts;
return filteredPosts.filter((post) => buildSlug(post) !== buildSlug(featuredPost));
return filteredPosts.filter(
(post) => buildSlug(post) !== buildSlug(featuredPost),
);
}, [filteredPosts, featuredPost]);
const trendingPosts = useMemo(() => {
@ -158,7 +174,9 @@ const Blog = () => {
() => [
{
label: "Teams publishing",
value: new Set(dataset.map((post) => (post.author || "AeThex Team").split(" ")[0])).size,
value: new Set(
dataset.map((post) => (post.author || "AeThex Team").split(" ")[0]),
).size,
helper: "Active contributors this month",
icon: <Layers className="h-4 w-4" />,
},
@ -205,9 +223,16 @@ const Blog = () => {
<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>
<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">
<Button
variant="ghost"
size="sm"
onClick={handleResetFilters}
className="self-start lg:self-auto"
>
Reset filters
</Button>
</div>
@ -235,9 +260,15 @@ const Blog = () => {
{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>
<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>
@ -249,10 +280,18 @@ const Blog = () => {
<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>
<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">
<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" />
@ -271,14 +310,23 @@ const Blog = () => {
<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="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.
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">
<Button
asChild
className="bg-gradient-to-r from-aethex-500 to-neon-blue"
>
<Link to="/docs">Open documentation hub</Link>
</Button>
</div>

View file

@ -1,6 +1,12 @@
import { useState } from "react";
import Layout from "@/components/Layout";
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@/components/ui/card";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
@ -34,7 +40,7 @@ interface ChangelogEntry {
id: string;
version: string;
date: string;
type: 'major' | 'minor' | 'patch';
type: "major" | "minor" | "patch";
category: string;
title: string;
description: string;
@ -44,9 +50,9 @@ interface ChangelogEntry {
}
interface ChangelogItem {
type: 'added' | 'improved' | 'fixed' | 'removed' | 'security';
type: "added" | "improved" | "fixed" | "removed" | "security";
description: string;
impact: 'high' | 'medium' | 'low';
impact: "high" | "medium" | "low";
}
export const changelogEntries: ChangelogEntry[] = [
@ -57,126 +63,162 @@ export const changelogEntries: ChangelogEntry[] = [
type: "major",
category: "Platform Enhancement",
title: "Major Platform Improvements & New Features",
description: "Comprehensive platform upgrade with new dashboard features, improved user experience, and enhanced system monitoring capabilities.",
description:
"Comprehensive platform upgrade with new dashboard features, improved user experience, and enhanced system monitoring capabilities.",
author: "AeThex Development Team",
pullRequest: "#121e8c26",
changes: [
{
type: "added",
description: "New comprehensive Profile system with overseer dashboard and cross-site communication monitoring",
impact: "high"
},
{
type: "added",
description: "System Status page with real-time monitoring of all AeThex services and infrastructure",
impact: "high"
description:
"New comprehensive Profile system with overseer dashboard and cross-site communication monitoring",
impact: "high",
},
{
type: "added",
description: "Comprehensive Tutorials library with categorized learning content and filtering capabilities",
impact: "medium"
description:
"System Status page with real-time monitoring of all AeThex services and infrastructure",
impact: "high",
},
{
type: "added",
description: "Nested Documentation routing system with improved navigation and breadcrumbs",
impact: "medium"
description:
"Comprehensive Tutorials library with categorized learning content and filtering capabilities",
impact: "medium",
},
{
type: "added",
description: "Documentation-specific tutorials section with interactive content types",
impact: "medium"
description:
"Nested Documentation routing system with improved navigation and breadcrumbs",
impact: "medium",
},
{
type: "added",
description: "Login access options on onboarding page to prevent duplicate account creation",
impact: "medium"
description:
"Documentation-specific tutorials section with interactive content types",
impact: "medium",
},
{
type: "added",
description:
"Login access options on onboarding page to prevent duplicate account creation",
impact: "medium",
},
{
type: "improved",
description: "Enhanced navigation system with user-specific menu items when authenticated",
impact: "medium"
description:
"Enhanced navigation system with user-specific menu items when authenticated",
impact: "medium",
},
{
type: "improved",
description: "Dashboard user interface with proper profile data integration and XP progression",
impact: "medium"
description:
"Dashboard user interface with proper profile data integration and XP progression",
impact: "medium",
},
{
type: "improved",
description: "Authentication flow with better error handling and redirect logic",
impact: "high"
description:
"Authentication flow with better error handling and redirect logic",
impact: "high",
},
{
type: "fixed",
description: "Resolved Dashboard infinite loading screen issue when accessing without authentication",
impact: "high"
description:
"Resolved Dashboard infinite loading screen issue when accessing without authentication",
impact: "high",
},
{
type: "fixed",
description: "Fixed toast notification bouncing animations that were causing UI disruption",
impact: "medium"
description:
"Fixed toast notification bouncing animations that were causing UI disruption",
impact: "medium",
},
{
type: "fixed",
description: "Corrected database adapter table name mismatches and TypeScript compilation errors",
impact: "high"
description:
"Corrected database adapter table name mismatches and TypeScript compilation errors",
impact: "high",
},
{
type: "fixed",
description: "Resolved DNS configuration issues for custom domain deployment",
impact: "medium"
description:
"Resolved DNS configuration issues for custom domain deployment",
impact: "medium",
},
{
type: "fixed",
description: "Fixed user profile property access issues throughout the application",
impact: "medium"
description:
"Fixed user profile property access issues throughout the application",
impact: "medium",
},
{
type: "security",
description: "Enhanced authentication redirect security with proper session handling",
impact: "high"
}
]
}
description:
"Enhanced authentication redirect security with proper session handling",
impact: "high",
},
],
},
];
const getTypeIcon = (type: string) => {
switch (type) {
case 'added': return <Plus className="h-4 w-4 text-green-500" />;
case 'improved': return <ArrowUpRight className="h-4 w-4 text-blue-500" />;
case 'fixed': return <Bug className="h-4 w-4 text-orange-500" />;
case 'removed': return <AlertTriangle className="h-4 w-4 text-red-500" />;
case 'security': return <Shield className="h-4 w-4 text-purple-500" />;
default: return <Info className="h-4 w-4 text-gray-500" />;
case "added":
return <Plus className="h-4 w-4 text-green-500" />;
case "improved":
return <ArrowUpRight className="h-4 w-4 text-blue-500" />;
case "fixed":
return <Bug className="h-4 w-4 text-orange-500" />;
case "removed":
return <AlertTriangle className="h-4 w-4 text-red-500" />;
case "security":
return <Shield className="h-4 w-4 text-purple-500" />;
default:
return <Info className="h-4 w-4 text-gray-500" />;
}
};
const getTypeColor = (type: string) => {
switch (type) {
case 'added': return 'bg-green-500';
case 'improved': return 'bg-blue-500';
case 'fixed': return 'bg-orange-500';
case 'removed': return 'bg-red-500';
case 'security': return 'bg-purple-500';
default: return 'bg-gray-500';
case "added":
return "bg-green-500";
case "improved":
return "bg-blue-500";
case "fixed":
return "bg-orange-500";
case "removed":
return "bg-red-500";
case "security":
return "bg-purple-500";
default:
return "bg-gray-500";
}
};
const getVersionBadgeColor = (type: string) => {
switch (type) {
case 'major': return 'bg-purple-600';
case 'minor': return 'bg-blue-600';
case 'patch': return 'bg-green-600';
default: return 'bg-gray-600';
case "major":
return "bg-purple-600";
case "minor":
return "bg-blue-600";
case "patch":
return "bg-green-600";
default:
return "bg-gray-600";
}
};
const getImpactColor = (impact: string) => {
switch (impact) {
case 'high': return 'text-red-400';
case 'medium': return 'text-yellow-400';
case 'low': return 'text-green-400';
default: return 'text-gray-400';
case "high":
return "text-red-400";
case "medium":
return "text-yellow-400";
case "low":
return "text-green-400";
default:
return "text-gray-400";
}
};
@ -185,20 +227,28 @@ export default function Changelog() {
const [selectedType, setSelectedType] = useState("all");
const [selectedCategory, setSelectedCategory] = useState("all");
const filteredEntries = changelogEntries.filter(entry => {
const matchesSearch = entry.title.toLowerCase().includes(searchTerm.toLowerCase()) ||
entry.description.toLowerCase().includes(searchTerm.toLowerCase()) ||
entry.changes.some(change =>
change.description.toLowerCase().includes(searchTerm.toLowerCase())
);
const filteredEntries = changelogEntries.filter((entry) => {
const matchesSearch =
entry.title.toLowerCase().includes(searchTerm.toLowerCase()) ||
entry.description.toLowerCase().includes(searchTerm.toLowerCase()) ||
entry.changes.some((change) =>
change.description.toLowerCase().includes(searchTerm.toLowerCase()),
);
const matchesType = selectedType === "all" || entry.type === selectedType;
const matchesCategory = selectedCategory === "all" ||
entry.category.toLowerCase().replace(/\s+/g, '-') === selectedCategory;
const matchesCategory =
selectedCategory === "all" ||
entry.category.toLowerCase().replace(/\s+/g, "-") === selectedCategory;
return matchesSearch && matchesType && matchesCategory;
});
const categories = ["all", "platform-enhancement", "security", "performance", "ui-ux"];
const categories = [
"all",
"platform-enhancement",
"security",
"performance",
"ui-ux",
];
const types = ["all", "major", "minor", "patch"];
return (
@ -213,11 +263,15 @@ export default function Changelog() {
AeThex Changelog
</h1>
<p className="text-xl text-gray-300">
Track the latest updates, improvements, and fixes to the AeThex platform
Track the latest updates, improvements, and fixes to the
AeThex platform
</p>
</div>
<div className="flex items-center space-x-4">
<Button variant="outline" className="border-slate-600 text-white hover:bg-slate-800">
<Button
variant="outline"
className="border-slate-600 text-white hover:bg-slate-800"
>
<Github className="h-4 w-4 mr-2" />
View on GitHub
</Button>
@ -227,7 +281,7 @@ export default function Changelog() {
</Button>
</div>
</div>
{/* Stats */}
<div className="grid grid-cols-1 md:grid-cols-4 gap-4 mb-6">
<Card className="bg-slate-800/50 border-slate-700">
@ -235,21 +289,27 @@ export default function Changelog() {
<div className="flex items-center justify-between">
<div>
<p className="text-sm text-gray-400">Total Releases</p>
<p className="text-2xl font-bold text-white">{changelogEntries.length}</p>
<p className="text-2xl font-bold text-white">
{changelogEntries.length}
</p>
</div>
<Calendar className="h-8 w-8 text-purple-400" />
</div>
</CardContent>
</Card>
<Card className="bg-slate-800/50 border-slate-700">
<CardContent className="p-4">
<div className="flex items-center justify-between">
<div>
<p className="text-sm text-gray-400">New Features</p>
<p className="text-2xl font-bold text-green-400">
{changelogEntries.reduce((acc, entry) =>
acc + entry.changes.filter(c => c.type === 'added').length, 0
{changelogEntries.reduce(
(acc, entry) =>
acc +
entry.changes.filter((c) => c.type === "added")
.length,
0,
)}
</p>
</div>
@ -257,15 +317,19 @@ export default function Changelog() {
</div>
</CardContent>
</Card>
<Card className="bg-slate-800/50 border-slate-700">
<CardContent className="p-4">
<div className="flex items-center justify-between">
<div>
<p className="text-sm text-gray-400">Bug Fixes</p>
<p className="text-2xl font-bold text-orange-400">
{changelogEntries.reduce((acc, entry) =>
acc + entry.changes.filter(c => c.type === 'fixed').length, 0
{changelogEntries.reduce(
(acc, entry) =>
acc +
entry.changes.filter((c) => c.type === "fixed")
.length,
0,
)}
</p>
</div>
@ -273,15 +337,19 @@ export default function Changelog() {
</div>
</CardContent>
</Card>
<Card className="bg-slate-800/50 border-slate-700">
<CardContent className="p-4">
<div className="flex items-center justify-between">
<div>
<p className="text-sm text-gray-400">Improvements</p>
<p className="text-2xl font-bold text-blue-400">
{changelogEntries.reduce((acc, entry) =>
acc + entry.changes.filter(c => c.type === 'improved').length, 0
{changelogEntries.reduce(
(acc, entry) =>
acc +
entry.changes.filter((c) => c.type === "improved")
.length,
0,
)}
</p>
</div>
@ -310,9 +378,11 @@ export default function Changelog() {
onChange={(e) => setSelectedType(e.target.value)}
className="bg-slate-800/50 border-slate-600 text-white rounded-md px-3 py-2"
>
{types.map(type => (
{types.map((type) => (
<option key={type} value={type}>
{type === 'all' ? 'All Types' : type.charAt(0).toUpperCase() + type.slice(1)}
{type === "all"
? "All Types"
: type.charAt(0).toUpperCase() + type.slice(1)}
</option>
))}
</select>
@ -321,13 +391,17 @@ export default function Changelog() {
onChange={(e) => setSelectedCategory(e.target.value)}
className="bg-slate-800/50 border-slate-600 text-white rounded-md px-3 py-2"
>
{categories.map(category => (
{categories.map((category) => (
<option key={category} value={category}>
{category === 'all' ? 'All Categories' :
category.split('-').map(word =>
word.charAt(0).toUpperCase() + word.slice(1)
).join(' ')
}
{category === "all"
? "All Categories"
: category
.split("-")
.map(
(word) =>
word.charAt(0).toUpperCase() + word.slice(1),
)
.join(" ")}
</option>
))}
</select>
@ -338,11 +412,16 @@ export default function Changelog() {
{/* Changelog Entries */}
<div className="space-y-8">
{filteredEntries.map((entry, index) => (
<Card key={entry.id} className="bg-slate-800/50 border-slate-700 hover:border-purple-500/50 transition-all duration-300">
<Card
key={entry.id}
className="bg-slate-800/50 border-slate-700 hover:border-purple-500/50 transition-all duration-300"
>
<CardHeader>
<div className="flex items-start justify-between mb-4">
<div className="flex items-center space-x-4">
<Badge className={`${getVersionBadgeColor(entry.type)} text-white border-0 px-3 py-1`}>
<Badge
className={`${getVersionBadgeColor(entry.type)} text-white border-0 px-3 py-1`}
>
v{entry.version}
</Badge>
<Badge variant="outline" className="text-gray-300">
@ -350,68 +429,83 @@ export default function Changelog() {
</Badge>
<div className="flex items-center text-sm text-gray-400">
<Calendar className="h-4 w-4 mr-1" />
{new Date(entry.date).toLocaleDateString('en-US', {
year: 'numeric',
month: 'long',
day: 'numeric'
{new Date(entry.date).toLocaleDateString("en-US", {
year: "numeric",
month: "long",
day: "numeric",
})}
</div>
</div>
{entry.pullRequest && (
<Button variant="ghost" size="sm" className="text-gray-400 hover:text-white">
<Button
variant="ghost"
size="sm"
className="text-gray-400 hover:text-white"
>
<Github className="h-4 w-4 mr-1" />
{entry.pullRequest}
</Button>
)}
</div>
<CardTitle className="text-2xl text-white">
{entry.title}
</CardTitle>
<CardDescription className="text-gray-300 text-base">
{entry.description}
</CardDescription>
<div className="flex items-center text-sm text-gray-400 mt-2">
<Users className="h-4 w-4 mr-1" />
By {entry.author}
</div>
</CardHeader>
<CardContent>
<div className="space-y-4">
{/* Group changes by type */}
{['added', 'improved', 'fixed', 'security', 'removed'].map(changeType => {
const changesOfType = entry.changes.filter(change => change.type === changeType);
if (changesOfType.length === 0) return null;
return (
<div key={changeType} className="space-y-3">
<div className="flex items-center space-x-2">
{getTypeIcon(changeType)}
<h4 className="font-semibold text-white capitalize">
{changeType} ({changesOfType.length})
</h4>
{["added", "improved", "fixed", "security", "removed"].map(
(changeType) => {
const changesOfType = entry.changes.filter(
(change) => change.type === changeType,
);
if (changesOfType.length === 0) return null;
return (
<div key={changeType} className="space-y-3">
<div className="flex items-center space-x-2">
{getTypeIcon(changeType)}
<h4 className="font-semibold text-white capitalize">
{changeType} ({changesOfType.length})
</h4>
</div>
<ul className="space-y-2 ml-6">
{changesOfType.map((change, changeIndex) => (
<li
key={changeIndex}
className="flex items-start space-x-3"
>
<div
className={`w-2 h-2 rounded-full ${getTypeColor(change.type)} mt-2 flex-shrink-0`}
/>
<div className="flex-1">
<p className="text-gray-300">
{change.description}
</p>
<Badge
variant="outline"
className={`mt-1 text-xs ${getImpactColor(change.impact)} border-current`}
>
{change.impact} impact
</Badge>
</div>
</li>
))}
</ul>
</div>
<ul className="space-y-2 ml-6">
{changesOfType.map((change, changeIndex) => (
<li key={changeIndex} className="flex items-start space-x-3">
<div className={`w-2 h-2 rounded-full ${getTypeColor(change.type)} mt-2 flex-shrink-0`} />
<div className="flex-1">
<p className="text-gray-300">{change.description}</p>
<Badge
variant="outline"
className={`mt-1 text-xs ${getImpactColor(change.impact)} border-current`}
>
{change.impact} impact
</Badge>
</div>
</li>
))}
</ul>
</div>
);
})}
);
},
)}
</div>
</CardContent>
</Card>
@ -426,7 +520,8 @@ export default function Changelog() {
No changelog entries found
</h3>
<p className="text-gray-400">
Try adjusting your search terms or filters to find what you're looking for.
Try adjusting your search terms or filters to find what you're
looking for.
</p>
</CardContent>
</Card>
@ -440,10 +535,14 @@ export default function Changelog() {
Stay Updated
</h3>
<p className="text-gray-300 mb-4">
Follow our development progress and get notified about new releases
Follow our development progress and get notified about new
releases
</p>
<div className="flex justify-center space-x-4">
<Button variant="outline" className="border-slate-600 text-white hover:bg-slate-800">
<Button
variant="outline"
className="border-slate-600 text-white hover:bg-slate-800"
>
<Github className="h-4 w-4 mr-2" />
Watch on GitHub
</Button>

View file

@ -35,7 +35,9 @@ export default function Login() {
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [fullName, setFullName] = useState("");
const [manualVerificationLink, setManualVerificationLink] = useState<string | null>(null);
const [manualVerificationLink, setManualVerificationLink] = useState<
string | null
>(null);
const navigate = useNavigate();
const { signIn, signUp, signInWithOAuth, user, loading, profileComplete } =
useAuth();
@ -99,7 +101,8 @@ export default function Login() {
console.error("Authentication error:", error);
toastError({
title: "Authentication failed",
description: error?.message || "Something went wrong. Please try again.",
description:
error?.message || "Something went wrong. Please try again.",
});
} finally {
setIsLoading(false);
@ -203,7 +206,13 @@ export default function Login() {
size="sm"
variant="outline"
className="mt-3 border-aethex-400/40"
onClick={() => window.open(manualVerificationLink, "_blank", "noopener")}
onClick={() =>
window.open(
manualVerificationLink,
"_blank",
"noopener",
)
}
>
Open verification link
</Button>

View file

@ -1,8 +1,10 @@
import { Resend } from "resend";
const resendApiKey = process.env.RESEND_API_KEY;
const defaultFromAddress = process.env.RESEND_FROM_EMAIL ?? "AeThex OS <no-reply@aethex.dev>";
const verifySupportEmail = process.env.VERIFY_SUPPORT_EMAIL ?? "support@aethex.biz";
const defaultFromAddress =
process.env.RESEND_FROM_EMAIL ?? "AeThex OS <no-reply@aethex.dev>";
const verifySupportEmail =
process.env.VERIFY_SUPPORT_EMAIL ?? "support@aethex.biz";
const resendClient = resendApiKey ? new Resend(resendApiKey) : null;
@ -42,7 +44,9 @@ export const emailService = {
</div>
`;
const text = [`Welcome to AeThex, ${safeName}!`, "",
const text = [
`Welcome to AeThex, ${safeName}!`,
"",
"Use the link below to verify your account:",
verificationUrl,
"",