Rewrite Admin dashboard with tabbed layout

cgen-b3adf2085f4148f693748703c1e60577
This commit is contained in:
Builder.io 2025-10-14 02:09:08 +00:00
parent 6fea8ffe8c
commit 14a2f34976

View file

@ -111,17 +111,16 @@ export default function Admin() {
<Layout> <Layout>
<div className="min-h-screen bg-aethex-gradient py-12"> <div className="min-h-screen bg-aethex-gradient py-12">
<div className="container mx-auto px-4 max-w-3xl"> <div className="container mx-auto px-4 max-w-3xl">
<Card className="bg-red-500/10 border-red-500/30"> <Card className="bg-red-500/10 border-red-500/30 backdrop-blur">
<CardHeader> <CardHeader>
<CardTitle className="text-red-400">Access Denied</CardTitle> <CardTitle className="text-red-400">Access denied</CardTitle>
<CardDescription> <CardDescription>
You don't have permission to access the admin panel. This panel is restricted to {ownerEmail}. If you need access, contact the site owner.
</CardDescription> </CardDescription>
</CardHeader> </CardHeader>
<CardContent> <CardContent className="flex gap-2">
<Button onClick={() => navigate("/dashboard")}> <Button onClick={() => navigate("/dashboard")}>Go to dashboard</Button>
Go to Dashboard <Button variant="outline" onClick={() => navigate("/support")}>Contact support</Button>
</Button>
</CardContent> </CardContent>
</Card> </Card>
</div> </div>
@ -287,447 +286,518 @@ export default function Admin() {
<Layout> <Layout>
<div className="min-h-screen bg-aethex-gradient py-12"> <div className="min-h-screen bg-aethex-gradient py-12">
<div className="container mx-auto px-4 max-w-6xl space-y-8"> <div className="container mx-auto px-4 max-w-6xl space-y-8">
<div className="flex items-center justify-between"> <div className="flex flex-col gap-4 lg:flex-row lg:items-center lg:justify-between">
<div> <div className="space-y-2">
<h1 className="text-3xl font-bold text-gradient">Admin Panel</h1> <h1 className="text-3xl font-bold text-gradient">Admin Control Center</h1>
<p className="text-muted-foreground"> <p className="text-muted-foreground">
Site Owner Admin Founder Unified oversight for AeThex operations, content, and community.
</p> </p>
<div className="flex gap-2 mt-2"> <div className="flex flex-wrap gap-2">
<Badge <Badge variant="outline" className="border-green-500/50 text-green-300">
variant="outline" Owner
className="border-green-500/50 text-green-400"
>
Site Owner
</Badge> </Badge>
<Badge <Badge variant="outline" className="border-blue-500/50 text-blue-300">
variant="outline"
className="border-blue-500/50 text-blue-400"
>
Admin Admin
</Badge> </Badge>
<Badge <Badge variant="outline" className="border-purple-500/50 text-purple-300">
variant="outline"
className="border-purple-500/50 text-purple-400"
>
Founder Founder
</Badge> </Badge>
</div> </div>
<p className="text-xs text-muted-foreground">
Signed in as <span className="text-foreground">{normalizedEmail || ownerEmail}</span>
</p>
</div> </div>
<div className="flex gap-2"> <div className="flex flex-wrap gap-2">
<Button variant="outline" onClick={() => navigate("/dashboard")}> <Button variant="outline" onClick={() => navigate("/dashboard")}>
Dashboard Dashboard
</Button> </Button>
<Button variant="outline" onClick={() => navigate("/profile")}> <Button variant="outline" onClick={() => navigate("/profile")}>
Profile Profile
</Button> </Button>
<Button onClick={() => setActiveTab("content")}>Create update</Button>
</div> </div>
</div> </div>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6"> <Tabs value={activeTab} onValueChange={setActiveTab} className="space-y-6">
<Card className="bg-card/50 border-border/50"> <TabsList className="w-full justify-start gap-2 overflow-x-auto border border-border/40 bg-background/40 px-1 py-1 backdrop-blur">
<CardHeader> <TabsTrigger value="overview">Overview</TabsTrigger>
<div className="flex items-center gap-2"> <TabsTrigger value="content">Content</TabsTrigger>
<Shield className="h-5 w-5 text-green-400" /> <TabsTrigger value="community">Community</TabsTrigger>
<CardTitle className="text-lg">Access Control</CardTitle> <TabsTrigger value="operations">Operations</TabsTrigger>
</div> </TabsList>
<CardDescription>
Owner-only access is enforced by email
</CardDescription>
</CardHeader>
<CardContent>
<ul className="text-sm space-y-1 text-muted-foreground">
<li>
Owner:{" "}
<span className="text-foreground">mrpiglr@gmail.com</span>
</li>
<li>All other users are denied access</li>
</ul>
</CardContent>
</Card>
<Card className="bg-card/50 border-border/50"> <TabsContent value="overview" className="space-y-6">
<CardHeader> <div className="grid gap-4 md:grid-cols-2 xl:grid-cols-4">
<div className="flex items-center gap-2"> {overviewStats.map((stat) => (
<Users className="h-5 w-5 text-blue-400" /> <AdminStatCard
<CardTitle className="text-lg">Users & Roles</CardTitle> key={stat.title}
</div> title={stat.title}
<CardDescription> value={stat.value}
Future: manage roles, invitations, and status description={stat.description}
</CardDescription> trend={stat.trend}
</CardHeader> icon={stat.icon}
<CardContent> tone={stat.tone}
<p className="text-sm text-muted-foreground">Coming soon</p> />
</CardContent> ))}
</Card> </div>
{/* Blog Posts Management */} <div className="grid gap-6 lg:grid-cols-2">
<Card className="bg-card/50 border-border/50 md:col-span-2 lg:col-span-3"> <Card className="bg-card/60 border-border/40 backdrop-blur">
<CardHeader> <CardHeader>
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<PenTool className="h-5 w-5 text-aethex-400" /> <Command className="h-5 w-5 text-aethex-300" />
<CardTitle className="text-lg">Blog Posts</CardTitle> <CardTitle>Quick actions</CardTitle>
</div> </div>
<CardDescription> <CardDescription>Launch frequent administrative workflows.</CardDescription>
Manage blog content stored in Supabase </CardHeader>
</CardDescription> <CardContent className="grid gap-3">
</CardHeader> {quickActions.map(({ label, description, icon: ActionIcon, action }) => (
<CardContent className="space-y-3"> <button
<div className="flex justify-between"> key={label}
<Button type="button"
size="sm" onClick={action}
variant="outline" className="group flex items-start gap-3 rounded-lg border border-border/30 bg-background/40 px-4 py-3 text-left transition hover:border-aethex-400/60 hover:bg-background/60"
disabled={loadingPosts} >
onClick={async () => { <ActionIcon className="mt-0.5 h-5 w-5 text-aethex-400 transition group-hover:text-aethex-200" />
try { <div className="space-y-1">
setLoadingPosts(true); <p className="font-medium text-foreground">{label}</p>
const res = await fetch("/api/blog?limit=100"); <p className="text-sm text-muted-foreground">{description}</p>
const data = res.ok ? await res.json() : []; </div>
if (Array.isArray(data)) setBlogPosts(data); </button>
} finally { ))}
setLoadingPosts(false); </CardContent>
</Card>
<Card className="bg-card/60 border-border/40 backdrop-blur">
<CardHeader>
<div className="flex items-center gap-2">
<Shield className="h-5 w-5 text-green-400" />
<CardTitle>Access control</CardTitle>
</div>
<CardDescription>Owner-only access enforced via Supabase roles.</CardDescription>
</CardHeader>
<CardContent className="space-y-3 text-sm text-muted-foreground">
<ul className="space-y-2 leading-relaxed">
<li>
Owner email: <span className="text-foreground">{ownerEmail}</span>
</li>
<li>Roles are provisioned automatically on owner sign-in.</li>
<li>Grant additional admins by updating Supabase role assignments.</li>
</ul>
<div className="flex gap-2">
<Button variant="outline" size="sm" onClick={() => setActiveTab("community")}>
View members
</Button>
<Button variant="outline" size="sm" onClick={() => navigate("/support")}>
Contact support
</Button>
</div>
</CardContent>
</Card>
</div>
</TabsContent>
<TabsContent value="content" className="space-y-6">
<Card className="bg-card/60 border-border/40 backdrop-blur">
<CardHeader>
<div className="flex items-center gap-2">
<PenTool className="h-5 w-5 text-aethex-300" />
<CardTitle>Content overview</CardTitle>
</div>
<CardDescription>
{publishedPosts} published {publishedPosts === 1 ? "post" : "posts"} · {loadingPosts ? "refreshing content…" : "latest Supabase sync"}
</CardDescription>
</CardHeader>
<CardContent className="text-sm text-muted-foreground space-y-2">
<p>
Drafts and announcements appear instantly on the public blog after saving. Use scheduled releases for major updates and keep thumbnails optimised for 1200×630.
</p>
</CardContent>
</Card>
<Card className="bg-card/60 border-border/40 backdrop-blur">
<CardHeader>
<div className="flex items-center gap-2">
<PenTool className="h-5 w-5 text-aethex-400" />
<CardTitle className="text-lg">Blog posts</CardTitle>
</div>
<CardDescription>Manage blog content stored in Supabase</CardDescription>
</CardHeader>
<CardContent className="space-y-3">
<div className="flex flex-wrap items-center gap-3">
<Button
size="sm"
variant="outline"
disabled={loadingPosts}
onClick={async () => {
try {
setLoadingPosts(true);
const res = await fetch("/api/blog?limit=100");
const data = res.ok ? await res.json() : [];
if (Array.isArray(data)) setBlogPosts(data);
} finally {
setLoadingPosts(false);
}
}}
>
{loadingPosts ? "Refreshing…" : "Refresh"}
</Button>
<Button
size="sm"
onClick={() =>
setBlogPosts([
{
title: "New Post",
slug: "new-post",
category: "General",
},
...blogPosts,
])
} }
}} >
> Add post
Refresh </Button>
</Button> </div>
<Button
size="sm"
onClick={() =>
setBlogPosts([
{
title: "New Post",
slug: "new-post",
category: "General",
},
...blogPosts,
])
}
>
Add Post
</Button>
</div>
{blogPosts.map((p, i) => ( {blogPosts.length === 0 && (
<div <p className="text-sm text-muted-foreground">
key={p.id || p.slug || i} No posts loaded yet. Use Refresh or Add post to start managing content.
className="p-3 rounded border border-border/40 space-y-2" </p>
> )}
<div className="grid md:grid-cols-2 gap-2">
<input {blogPosts.map((p, i) => (
className="bg-background/50 border border-border/40 rounded px-2 py-1 text-sm" <div
placeholder="Title" key={p.id || p.slug || i}
value={p.title || ""} className="space-y-2 rounded border border-border/40 bg-background/40 p-3"
>
<div className="grid gap-2 md:grid-cols-2">
<input
className="bg-background/50 border border-border/40 rounded px-2 py-1 text-sm"
placeholder="Title"
value={p.title || ""}
onChange={(e) => {
const next = blogPosts.slice();
next[i] = { ...next[i], title: e.target.value };
setBlogPosts(next);
}}
/>
<input
className="bg-background/50 border border-border/40 rounded px-2 py-1 text-sm"
placeholder="Slug"
value={p.slug || ""}
onChange={(e) => {
const next = blogPosts.slice();
next[i] = { ...next[i], slug: e.target.value };
setBlogPosts(next);
}}
/>
</div>
<div className="grid gap-2 md:grid-cols-2">
<input
className="bg-background/50 border border-border/40 rounded px-2 py-1 text-sm"
placeholder="Author"
value={p.author || ""}
onChange={(e) => {
const n = blogPosts.slice();
n[i] = { ...n[i], author: e.target.value };
setBlogPosts(n);
}}
/>
<input
className="bg-background/50 border border-border/40 rounded px-2 py-1 text-sm"
placeholder="Date"
value={p.date || ""}
onChange={(e) => {
const n = blogPosts.slice();
n[i] = { ...n[i], date: e.target.value };
setBlogPosts(n);
}}
/>
</div>
<div className="grid gap-2 md:grid-cols-3">
<input
className="bg-background/50 border border-border/40 rounded px-2 py-1 text-sm"
placeholder="Read time (e.g., 8 min read)"
value={p.read_time || ""}
onChange={(e) => {
const n = blogPosts.slice();
n[i] = { ...n[i], read_time: e.target.value };
setBlogPosts(n);
}}
/>
<input
className="bg-background/50 border border-border/40 rounded px-2 py-1 text-sm"
placeholder="Category"
value={p.category || ""}
onChange={(e) => {
const n = blogPosts.slice();
n[i] = { ...n[i], category: e.target.value };
setBlogPosts(n);
}}
/>
<input
className="bg-background/50 border border-border/40 rounded px-2 py-1 text-sm"
placeholder="Image URL"
value={p.image || ""}
onChange={(e) => {
const n = blogPosts.slice();
n[i] = { ...n[i], image: e.target.value };
setBlogPosts(n);
}}
/>
</div>
<textarea
className="w-full bg-background/50 border border-border/40 rounded px-2 py-1 text-sm"
rows={2}
placeholder="Excerpt"
value={p.excerpt || ""}
onChange={(e) => { onChange={(e) => {
const next = blogPosts.slice(); const n = blogPosts.slice();
next[i] = { ...next[i], title: e.target.value }; n[i] = { ...n[i], excerpt: e.target.value };
setBlogPosts(next); setBlogPosts(n);
}} }}
/> />
<input <textarea
className="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"
placeholder="Slug" rows={6}
value={p.slug || ""} placeholder="Body HTML"
value={p.body_html || ""}
onChange={(e) => { onChange={(e) => {
const next = blogPosts.slice(); const n = blogPosts.slice();
next[i] = { ...next[i], slug: e.target.value }; n[i] = { ...n[i], body_html: e.target.value };
setBlogPosts(next); setBlogPosts(n);
}} }}
/> />
<div className="flex justify-end gap-2">
<Button
size="sm"
variant="outline"
onClick={() => deletePost(i)}
>
Delete
</Button>
<Button size="sm" onClick={() => savePost(i)}>
Save
</Button>
</div>
</div> </div>
<div className="grid md:grid-cols-2 gap-2"> ))}
<input </CardContent>
className="bg-background/50 border border-border/40 rounded px-2 py-1 text-sm" </Card>
placeholder="Author" </TabsContent>
value={p.author || ""}
onChange={(e) => { <TabsContent value="community" className="space-y-6">
const n = blogPosts.slice(); <Card className="bg-card/60 border-border/40 backdrop-blur">
n[i] = { ...n[i], author: e.target.value }; <CardHeader>
setBlogPosts(n); <div className="flex items-center gap-2">
}} <Users className="h-5 w-5 text-cyan-300" />
/> <CardTitle>Member directory</CardTitle>
<input </div>
className="bg-background/50 border border-border/40 rounded px-2 py-1 text-sm" <CardDescription>
placeholder="Date" Showing {displayProfiles.length} of {totalMembers} profiles.
value={p.date || ""} </CardDescription>
onChange={(e) => { </CardHeader>
const n = blogPosts.slice(); <CardContent>
n[i] = { ...n[i], date: e.target.value }; {displayProfiles.length === 0 ? (
setBlogPosts(n); <p className="text-sm text-muted-foreground">
}} No profiles were returned from the identity service. Trigger a refresh or invite teammates to join AeThex.
/> </p>
) : (
<div className="overflow-x-auto">
<table className="min-w-full text-sm">
<thead>
<tr className="text-left text-xs uppercase tracking-wide text-muted-foreground">
<th className="pb-2 pr-4 font-medium">Name</th>
<th className="pb-2 pr-4 font-medium">Email</th>
<th className="pb-2 pr-4 font-medium">Role</th>
<th className="pb-2 font-medium">Loyalty</th>
</tr>
</thead>
<tbody className="divide-y divide-border/30">
{displayProfiles.map((profile) => (
<tr key={profile.id} className="text-foreground/90">
<td className="py-2 pr-4">
{profile.full_name || profile.username || "Unknown"}
</td>
<td className="py-2 pr-4 text-muted-foreground">
{profile.email || "—"}
</td>
<td className="py-2 pr-4">
<Badge variant="outline">{profile.role || "member"}</Badge>
</td>
<td className="py-2 text-muted-foreground">
{profile.loyalty_points ?? 0}
</td>
</tr>
))}
</tbody>
</table>
</div> </div>
<div className="grid md:grid-cols-3 gap-2"> )}
<input </CardContent>
className="bg-background/50 border border-border/40 rounded px-2 py-1 text-sm" </Card>
placeholder="Read time (e.g., 8 min read)"
value={p.read_time || ""} <Card className="bg-card/60 border-border/40 backdrop-blur">
onChange={(e) => { <CardHeader>
const n = blogPosts.slice(); <div className="flex items-center gap-2">
n[i] = { ...n[i], read_time: e.target.value }; <UserCog className="h-5 w-5 text-teal-300" />
setBlogPosts(n); <CardTitle>Community actions</CardTitle>
}} </div>
/> <CardDescription>Grow the network and celebrate contributors.</CardDescription>
<input </CardHeader>
className="bg-background/50 border border-border/40 rounded px-2 py-1 text-sm" <CardContent className="flex flex-wrap gap-2">
placeholder="Category" <Button size="sm" onClick={() => navigate("/community")}>Open community hub</Button>
value={p.category || ""} <Button size="sm" variant="outline" onClick={() => navigate("/mentorship")}>
onChange={(e) => { Manage mentorships
const n = blogPosts.slice(); </Button>
n[i] = { ...n[i], category: e.target.value }; <Button size="sm" variant="outline" onClick={() => navigate("/support")}>Support queue</Button>
setBlogPosts(n); </CardContent>
}} </Card>
/> </TabsContent>
<input
className="bg-background/50 border border-border/40 rounded px-2 py-1 text-sm" <TabsContent value="operations" className="space-y-6">
placeholder="Image URL" <div className="grid gap-6 lg:grid-cols-2">
value={p.image || ""} <Card className="bg-card/60 border-border/40 backdrop-blur lg:col-span-2">
onChange={(e) => { <CardHeader>
const n = blogPosts.slice(); <div className="flex items-center gap-2">
n[i] = { ...n[i], image: e.target.value }; <Settings className="h-5 w-5 text-yellow-300" />
setBlogPosts(n); <CardTitle>Featured studios</CardTitle>
}}
/>
</div> </div>
<textarea <CardDescription>Control studios highlighted across AeThex experiences.</CardDescription>
className="w-full bg-background/50 border border-border/40 rounded px-2 py-1 text-sm" </CardHeader>
rows={2} <CardContent className="space-y-3">
placeholder="Excerpt" {studios.map((s, i) => (
value={p.excerpt || ""} <div
onChange={(e) => { key={`${s.name}-${i}`}
const n = blogPosts.slice(); className="space-y-2 rounded border border-border/40 bg-background/40 p-3"
n[i] = { ...n[i], excerpt: e.target.value }; >
setBlogPosts(n); <div className="grid gap-2 md:grid-cols-2">
}} <input
/> className="bg-background/50 border border-border/40 rounded px-2 py-1 text-sm"
<textarea value={s.name}
className="w-full bg-background/50 border border-border/40 rounded px-2 py-1 text-sm" onChange={(e) => {
rows={6} const next = studios.slice();
placeholder="Body HTML" next[i] = { ...next[i], name: e.target.value };
value={p.body_html || ""} setStudios(next);
onChange={(e) => { }}
const n = blogPosts.slice(); placeholder="Studio name"
n[i] = { ...n[i], body_html: e.target.value }; />
setBlogPosts(n); <input
}} className="bg-background/50 border border-border/40 rounded px-2 py-1 text-sm"
/> value={s.tagline || ""}
<div className="flex justify-end gap-2"> onChange={(e) => {
const next = studios.slice();
next[i] = { ...next[i], tagline: e.target.value };
setStudios(next);
}}
placeholder="Tagline"
/>
</div>
<div className="grid gap-2 md:grid-cols-2">
<input
className="bg-background/50 border border-border/40 rounded px-2 py-1 text-sm"
value={s.metrics || ""}
onChange={(e) => {
const next = studios.slice();
next[i] = { ...next[i], metrics: e.target.value };
setStudios(next);
}}
placeholder="Metrics (e.g., 1B+ sessions)"
/>
<input
className="bg-background/50 border border-border/40 rounded px-2 py-1 text-sm"
value={(s.specialties || []).join(", ")}
onChange={(e) => {
const next = studios.slice();
next[i] = {
...next[i],
specialties: e.target.value
.split(",")
.map((v) => v.trim())
.filter(Boolean),
};
setStudios(next);
}}
placeholder="Specialties (comma separated)"
/>
</div>
<div className="flex justify-end">
<Button
size="sm"
variant="outline"
onClick={() => setStudios(studios.filter((_, idx) => idx !== i))}
>
Remove
</Button>
</div>
</div>
))}
<div className="flex flex-wrap justify-between gap-2">
<Button <Button
size="sm" size="sm"
variant="outline" variant="outline"
onClick={() => deletePost(i)} onClick={() => setStudios([...studios, { name: "New Studio" }])}
> >
Delete Add studio
</Button> </Button>
<Button size="sm" onClick={() => savePost(i)}>
Save
</Button>
</div>
</div>
))}
</CardContent>
</Card>
<Card className="bg-card/50 border-border/50">
<CardHeader>
<div className="flex items-center gap-2">
<Settings className="h-5 w-5 text-yellow-400" />
<CardTitle className="text-lg">Featured Studios</CardTitle>
</div>
<CardDescription>
Manage studios shown on Game Development page
</CardDescription>
</CardHeader>
<CardContent className="space-y-3">
{studios.map((s, i) => (
<div
key={i}
className="p-3 rounded border border-border/40 space-y-2"
>
<div className="grid md:grid-cols-2 gap-2">
<input
className="bg-background/50 border border-border/40 rounded px-2 py-1 text-sm"
value={s.name}
onChange={(e) => {
const next = studios.slice();
next[i] = { ...next[i], name: e.target.value };
setStudios(next);
}}
placeholder="Studio name"
/>
<input
className="bg-background/50 border border-border/40 rounded px-2 py-1 text-sm"
value={s.tagline || ""}
onChange={(e) => {
const next = studios.slice();
next[i] = { ...next[i], tagline: e.target.value };
setStudios(next);
}}
placeholder="Tagline"
/>
</div>
<div className="grid md:grid-cols-2 gap-2">
<input
className="bg-background/50 border border-border/40 rounded px-2 py-1 text-sm"
value={s.metrics || ""}
onChange={(e) => {
const next = studios.slice();
next[i] = { ...next[i], metrics: e.target.value };
setStudios(next);
}}
placeholder="Metrics (e.g., 1B+ sessions)"
/>
<input
className="bg-background/50 border border-border/40 rounded px-2 py-1 text-sm"
value={(s.specialties || []).join(", ")}
onChange={(e) => {
const next = studios.slice();
next[i] = {
...next[i],
specialties: e.target.value
.split(",")
.map((v) => v.trim())
.filter(Boolean),
};
setStudios(next);
}}
placeholder="Specialties (comma separated)"
/>
</div>
<div className="flex justify-end gap-2">
<Button <Button
size="sm" size="sm"
variant="outline" onClick={async () => {
onClick={() => { const resp = await fetch("/api/featured-studios", {
const next = studios.filter((_, idx) => idx !== i); method: "POST",
setStudios(next); headers: { "Content-Type": "application/json" },
body: JSON.stringify({ studios }),
});
if (!resp.ok) {
aethexToast.error({
title: "Save failed",
description: "Unable to persist featured studios.",
});
} else {
aethexToast.success({
title: "Studios saved",
description: "Featured studios updated successfully.",
});
}
}} }}
> >
Remove Save studios
</Button> </Button>
</div> </div>
</div> </CardContent>
))} </Card>
<div className="flex justify-between">
<Button
size="sm"
variant="outline"
onClick={() =>
setStudios([...studios, { name: "New Studio" }])
}
>
Add Studio
</Button>
<Button
size="sm"
onClick={async () => {
const resp = await fetch("/api/featured-studios", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ studios }),
});
if (!resp.ok) {
alert("Failed to save studios");
return;
}
}}
>
Save
</Button>
</div>
</CardContent>
</Card>
<Card className="bg-card/50 border-border/50"> <Card className="bg-card/60 border-border/40 backdrop-blur">
<CardHeader> <CardHeader>
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<Activity className="h-5 w-5 text-orange-400" /> <Activity className="h-5 w-5 text-orange-300" />
<CardTitle className="text-lg">System Status</CardTitle> <CardTitle>System status</CardTitle>
</div>
<CardDescription>Auth, database, and services</CardDescription>
</CardHeader>
<CardContent>
<ul className="text-sm text-muted-foreground space-y-1">
<li>Auth: Operational</li>
<li>Database: Operational (mock fallback available)</li>
<li>Realtime: Operational</li>
</ul>
</CardContent>
</Card>
<Card className="bg-card/50 border-border/50">
<CardHeader>
<div className="flex items-center gap-2">
<Rocket className="h-5 w-5 text-purple-400" />
<CardTitle className="text-lg">Quick Actions</CardTitle>
</div>
<CardDescription>Common admin operations</CardDescription>
</CardHeader>
<CardContent className="flex flex-wrap gap-2">
<Button size="sm" onClick={() => navigate("/dashboard")}>
View Dashboard
</Button>
<Button
size="sm"
variant="outline"
onClick={() => navigate("/onboarding")}
>
Run Onboarding
</Button>
</CardContent>
</Card>
<Card className="bg-card/50 border-border/50">
<CardHeader>
<div className="flex items-center gap-2">
<UserCog className="h-5 w-5 text-teal-400" />
<CardTitle className="text-lg">Your Account</CardTitle>
</div>
<CardDescription>Signed in as {user.email}</CardDescription>
</CardHeader>
<CardContent>
<div className="text-sm text-muted-foreground">
You have full administrative access.
</div>
</CardContent>
</Card>
<Card className="bg-card/50 border-border/50">
<CardHeader>
<div className="flex items-center gap-2">
<Users className="h-5 w-5 text-cyan-400" />
<CardTitle className="text-lg">Demo Accounts</CardTitle>
</div>
<CardDescription>
Managed by{" "}
<span className="text-foreground">mrpiglr@gmail.com</span>
</CardDescription>
</CardHeader>
<CardContent className="space-y-3">
{managedProfiles.length === 0 && (
<div className="text-sm text-muted-foreground">
No developer profiles found yet.
</div>
)}
{managedProfiles.map((p) => (
<div
key={p.id}
className="flex items-center justify-between p-2 rounded border border-border/40"
>
<div>
<div className="font-medium">
{p.full_name || p.username}
</div>
<div className="text-xs text-muted-foreground">
{p.email}
</div>
</div> </div>
<Badge variant="outline">Managed</Badge> <CardDescription>Auth, database, and realtime services.</CardDescription>
</div> </CardHeader>
))} <CardContent className="text-sm text-muted-foreground space-y-1">
</CardContent> <p>Auth: Operational</p>
</Card> <p>Database: Operational (mock fallback available)</p>
</div> <p>Realtime: Operational</p>
</CardContent>
</Card>
<Card className="bg-card/60 border-border/40 backdrop-blur">
<CardHeader>
<div className="flex items-center gap-2">
<UserCog className="h-5 w-5 text-teal-300" />
<CardTitle>Your account</CardTitle>
</div>
<CardDescription>Owner privileges are active.</CardDescription>
</CardHeader>
<CardContent className="text-sm text-muted-foreground space-y-2">
<p>Signed in as {user.email}.</p>
<p>You have full administrative access across AeThex services.</p>
</CardContent>
</Card>
</div>
</TabsContent>
</Tabs>
</div> </div>
</div> </div>
</Layout> </Layout>