Refactor root admin dashboards and settings
This commit is contained in:
+59
-242
@@ -1,33 +1,15 @@
|
||||
import {
|
||||
ExternalLink,
|
||||
FolderKanban,
|
||||
Globe2,
|
||||
ImageIcon,
|
||||
LockKeyhole,
|
||||
ShieldAlert,
|
||||
Shapes,
|
||||
} from "lucide-react";
|
||||
import Link from "next/link";
|
||||
import { FolderKanban, ImageIcon, LockKeyhole, ShieldAlert } from "lucide-react";
|
||||
import { redirect } from "next/navigation";
|
||||
|
||||
import { Container } from "@/components/layout/container";
|
||||
import { StatsCard } from "@/components/dashboard/stats-card";
|
||||
import { MotionFade } from "@/components/motion-fade";
|
||||
import { DashboardCard } from "@/components/dashboard/dashboard-card";
|
||||
import { RootDashboardShell } from "@/components/root/root-dashboard-shell";
|
||||
import { Badge, type BadgeProps } from "@/components/ui/badge";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import { Card, CardContent, CardDescription, CardHeader } from "@/components/ui/card";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { Separator } from "@/components/ui/separator";
|
||||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableHead,
|
||||
TableHeader,
|
||||
TableRow,
|
||||
} from "@/components/ui/table";
|
||||
import {
|
||||
clearAdminSessionCookie,
|
||||
getAdminLockState,
|
||||
@@ -40,10 +22,7 @@ import {
|
||||
} from "@/lib/admin-auth";
|
||||
import { getMaintenanceMode } from "@/lib/app-config";
|
||||
import { getAdminMediaAssets } from "@/lib/media";
|
||||
import {
|
||||
getAdminPortfolioCategories,
|
||||
getAdminPortfolioProjects,
|
||||
} from "@/lib/portfolio";
|
||||
import { getAdminPortfolioCategories, getAdminPortfolioProjects } from "@/lib/portfolio";
|
||||
|
||||
type RootPageProps = {
|
||||
searchParams?: {
|
||||
@@ -55,31 +34,15 @@ export const dynamic = "force-dynamic";
|
||||
|
||||
const copy = {
|
||||
title: "Uebersicht",
|
||||
subtitle: "Interner Bereich fuer Kennzahlen und zentrale Navigation.",
|
||||
subtitle: "Kompakter Status zu Portfolio, Media und Wartung.",
|
||||
overview: "Uebersicht",
|
||||
maintenance: "Wartungsmodus",
|
||||
maintenanceTitle: "Wartungsmodus",
|
||||
maintenanceVisitorsClosed: "Website fuer Besucher geschlossen",
|
||||
maintenanceVisitorsShort: "Besucher gesperrt",
|
||||
maintenanceDescription:
|
||||
"Wenn aktiv, werden alle Seiten auf die Coming Soon Seite weitergeleitet.",
|
||||
maintenanceOpen: "Website fuer Besucher offen",
|
||||
maintenanceAction: "Zur Wartungsseite",
|
||||
uiKitTitle: "UI Kit",
|
||||
uiKitDescription: "Globale Referenz fuer Cards, Buttons, Inputs und Surface Levels.",
|
||||
uiKitAction: "Zur UI Kit",
|
||||
media: "Media",
|
||||
siteSettings: "SEO",
|
||||
siteSettings: "Settings",
|
||||
portfolio: "Portfolio",
|
||||
portfolioTitle: "Portfolio",
|
||||
portfolioDescription: "Kategorien, Projekte, Abschnitte und Dateien verwalten.",
|
||||
portfolioAction: "Zum Portfolio",
|
||||
mediaTitle: "Media Library",
|
||||
mediaDescription: "Uploads, externe URLs und Verwendungsorte zentral verwalten.",
|
||||
mediaAction: "Zur Media Library",
|
||||
siteSettingsTitle: "SEO",
|
||||
siteSettingsDescription: "Favicon, Site Name, Description, Subhead und globale SEO Defaults verwalten.",
|
||||
siteSettingsAction: "Zu SEO",
|
||||
uiKit: "UI Kit",
|
||||
logout: "Ausloggen",
|
||||
backToSite: "Zur Website",
|
||||
loginTitle: "Root Login",
|
||||
loginText: "Nur autorisierte Nutzer duerfen diesen Bereich verwenden.",
|
||||
passwordLabel: "Passwort",
|
||||
@@ -88,34 +51,13 @@ const copy = {
|
||||
lockedLogin: "Zu viele Fehlversuche. Bitte spaeter erneut versuchen.",
|
||||
configMissing: "ADMIN_PASSWORD und ADMIN_AUTH_SECRET fehlen in env.",
|
||||
basicAuthMissing: "ROOT_BASIC_AUTH_USER und ROOT_BASIC_AUTH_PASS fehlen in env.",
|
||||
logout: "Ausloggen",
|
||||
backToSite: "Zur Website",
|
||||
uiKit: "UI Kit",
|
||||
tableTitle: "Bereiche",
|
||||
tableDescription: "Direkter Zugriff auf alle Verwaltungsbereiche mit aktuellem Status.",
|
||||
tableSection: "Bereich",
|
||||
tableSummary: "Zusammenfassung",
|
||||
tableStatus: "Status",
|
||||
tableAction: "Aktion",
|
||||
maintenanceStatusOn: "Aktiv",
|
||||
maintenanceStatusOff: "Inaktiv",
|
||||
authStatusOk: "Bereit",
|
||||
authStatusWarning: "Pruefen",
|
||||
authCard: "Root Zugang",
|
||||
authDescription: "Admin Passwort und Session Signatur sind gesetzt.",
|
||||
authDescriptionWarning: "Eine oder mehrere Root Variablen fehlen und sollten geprueft werden.",
|
||||
mediaCountDescription: "Verfuegbare Dateien fuer Portfolio und Inhalte.",
|
||||
projectCountDescription: "Gespeicherte Portfolio Projekte im System.",
|
||||
categoryCountDescription: "Portfolio Kategorien mit eigener Sortierung.",
|
||||
systemStatusTitle: "Systemstatus",
|
||||
systemStatusDescription: "Technischer Zustand des Root Bereichs und der wichtigsten Inhaltsbereiche.",
|
||||
maintenanceLabel: "Wartung",
|
||||
contentLabel: "Inhalte",
|
||||
contentDescription: "Portfolio, Media und Kategorien sind direkt aus diesem Bereich erreichbar.",
|
||||
publishedProjects: "Veroeffentlicht",
|
||||
totalProjects: "Projekte",
|
||||
totalCategories: "Kategorien",
|
||||
totalMedia: "Dateien",
|
||||
maintenanceOn: "Aktiv",
|
||||
maintenanceOff: "Inaktiv",
|
||||
portfolioOnline: "Portfolio online",
|
||||
mediaImages: "Media Images",
|
||||
maintenanceStatus: "Maintenance",
|
||||
drafts: "Drafts",
|
||||
activeCategories: "Active Categories",
|
||||
};
|
||||
|
||||
export default async function RootPage({ searchParams }: RootPageProps) {
|
||||
@@ -232,57 +174,11 @@ export default async function RootPage({ searchParams }: RootPageProps) {
|
||||
getAdminPortfolioProjects(),
|
||||
getAdminMediaAssets(),
|
||||
]);
|
||||
|
||||
const publishedProjects = projects.filter((project) => project.isPublished).length;
|
||||
const authHealthy = authConfigured && basicConfigured;
|
||||
const sectionRows: Array<{
|
||||
label: string;
|
||||
summary: string;
|
||||
status: string;
|
||||
href: string;
|
||||
badgeVariant: BadgeProps["variant"];
|
||||
action: string;
|
||||
}> = [
|
||||
{
|
||||
label: copy.maintenanceTitle,
|
||||
summary: copy.maintenanceDescription,
|
||||
status: maintenanceEnabled ? copy.maintenanceStatusOn : copy.maintenanceStatusOff,
|
||||
href: "/root/maintenance",
|
||||
badgeVariant: maintenanceEnabled ? "warning" : "success",
|
||||
action: copy.maintenanceAction,
|
||||
},
|
||||
{
|
||||
label: copy.mediaTitle,
|
||||
summary: `${mediaAssets.length} ${copy.totalMedia}`,
|
||||
status: copy.authStatusOk,
|
||||
href: "/root/media",
|
||||
badgeVariant: "outline" as const,
|
||||
action: copy.mediaAction,
|
||||
},
|
||||
{
|
||||
label: copy.siteSettingsTitle,
|
||||
summary: copy.siteSettingsDescription,
|
||||
status: copy.authStatusOk,
|
||||
href: "/root/site-settings",
|
||||
badgeVariant: "outline" as const,
|
||||
action: copy.siteSettingsAction,
|
||||
},
|
||||
{
|
||||
label: copy.portfolioTitle,
|
||||
summary: `${projects.length} ${copy.totalProjects} / ${categories.length} ${copy.totalCategories}`,
|
||||
status: `${publishedProjects} ${copy.publishedProjects}`,
|
||||
href: "/root/portfolio",
|
||||
badgeVariant: "outline" as const,
|
||||
action: copy.portfolioAction,
|
||||
},
|
||||
{
|
||||
label: copy.uiKitTitle,
|
||||
summary: copy.uiKitDescription,
|
||||
status: copy.authStatusOk,
|
||||
href: "/root/ui-kit",
|
||||
badgeVariant: "outline" as const,
|
||||
action: copy.uiKitAction,
|
||||
},
|
||||
];
|
||||
const draftProjects = projects.length - publishedProjects;
|
||||
const activeCategories = categories.filter((category) => category.isActive).length;
|
||||
const imageCount = mediaAssets.filter((asset) => asset.kind === "IMAGE").length;
|
||||
|
||||
return (
|
||||
<RootDashboardShell
|
||||
@@ -293,131 +189,52 @@ export default async function RootPage({ searchParams }: RootPageProps) {
|
||||
headerDescription={copy.subtitle}
|
||||
>
|
||||
<div className="space-y-6">
|
||||
<section className="grid gap-4 md:grid-cols-2 xl:grid-cols-4">
|
||||
<section className="grid gap-4 md:grid-cols-2 xl:grid-cols-3">
|
||||
<MotionFade delay={0.05}>
|
||||
<DashboardCard
|
||||
title={copy.maintenanceTitle}
|
||||
value={maintenanceEnabled ? copy.maintenanceStatusOn : copy.maintenanceStatusOff}
|
||||
description={copy.maintenanceDescription}
|
||||
icon={ShieldAlert}
|
||||
/>
|
||||
</MotionFade>
|
||||
<MotionFade delay={0.1}>
|
||||
<DashboardCard
|
||||
title={copy.totalMedia}
|
||||
value={String(mediaAssets.length)}
|
||||
description={copy.mediaCountDescription}
|
||||
icon={ImageIcon}
|
||||
/>
|
||||
</MotionFade>
|
||||
<MotionFade delay={0.15}>
|
||||
<DashboardCard
|
||||
title={copy.totalProjects}
|
||||
value={String(projects.length)}
|
||||
description={copy.projectCountDescription}
|
||||
icon={Globe2}
|
||||
/>
|
||||
</MotionFade>
|
||||
<MotionFade delay={0.2}>
|
||||
<DashboardCard
|
||||
title={copy.totalCategories}
|
||||
value={String(categories.length)}
|
||||
description={copy.categoryCountDescription}
|
||||
<StatsCard
|
||||
title={copy.portfolioOnline}
|
||||
value={String(publishedProjects)}
|
||||
icon={FolderKanban}
|
||||
className="h-full"
|
||||
footer={(
|
||||
<div className="flex flex-wrap gap-x-4 gap-y-2 text-sm text-muted-foreground">
|
||||
<span>{copy.drafts}: {draftProjects}</span>
|
||||
<span>{copy.activeCategories}: {activeCategories}</span>
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
</MotionFade>
|
||||
<MotionFade delay={0.25}>
|
||||
<DashboardCard
|
||||
title={copy.siteSettingsTitle}
|
||||
value={copy.authStatusOk}
|
||||
description={copy.siteSettingsDescription}
|
||||
icon={Shapes}
|
||||
|
||||
<MotionFade delay={0.1}>
|
||||
<StatsCard
|
||||
title={copy.mediaImages}
|
||||
value={String(imageCount)}
|
||||
icon={ImageIcon}
|
||||
description="Anzahl der Bilder innerhalb der Media Library."
|
||||
className="h-full"
|
||||
/>
|
||||
</MotionFade>
|
||||
</section>
|
||||
|
||||
<section className="grid gap-6 xl:grid-cols-[minmax(0,1.5fr)_minmax(320px,1fr)]">
|
||||
<MotionFade delay={0.3}>
|
||||
<Card className="border-border/70 bg-card/95 shadow-sm">
|
||||
<CardHeader>
|
||||
<CardTitle>{copy.tableTitle}</CardTitle>
|
||||
<CardDescription>{copy.tableDescription}</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead>{copy.tableSection}</TableHead>
|
||||
<TableHead>{copy.tableSummary}</TableHead>
|
||||
<TableHead>{copy.tableStatus}</TableHead>
|
||||
<TableHead className="text-right">{copy.tableAction}</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{sectionRows.map((row) => (
|
||||
<TableRow key={row.href}>
|
||||
<TableCell className="font-medium">{row.label}</TableCell>
|
||||
<TableCell className="text-muted-foreground">{row.summary}</TableCell>
|
||||
<TableCell>
|
||||
<Badge variant={row.badgeVariant}>{row.status}</Badge>
|
||||
</TableCell>
|
||||
<TableCell className="text-right">
|
||||
<Button asChild variant="ghost" size="sm">
|
||||
<Link href={row.href}>
|
||||
{row.action}
|
||||
<ExternalLink className="h-4 w-4" />
|
||||
</Link>
|
||||
</Button>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<MotionFade delay={0.15}>
|
||||
<StatsCard
|
||||
title={copy.maintenanceStatus}
|
||||
value={maintenanceEnabled ? copy.maintenanceOn : copy.maintenanceOff}
|
||||
icon={ShieldAlert}
|
||||
className="h-full"
|
||||
footer={(
|
||||
<div className="flex items-center justify-between gap-3 text-sm">
|
||||
<span className="text-muted-foreground">
|
||||
{maintenanceEnabled
|
||||
? "Website ist aktuell fuer Besucher gesperrt."
|
||||
: "Website ist aktuell offen."}
|
||||
</span>
|
||||
<Badge variant={maintenanceEnabled ? "warning" : "success"}>
|
||||
{maintenanceEnabled ? copy.maintenanceOn : copy.maintenanceOff}
|
||||
</Badge>
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
</MotionFade>
|
||||
|
||||
<div className="grid gap-6">
|
||||
<MotionFade delay={0.35}>
|
||||
<Card className="border-border/70 bg-card/95 shadow-sm">
|
||||
<CardHeader>
|
||||
<CardTitle>{copy.systemStatusTitle}</CardTitle>
|
||||
<CardDescription>{copy.systemStatusDescription}</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
<div className="rounded-nested border border-border bg-muted/40 p-4">
|
||||
<div className="flex items-center justify-between gap-3">
|
||||
<div>
|
||||
<p className="text-sm font-medium text-foreground">{copy.authCard}</p>
|
||||
<p className="mt-1 text-sm text-muted-foreground">
|
||||
{authHealthy ? copy.authDescription : copy.authDescriptionWarning}
|
||||
</p>
|
||||
</div>
|
||||
<Badge variant={authHealthy ? "success" : "warning"}>
|
||||
{authHealthy ? copy.authStatusOk : copy.authStatusWarning}
|
||||
</Badge>
|
||||
</div>
|
||||
</div>
|
||||
<Separator />
|
||||
<div className="space-y-3 text-sm">
|
||||
<div className="flex items-center justify-between gap-4">
|
||||
<span className="text-muted-foreground">{copy.maintenanceLabel}</span>
|
||||
<Badge variant={maintenanceEnabled ? "warning" : "success"}>
|
||||
{maintenanceEnabled ? copy.maintenanceStatusOn : copy.maintenanceStatusOff}
|
||||
</Badge>
|
||||
</div>
|
||||
<div className="flex items-center justify-between gap-4">
|
||||
<span className="text-muted-foreground">{copy.contentLabel}</span>
|
||||
<span className="font-medium text-foreground">
|
||||
{`${publishedProjects} ${copy.publishedProjects} / ${projects.length} ${copy.totalProjects}`}
|
||||
</span>
|
||||
</div>
|
||||
<p className="text-muted-foreground">{copy.contentDescription}</p>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</MotionFade>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</RootDashboardShell>
|
||||
|
||||
Reference in New Issue
Block a user