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.label} -
- ) : ( -
- {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 ( + + +
+
+

{title}

+

{value}

+
+ {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 ( -
+ Turnstile Cloudflare Schutz fuer das Kontaktformular. - +