diff --git a/app/root/maintenance/page.tsx b/app/root/maintenance/page.tsx
index c327350..0611150 100644
--- a/app/root/maintenance/page.tsx
+++ b/app/root/maintenance/page.tsx
@@ -17,7 +17,7 @@ const copy = {
maintenance: "Wartungsmodus",
uiKit: "UI Kit",
media: "Media",
- siteSettings: "SEO",
+ siteSettings: "Settings",
portfolio: "Portfolio",
maintenanceText: "Wenn aktiv, werden alle Seiten auf die Coming Soon Seite weitergeleitet.",
maintenanceOn: "Aktiv",
diff --git a/app/root/marquee/page.tsx b/app/root/marquee/page.tsx
index d35e579..c16f757 100644
--- a/app/root/marquee/page.tsx
+++ b/app/root/marquee/page.tsx
@@ -18,7 +18,7 @@ const copy = {
maintenance: "Wartungsmodus",
uiKit: "UI Kit",
media: "Media",
- siteSettings: "SEO",
+ siteSettings: "Settings",
marquee: "Marquee",
smtp: "SMTP",
portfolio: "Portfolio",
diff --git a/app/root/media/actions.ts b/app/root/media/actions.ts
index be80265..d98acf8 100644
--- a/app/root/media/actions.ts
+++ b/app/root/media/actions.ts
@@ -43,7 +43,6 @@ export async function createMediaAssetAction(formData: FormData) {
kind,
label: String(formData.get("label") ?? ""),
uploadFile: formData.get("file"),
- externalUrl: String(formData.get("externalUrl") ?? ""),
});
revalidateMediaPages();
diff --git a/app/root/media/page.tsx b/app/root/media/page.tsx
index d309ae1..0817367 100644
--- a/app/root/media/page.tsx
+++ b/app/root/media/page.tsx
@@ -1,29 +1,12 @@
-/* eslint-disable @next/next/no-img-element */
-
-import { ExternalLink, FileType2, ImageIcon, Link2, Tag, Trash2, Upload } from "lucide-react";
-import Link from "next/link";
import { redirect } from "next/navigation";
-import { MotionFade } from "@/components/motion-fade";
import { FlashMessage } from "@/components/root/flash-message";
+import { MediaLibraryManager } from "@/components/root/media-library-manager";
+import { MotionFade } from "@/components/motion-fade";
import { RootDashboardShell } from "@/components/root/root-dashboard-shell";
-import { AppCard } from "@/components/ui/app-card";
-import { Button } from "@/components/ui/button";
-import { CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
-import { Input } from "@/components/ui/input";
-import { Label } from "@/components/ui/label";
-import {
- Select,
- SelectContent,
- SelectItem,
- SelectTrigger,
- SelectValue,
-} from "@/components/ui/select";
import { clearAdminSessionCookie, isAdminAuthenticated } from "@/lib/admin-auth";
import { getAdminMediaAssets } from "@/lib/media";
-import { createMediaAssetAction, deleteMediaAssetAction } from "./actions";
-
export const dynamic = "force-dynamic";
const copy = {
@@ -33,21 +16,10 @@ const copy = {
maintenance: "Wartungsmodus",
uiKit: "UI Kit",
media: "Media",
- siteSettings: "SEO",
+ siteSettings: "Settings",
portfolio: "Portfolio",
logout: "Ausloggen",
backToSite: "Zur Website",
- createTitle: "Neue Datei",
- createDescription: "Datei hochladen oder externe URL zentral fuer spaetere Wiederverwendung speichern.",
- label: "Bezeichnung",
- kind: "Typ",
- uploadFile: "Datei hochladen",
- externalUrl: "Externe URL",
- saveMedia: "Datei speichern",
- open: "Oeffnen",
- delete: "Loeschen",
- usages: "Verwendungen",
- noAssets: "Noch keine Dateien vorhanden.",
};
type RootMediaPageProps = {
@@ -92,138 +64,7 @@ export default async function RootMediaPage({ searchParams }: RootMediaPageProps
) : null}
-
-
-
- {copy.createTitle}
- {copy.createDescription}
-
-
-
-
-
-
-
-
- {mediaAssets.map((asset, index) => (
-
-
-
- {asset.label}
-
- {asset.kind}
- {asset.source}
- {asset.usages.length} {copy.usages}
-
-
-
- {asset.kind === "IMAGE" ? (
-
-

-
- ) : (
-
- {asset.fileName}
-
- )}
-
-
-
{asset.url}
-
-
-
- {copy.open}
-
-
-
-
- {asset.usages.length > 0 ? (
-
- {asset.usages.map((usage) => (
-
- {usage.usageType} / {usage.entityType} / {usage.fieldKey}
-
- ))}
-
- ) : null}
-
-
-
-
-
- ))}
-
-
- {mediaAssets.length === 0 ? (
-
-
-
- {copy.noAssets}
-
-
-
- ) : null}
+
);
diff --git a/app/root/page.tsx b/app/root/page.tsx
index 6b4c98e..03ee884 100644
--- a/app/root/page.tsx
+++ b/app/root/page.tsx
@@ -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 (
-
+
-
-
-
-
-
-
-
-
-
-
+ {copy.drafts}: {draftProjects}
+ {copy.activeCategories}: {activeCategories}
+
+ )}
/>
-
-
+
-
-
-
-
-
- {copy.tableTitle}
- {copy.tableDescription}
-
-
-
-
-
- {copy.tableSection}
- {copy.tableSummary}
- {copy.tableStatus}
- {copy.tableAction}
-
-
-
- {sectionRows.map((row) => (
-
- {row.label}
- {row.summary}
-
- {row.status}
-
-
-
-
-
- ))}
-
-
-
-
+
+
+
+ {maintenanceEnabled
+ ? "Website ist aktuell fuer Besucher gesperrt."
+ : "Website ist aktuell offen."}
+
+
+ {maintenanceEnabled ? copy.maintenanceOn : copy.maintenanceOff}
+
+
+ )}
+ />
-
-
-
-
-
- {copy.systemStatusTitle}
- {copy.systemStatusDescription}
-
-
-
-
-
-
{copy.authCard}
-
- {authHealthy ? copy.authDescription : copy.authDescriptionWarning}
-
-
-
- {authHealthy ? copy.authStatusOk : copy.authStatusWarning}
-
-
-
-
-
-
- {copy.maintenanceLabel}
-
- {maintenanceEnabled ? copy.maintenanceStatusOn : copy.maintenanceStatusOff}
-
-
-
- {copy.contentLabel}
-
- {`${publishedProjects} ${copy.publishedProjects} / ${projects.length} ${copy.totalProjects}`}
-
-
-
{copy.contentDescription}
-
-
-
-
-
diff --git a/app/root/portfolio/categories/page.tsx b/app/root/portfolio/categories/page.tsx
index 0131b89..2c99402 100644
--- a/app/root/portfolio/categories/page.tsx
+++ b/app/root/portfolio/categories/page.tsx
@@ -18,7 +18,7 @@ const copy = {
maintenance: "Wartungsmodus",
uiKit: "UI Kit",
media: "Media",
- siteSettings: "SEO",
+ siteSettings: "Settings",
portfolio: "Portfolio",
logout: "Ausloggen",
backToSite: "Zur Website",
diff --git a/app/root/portfolio/page.tsx b/app/root/portfolio/page.tsx
index e55b69c..2999605 100644
--- a/app/root/portfolio/page.tsx
+++ b/app/root/portfolio/page.tsx
@@ -16,7 +16,7 @@ const copy = {
maintenance: "Wartungsmodus",
uiKit: "UI Kit",
media: "Media",
- siteSettings: "SEO",
+ siteSettings: "Settings",
portfolio: "Portfolio",
logout: "Ausloggen",
backToSite: "Zur Website",
diff --git a/app/root/portfolio/projects/[id]/page.tsx b/app/root/portfolio/projects/[id]/page.tsx
index de2bfcb..d0ea2bf 100644
--- a/app/root/portfolio/projects/[id]/page.tsx
+++ b/app/root/portfolio/projects/[id]/page.tsx
@@ -25,7 +25,7 @@ const copy = {
maintenance: "Wartungsmodus",
uiKit: "UI Kit",
media: "Media",
- siteSettings: "SEO",
+ siteSettings: "Settings",
portfolio: "Portfolio",
logout: "Ausloggen",
backToSite: "Zur Website",
diff --git a/app/root/portfolio/projects/new/page.tsx b/app/root/portfolio/projects/new/page.tsx
index 980c2e0..d1df229 100644
--- a/app/root/portfolio/projects/new/page.tsx
+++ b/app/root/portfolio/projects/new/page.tsx
@@ -19,7 +19,7 @@ const copy = {
maintenance: "Wartungsmodus",
uiKit: "UI Kit",
media: "Media",
- siteSettings: "SEO",
+ siteSettings: "Settings",
portfolio: "Portfolio",
logout: "Ausloggen",
backToSite: "Zur Website",
diff --git a/app/root/portfolio/projects/page.tsx b/app/root/portfolio/projects/page.tsx
index a29b629..47f15c8 100644
--- a/app/root/portfolio/projects/page.tsx
+++ b/app/root/portfolio/projects/page.tsx
@@ -16,7 +16,7 @@ const copy = {
maintenance: "Wartungsmodus",
uiKit: "UI Kit",
media: "Media",
- siteSettings: "SEO",
+ siteSettings: "Settings",
portfolio: "Portfolio",
logout: "Ausloggen",
backToSite: "Zur Website",
diff --git a/app/root/site-settings/page.tsx b/app/root/site-settings/page.tsx
index 1474157..b9b8572 100644
--- a/app/root/site-settings/page.tsx
+++ b/app/root/site-settings/page.tsx
@@ -17,13 +17,13 @@ import { saveSiteSettingsAction } from "./actions";
export const dynamic = "force-dynamic";
const copy = {
- title: "SEO",
- subtitle: "Globale SEO Einstellungen, Titelstruktur und Standardbilder verwalten.",
+ title: "Settings",
+ subtitle: "Globale Titel, Copy und Brand Assets verwalten.",
overview: "Uebersicht",
maintenance: "Wartungsmodus",
uiKit: "UI Kit",
media: "Media",
- siteSettings: "SEO",
+ siteSettings: "Settings",
smtp: "SMTP",
portfolio: "Portfolio",
logout: "Ausloggen",
diff --git a/app/root/smtp/contact-protection/page.tsx b/app/root/smtp/contact-protection/page.tsx
index c30c58b..e075723 100644
--- a/app/root/smtp/contact-protection/page.tsx
+++ b/app/root/smtp/contact-protection/page.tsx
@@ -18,7 +18,7 @@ const copy = {
maintenance: "Wartungsmodus",
uiKit: "UI Kit",
media: "Media",
- siteSettings: "SEO",
+ siteSettings: "Settings",
smtp: "SMTP",
contactProtection: "Contact Protection",
portfolio: "Portfolio",
diff --git a/app/root/smtp/page.tsx b/app/root/smtp/page.tsx
index 9bc3226..9882235 100644
--- a/app/root/smtp/page.tsx
+++ b/app/root/smtp/page.tsx
@@ -19,7 +19,7 @@ const copy = {
maintenance: "Wartungsmodus",
uiKit: "UI Kit",
media: "Media",
- siteSettings: "SEO",
+ siteSettings: "Settings",
smtp: "SMTP",
contactProtection: "Contact Protection",
portfolio: "Portfolio",
diff --git a/app/root/ui-kit/page.tsx b/app/root/ui-kit/page.tsx
index 8e69823..c903bb3 100644
--- a/app/root/ui-kit/page.tsx
+++ b/app/root/ui-kit/page.tsx
@@ -14,7 +14,7 @@ const copy = {
overview: "Uebersicht",
uiKit: "UI Kit",
media: "Media",
- siteSettings: "SEO",
+ siteSettings: "Settings",
portfolio: "Portfolio",
logout: "Ausloggen",
backToSite: "Zur Website",
diff --git a/components/dashboard/dashboard-card.tsx b/components/dashboard/dashboard-card.tsx
index eeb80cc..5552ce8 100644
--- a/components/dashboard/dashboard-card.tsx
+++ b/components/dashboard/dashboard-card.tsx
@@ -1,6 +1,6 @@
import type { LucideIcon } from "lucide-react";
-import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
+import { StatsCard } from "@/components/dashboard/stats-card";
type DashboardCardProps = {
title: string;
@@ -16,19 +16,6 @@ export function DashboardCard({
icon: Icon,
}: DashboardCardProps) {
return (
-
-
-
- {title}
- {value}
-
-
-
-
-
-
- {description}
-
-
+
);
}
diff --git a/components/dashboard/stats-card.tsx b/components/dashboard/stats-card.tsx
new file mode 100644
index 0000000..4b06a28
--- /dev/null
+++ b/components/dashboard/stats-card.tsx
@@ -0,0 +1,47 @@
+import type { LucideIcon } from "lucide-react";
+import type { ReactNode } from "react";
+
+import { AppCard } from "@/components/ui/app-card";
+import { CardContent } from "@/components/ui/card";
+
+type StatsCardProps = {
+ title: string;
+ value: string;
+ icon?: LucideIcon;
+ description?: string;
+ footer?: ReactNode;
+ className?: string;
+};
+
+export function StatsCard({
+ title,
+ value,
+ icon: Icon,
+ description,
+ footer,
+ className,
+}: StatsCardProps) {
+ return (
+
+
+
+
+ {Icon ? (
+
+
+
+ ) : null}
+
+
+ {description ? (
+ {description}
+ ) : null}
+
+ {footer ? footer : null}
+
+
+ );
+}
diff --git a/components/root/contact-protection-form.tsx b/components/root/contact-protection-form.tsx
index 3d45f60..c2f0a0e 100644
--- a/components/root/contact-protection-form.tsx
+++ b/components/root/contact-protection-form.tsx
@@ -22,13 +22,13 @@ export function ContactProtectionForm({
const [rateLimitEnabled, setRateLimitEnabled] = useState(initialSettings.rateLimit.enabled);
return (
-