Clean up admin hub UX: tabs, chrome, and redundant headers.
Local Build and Deploy / deploy (push) Failing after 53s

Wire orphan routes into hub tabs, group Radio into primary/tools rows, show theme/language once on desktop, and remove duplicate page titles under AdminHubChrome.

Co-authored-by: Cursor <[email protected]>
This commit is contained in:
SimoandCursor committed 2026-07-17 18:01:01 +02:00
1 parent 378705dd73
commit d2c0619f2f
75 files changed
+244 -915

No files matched your search

-25
View File
@@ -1,5 +1,3 @@
import { Key } from "lucide-react";
import Link from "next/link";
import { getTranslations } from "next-intl/server";
import {
createApiKey,
@@ -71,30 +69,7 @@ export default async function AdminRadioApiKeysPage() {
return (
<main>
<nav className="text-xs theme-text-muted dark:theme-text-muted mb-2">
<Link href="/admin/radio">{t("title")}</Link> ·{" "}
<Link href="/admin/radio/settings">{t("settings")}</Link> ·{" "}
<Link href="/admin/radio/api-keys">{t("apiKeys")}</Link> ·{" "}
<Link href="/admin/radio/autodj">{t("autoDj")}</Link> ·{" "}
<Link href="/admin/radio/embed">{t("embed")}</Link> ·{" "}
<Link href="/admin/radio/points">{t("points")}</Link> ·{" "}
<Link href="/admin/radio/monitoring">{t("monitoring.title")}</Link>
</nav>
<section className="mt-6">
<div className="flex items-center gap-3 mb-6">
<div className="w-10 h-10 rounded-xl bg-gradient-to-br from-[var(--admin-accent)]/20 to-[var(--admin-accent)]/5 grid place-items-center">
<Key size={20} className="text-[var(--admin-accent)]" />
</div>
<div>
<h1 className="m-0 text-xl font-extrabold text-[var(--admin-text)]">
{t("apiKeysForm.title")}
</h1>
<p className="m-0 text-xs text-[var(--admin-text-muted)] mt-0.5">
{t("apiKeysForm.subtitle")}
</p>
</div>
</div>
{dbError ? (
<div className="admin-card" style={{ marginTop: "1rem" }}>
-24
View File
@@ -1,6 +1,3 @@
import { Disc3 } from "lucide-react";
import Image from "next/image";
import Link from "next/link";
import { getTranslations } from "next-intl/server";
import {
createTrack,
@@ -75,29 +72,8 @@ export default async function AdminRadioAutoDjPage() {
return (
<main>
<nav className="text-xs theme-text-muted dark:theme-text-muted mb-2">
<Link href="/admin/radio">{t("title")}</Link> ·{" "}
<Link href="/admin/radio/settings">{t("settings")}</Link> ·{" "}
<Link href="/admin/radio/banners">{t("banners.title")}</Link> ·{" "}
<Link href="/admin/radio/ranks">{t("ranks")}</Link> ·{" "}
<Link href="/admin/radio/history">{t("history")}</Link> ·{" "}
<Link href="/admin/radio/autodj">{t("autoDj")}</Link>
</nav>
<section className="mt-6">
<div className="flex items-center gap-3 mb-6">
<div className="w-10 h-10 rounded-xl bg-gradient-to-br from-[var(--admin-accent)]/20 to-[var(--admin-accent)]/5 grid place-items-center">
<Disc3 size={20} className="text-[var(--admin-accent)]" />
</div>
<div>
<h1 className="m-0 text-xl font-extrabold text-[var(--admin-text)]">
{t("autoDjForm.title")}
</h1>
<p className="m-0 text-xs text-[var(--admin-text-muted)] mt-0.5">
{t("autoDjForm.subtitle")}
</p>
</div>
</div>
{dbError ? (
<div className="admin-card mb-6">
-24
View File
@@ -1,6 +1,4 @@
import { Image as LucideImage } from "lucide-react";
import NextImage from "next/image";
import Link from "next/link";
import { getTranslations } from "next-intl/server";
import {
createRadioBanner,
@@ -57,28 +55,6 @@ export default async function AdminRadioBannersPage() {
return (
<main>
<nav className="text-xs theme-text-muted dark:theme-text-muted mb-2">
<Link href="/admin/radio">{t("title")}</Link> ·{" "}
<Link href="/admin/radio/settings">{t("settings")}</Link> ·{" "}
<Link href="/admin/radio/banners">{t("banners.title")}</Link> ·{" "}
<Link href="/admin/radio/ranks">{t("ranks")}</Link> ·{" "}
<Link href="/admin/radio/history">{t("history")}</Link>
</nav>
<div className="flex items-center gap-3 mb-6">
<div className="w-10 h-10 rounded-xl bg-gradient-to-br from-[var(--admin-accent)]/20 to-[var(--admin-accent)]/5 grid place-items-center">
<LucideImage size={20} className="text-[var(--admin-accent)]" />
</div>
<div>
<h1 className="m-0 text-xl font-extrabold text-[var(--admin-text)]">
{t("banners.heading")}
</h1>
<p className="m-0 text-xs text-[var(--admin-text-muted)] mt-0.5">
{t("banners.description")}
</p>
</div>
</div>
{dbError ? (
<div className="admin-card mb-6">
<p className="text-xs theme-text-muted dark:theme-text-muted m-0">
-24
View File
@@ -1,4 +1,3 @@
import { Code } from "lucide-react";
import Link from "next/link";
import { getTranslations } from "next-intl/server";
import { StatusCard } from "@/components/admin/dashboard";
@@ -41,30 +40,7 @@ export default async function AdminRadioEmbedPage() {
return (
<main>
<nav className="text-xs theme-text-muted dark:theme-text-muted mb-2">
<Link href="/admin/radio">{t("title")}</Link> ·{" "}
<Link href="/admin/radio/settings">{t("settings")}</Link> ·{" "}
<Link href="/admin/radio/embed">{t("embed")}</Link> ·{" "}
<Link href="/admin/radio/banners">{t("banners.title")}</Link> ·{" "}
<Link href="/admin/radio/ranks">{t("ranks")}</Link> ·{" "}
<Link href="/admin/radio/history">{t("history")}</Link>
</nav>
<section className="mt-6">
<div className="flex items-center gap-3 mb-6">
<div className="w-10 h-10 rounded-xl bg-gradient-to-br from-[var(--admin-accent)]/20 to-[var(--admin-accent)]/5 grid place-items-center">
<Code size={20} className="text-[var(--admin-accent)]" />
</div>
<div>
<h1 className="m-0 text-xl font-extrabold text-[var(--admin-text)]">
{t("embedPage.title")}
</h1>
<p className="m-0 text-xs text-[var(--admin-text-muted)] mt-0.5">
{t("embedPage.subtitle")}
</p>
</div>
</div>
<div className="grid grid-cols-[repeat(auto-fit,minmax(180px,1fr))] gap-3.5 mb-6">
<StatusCard
label={t("embedPage.streamUrl")}
-24
View File
@@ -1,5 +1,3 @@
import { Clock } from "lucide-react";
import Link from "next/link";
import { getTranslations } from "next-intl/server";
import { StatusCard } from "@/components/admin/dashboard";
import { prisma } from "@/lib/prisma";
@@ -76,28 +74,6 @@ export default async function AdminRadioHistoryPage() {
return (
<main>
<nav className="text-xs theme-text-muted dark:theme-text-muted mb-2">
<Link href="/admin/radio">{t("title")}</Link> ·{" "}
<Link href="/admin/radio/settings">{t("settings")}</Link> ·{" "}
<Link href="/admin/radio/banners">{t("banners.title")}</Link> ·{" "}
<Link href="/admin/radio/ranks">{t("ranks")}</Link> ·{" "}
<Link href="/admin/radio/history">{t("history")}</Link>
</nav>
<div className="flex items-center gap-3 mb-6">
<div className="w-10 h-10 rounded-xl bg-gradient-to-br from-[var(--admin-accent)]/20 to-[var(--admin-accent)]/5 grid place-items-center">
<Clock size={20} className="text-[var(--admin-accent)]" />
</div>
<div>
<h1 className="m-0 text-xl font-extrabold text-[var(--admin-text)]">
{t("historyPage.title")}
</h1>
<p className="m-0 text-xs text-[var(--admin-text-muted)] mt-0.5">
{t("historyPage.subtitle")}
</p>
</div>
</div>
{dbError ? (
<div className="admin-card mb-6">
<p className="text-xs theme-text-muted dark:theme-text-muted m-0">
-24
View File
@@ -1,5 +1,3 @@
import { Shield } from "lucide-react";
import Link from "next/link";
import { getTranslations } from "next-intl/server";
import { deleteShout } from "@/actions/admin-radio-moderation";
import { StatusCard } from "@/components/admin/dashboard";
@@ -41,28 +39,6 @@ export default async function AdminRadioModerationPage() {
return (
<main>
<nav className="text-xs theme-text-muted dark:theme-text-muted mb-2">
<Link href="/admin/radio">{t("title")}</Link> ·{" "}
<Link href="/admin/radio/moderation">{t("moderation")}</Link> ·{" "}
<Link href="/admin/radio/settings">{t("settings")}</Link> ·{" "}
<Link href="/admin/radio/banners">{t("banners.title")}</Link> ·{" "}
<Link href="/admin/radio/ranks">{t("ranks")}</Link> ·{" "}
<Link href="/admin/radio/history">{t("history")}</Link>
</nav>
<div className="flex items-center gap-3 mb-6">
<div className="w-10 h-10 rounded-xl bg-gradient-to-br from-[var(--admin-accent)]/20 to-[var(--admin-accent)]/5 grid place-items-center">
<Shield size={20} className="text-[var(--admin-accent)]" />
</div>
<div>
<h1 className="m-0 text-xl font-extrabold text-[var(--admin-text)]">
{t("moderationPage.title")}
</h1>
<p className="m-0 text-xs text-[var(--admin-text-muted)] mt-0.5">
{t("moderationPage.subtitle")}
</p>
</div>
</div>
<div className="grid grid-cols-[repeat(auto-fit,minmax(180px,1fr))] gap-3.5 mb-6">
<StatusCard
-24
View File
@@ -1,5 +1,3 @@
import { Radio } from "lucide-react";
import Link from "next/link";
import { getTranslations } from "next-intl/server";
import { DiagnosticRow, StatusCard } from "@/components/admin/dashboard";
import { siteSettings } from "@/lib/services/site-settings";
@@ -213,30 +211,8 @@ export default async function AdminRadioMonitoringPage() {
return (
<main>
<nav className="text-xs theme-text-muted dark:theme-text-muted mb-2">
<Link href="/admin/radio">{t("title")}</Link> ·{" "}
<Link href="/admin/radio/settings">{t("settings")}</Link> ·{" "}
<Link href="/admin/radio/monitoring">{t("monitoring.title")}</Link> ·{" "}
<Link href="/admin/radio/banners">{t("banners.title")}</Link> ·{" "}
<Link href="/admin/radio/ranks">{t("ranks")}</Link> ·{" "}
<Link href="/admin/radio/history">{t("history")}</Link>
</nav>
<section className="mt-6">
<div className="flex items-center gap-3 mb-6">
<div className="w-10 h-10 rounded-xl bg-gradient-to-br from-[var(--admin-accent)]/20 to-[var(--admin-accent)]/5 grid place-items-center">
<Radio size={20} className="text-[var(--admin-accent)]" />
</div>
<div>
<h1 className="m-0 text-xl font-extrabold text-[var(--admin-text)]">
{t("monitoring.title")}
</h1>
<p className="m-0 text-xs text-[var(--admin-text-muted)] mt-0.5">
{t("monitoring.subtitle", { timeout: FETCH_TIMEOUT_MS / 1000 })}
</p>
</div>
</div>
<div className="grid grid-cols-[repeat(auto-fit,minmax(180px,1fr))] gap-3.5 mb-6">
<StatusCard
label={t("monitoring.stream")}
-14
View File
@@ -1,4 +1,3 @@
import Link from "next/link";
import { getTranslations } from "next-intl/server";
import { deleteShout } from "@/actions/admin-radio-moderation";
import { StatusCard } from "@/components/admin/dashboard";
@@ -110,19 +109,6 @@ export default async function AdminRadioPage() {
return (
<main>
<nav className="text-xs theme-text-muted dark:theme-text-muted mb-2">
<Link href="/admin/radio">{t("title")}</Link> ·{" "}
<Link href="/admin/radio/settings">{t("settings")}</Link> ·{" "}
<Link href="/admin/radio/banners">{t("banners.title")}</Link> ·{" "}
<Link href="/admin/radio/ranks">{t("ranks")}</Link> ·{" "}
<Link href="/admin/radio/history">{t("history")}</Link>
</nav>
<h1>{t("title")}</h1>
<p className="text-xs theme-text-muted dark:theme-text-muted">
{t("subtitle")}
</p>
<div className="grid grid-cols-[repeat(auto-fit,minmax(180px,1fr))] gap-3.5 mb-6">
<StatusCard
label={t("shouts")}
-24
View File
@@ -1,5 +1,3 @@
import { Star } from "lucide-react";
import Link from "next/link";
import { getTranslations } from "next-intl/server";
import { savePoints } from "@/actions/admin-radio-points";
import { StatusCard } from "@/components/admin/dashboard";
@@ -63,30 +61,8 @@ export default async function AdminRadioPointsPage({
return (
<main>
<nav className="text-xs theme-text-muted dark:theme-text-muted mb-2">
<Link href="/admin/radio">{t("title")}</Link> ·{" "}
<Link href="/admin/radio/settings">{t("settings")}</Link> ·{" "}
<Link href="/admin/radio/points">{t("points")}</Link> ·{" "}
<Link href="/admin/radio/banners">{t("banners.title")}</Link> ·{" "}
<Link href="/admin/radio/ranks">{t("ranks")}</Link> ·{" "}
<Link href="/admin/radio/history">{t("history")}</Link>
</nav>
<section className="mt-6">
<div className="flex items-center gap-3 mb-6">
<div className="w-10 h-10 rounded-xl bg-gradient-to-br from-[var(--admin-accent)]/20 to-[var(--admin-accent)]/5 grid place-items-center">
<Star size={20} className="text-[var(--admin-accent)]" />
</div>
<div>
<h1 className="m-0 text-xl font-extrabold text-[var(--admin-text)]">
{t("pointsForm.title")}
</h1>
<p className="m-0 text-xs text-[var(--admin-text-muted)] mt-0.5">
{t("pointsForm.subtitle")}
</p>
</div>
</div>
{saved === "1" ? (
<p>
<span className="inline-block text-[0.72rem] font-bold px-2 py-0.5 rounded-full bg-[var(--admin-success-subtle)] text-[var(--admin-text)]">
-24
View File
@@ -1,5 +1,3 @@
import { Medal } from "lucide-react";
import Link from "next/link";
import { getTranslations } from "next-intl/server";
import {
createRadioRank,
@@ -45,28 +43,6 @@ export default async function AdminRadioRanksPage() {
return (
<main>
<nav className="text-xs theme-text-muted dark:theme-text-muted mb-2">
<Link href="/admin/radio">{t("title")}</Link> ·{" "}
<Link href="/admin/radio/settings">{t("settings")}</Link> ·{" "}
<Link href="/admin/radio/banners">{t("banners.title")}</Link> ·{" "}
<Link href="/admin/radio/ranks">{t("ranks")}</Link> ·{" "}
<Link href="/admin/radio/history">{t("history")}</Link>
</nav>
<div className="flex items-center gap-3 mb-6">
<div className="w-10 h-10 rounded-xl bg-gradient-to-br from-[var(--admin-accent)]/20 to-[var(--admin-accent)]/5 grid place-items-center">
<Medal size={20} className="text-[var(--admin-accent)]" />
</div>
<div>
<h1 className="m-0 text-xl font-extrabold text-[var(--admin-text)]">
{t("ranksForm.title")}
</h1>
<p className="m-0 text-xs text-[var(--admin-text-muted)] mt-0.5">
{t("ranksForm.subtitle")}
</p>
</div>
</div>
{dbError ? (
<div className="admin-card mb-6">
<p className="text-xs theme-text-muted dark:theme-text-muted m-0">
-24
View File
@@ -1,5 +1,3 @@
import { Settings } from "lucide-react";
import Link from "next/link";
import { getTranslations } from "next-intl/server";
import {
saveRadioSetting,
@@ -397,28 +395,6 @@ export default async function AdminRadioSettingsPage() {
return (
<main>
<nav className="text-xs theme-text-muted dark:theme-text-muted mb-2">
<Link href="/admin/radio">{t("title")}</Link> ·{" "}
<Link href="/admin/radio/settings">{t("settings")}</Link> ·{" "}
<Link href="/admin/radio/banners">{t("banners.title")}</Link> ·{" "}
<Link href="/admin/radio/ranks">{t("ranks")}</Link> ·{" "}
<Link href="/admin/radio/history">{t("history")}</Link>
</nav>
<div className="flex items-center gap-3 mb-6">
<div className="w-10 h-10 rounded-xl bg-gradient-to-br from-[var(--admin-accent)]/20 to-[var(--admin-accent)]/5 grid place-items-center">
<Settings size={20} className="text-[var(--admin-accent)]" />
</div>
<div>
<h1 className="m-0 text-xl font-extrabold text-[var(--admin-text)]">
{t("settingsForm.title")}
</h1>
<p className="m-0 text-xs text-[var(--admin-text-muted)] mt-0.5">
{t("settingsForm.subtitle")}
</p>
</div>
</div>
{dbError ? (
<div className="admin-card mb-6">
<p className="text-xs theme-text-muted dark:theme-text-muted m-0">