fix(admin): mount Clerk client SDK on admin panel to stop UNAUTHORIZED
The admin panel (src/app/p/[secret]/layout.tsx) never wrapped its tree in ClerkThemeProvider, unlike (app)/layout.tsx — a SpeedUp-Plan P0 optimization that deliberately kept Clerk's client bundle off routes without a <UserButton/>. Consequence: no client-side session-token refresh ran on admin pages. The initial page load stayed authenticated (Clerk's server-side handshake reissues the token on full document navigations), but subsequent tRPC XHRs — e.g. legal.publish — started failing with UNAUTHORIZED/401 a few minutes in, since that handshake only fires for navigations, not fetch calls. Confirmed live: a fresh page reload always restored a working session, matching this exactly. Mounting ClerkThemeProvider in the admin layout re-adds background token refresh. Also gives the legal-publish error handler an actionable message for UNAUTHORIZED specifically (distinct from other failures), since a residual case can still occur after a real 7-day session expiry.
This commit is contained in:
1 parent
e9378d9d7a
commit
c225c9aef7
2 files changed
+48
-24
No files matched your search
@@ -5,6 +5,7 @@ import { createTRPCCaller } from "@/trpc/server";
|
||||
import { timingSafeStringEqual } from "@/lib/timing-safe-compare";
|
||||
import { AdminMobileNav } from "@/components/admin/AdminMobileNav";
|
||||
import { AdminSidebarNav } from "@/components/admin/AdminSidebarNav";
|
||||
import { ClerkThemeProvider } from "@/components/providers/ClerkThemeProvider";
|
||||
import {
|
||||
LayoutDashboard,
|
||||
Users,
|
||||
@@ -80,31 +81,43 @@ export default async function AdminLayout({ children, params }: AdminLayoutProps
|
||||
}));
|
||||
|
||||
return (
|
||||
// Admin stays permanently dark regardless of the visitor site's theme
|
||||
// toggle — a moderator's ops tool, not a marketing surface — but now
|
||||
// pinned to PawFeed's own dark palette (the `dark` class resolves the
|
||||
// real design tokens) instead of a generic, disconnected zinc scaffold.
|
||||
<div className="dark flex min-h-screen flex-col bg-background text-foreground md:flex-row">
|
||||
{/* Mobile topbar + drawer — hidden md: and up */}
|
||||
<AdminMobileNav nav={navForMobile} base={base} />
|
||||
// ClerkThemeProvider mounts Clerk's client SDK, which keeps the short-lived
|
||||
// __session cookie refreshed in the background. Without it (as before),
|
||||
// this route tree never renewed the token client-side — the initial page
|
||||
// load kept working (Clerk's server-side "handshake" reissues the token on
|
||||
// full document navigations) but subsequent tRPC XHRs (e.g. legal.publish)
|
||||
// started failing with UNAUTHORIZED/401 a few minutes into a session, since
|
||||
// that handshake only runs for navigations, not fetch calls. This does
|
||||
// re-add the ~240 KiB Clerk client bundle that (app)/layout.tsx's
|
||||
// SpeedUp-Plan P0 deliberately kept off non-admin routes — acceptable here
|
||||
// since the admin panel is an internal ops tool, not perf-critical.
|
||||
<ClerkThemeProvider>
|
||||
{/* Admin stays permanently dark regardless of the visitor site's theme
|
||||
toggle — a moderator's ops tool, not a marketing surface — but now
|
||||
pinned to PawFeed's own dark palette (the `dark` class resolves the
|
||||
real design tokens) instead of a generic, disconnected zinc scaffold. */}
|
||||
<div className="dark flex min-h-screen flex-col bg-background text-foreground md:flex-row">
|
||||
{/* Mobile topbar + drawer — hidden md: and up */}
|
||||
<AdminMobileNav nav={navForMobile} base={base} />
|
||||
|
||||
{/* Sidebar — hidden below md:, permanent from md: up */}
|
||||
<aside className="hidden md:flex w-56 shrink-0 border-r border-border flex-col py-6 px-4 gap-1 h-screen sticky top-0 overflow-y-auto">
|
||||
<AdminSidebarNav base={base} nav={navForMobile} />
|
||||
{/* Sidebar — hidden below md:, permanent from md: up */}
|
||||
<aside className="hidden md:flex w-56 shrink-0 border-r border-border flex-col py-6 px-4 gap-1 h-screen sticky top-0 overflow-y-auto">
|
||||
<AdminSidebarNav base={base} nav={navForMobile} />
|
||||
|
||||
<div className="mt-auto pt-3 border-t border-border">
|
||||
<Link
|
||||
href="/feed"
|
||||
className="flex items-center gap-2.5 px-3 py-2 rounded-lg text-sm text-muted-foreground hover:bg-muted hover:text-foreground transition-colors"
|
||||
>
|
||||
<ArrowLeft className="h-4 w-4 shrink-0" />
|
||||
Zurück
|
||||
</Link>
|
||||
</div>
|
||||
</aside>
|
||||
<div className="mt-auto pt-3 border-t border-border">
|
||||
<Link
|
||||
href="/feed"
|
||||
className="flex items-center gap-2.5 px-3 py-2 rounded-lg text-sm text-muted-foreground hover:bg-muted hover:text-foreground transition-colors"
|
||||
>
|
||||
<ArrowLeft className="h-4 w-4 shrink-0" />
|
||||
Zurück
|
||||
</Link>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
{/* Content */}
|
||||
<main className="min-w-0 flex-1 overflow-auto p-4 md:p-8">{children}</main>
|
||||
</div>
|
||||
{/* Content */}
|
||||
<main className="min-w-0 flex-1 overflow-auto p-4 md:p-8">{children}</main>
|
||||
</div>
|
||||
</ClerkThemeProvider>
|
||||
);
|
||||
}
|
||||
@@ -45,7 +45,18 @@ export default function AdminLegalPage() {
|
||||
toast.success("Veröffentlicht — Owner sehen die Meldung beim nächsten Laden der App");
|
||||
setChangeSummary("");
|
||||
},
|
||||
onError: () => toast.error("Veröffentlichung fehlgeschlagen"),
|
||||
// UNAUTHORIZED means the Clerk session cookie went stale (see
|
||||
// ClerkThemeProvider in layout.tsx for the background-refresh fix) —
|
||||
// distinct from every other failure, so surface it with an actionable
|
||||
// message instead of the generic one. The typed text is preserved
|
||||
// (no setChangeSummary("") here) so a reload doesn't lose it.
|
||||
onError: (error) => {
|
||||
if (error.data?.code === "UNAUTHORIZED") {
|
||||
toast.error("Session abgelaufen — bitte Seite neu laden und erneut versuchen");
|
||||
} else {
|
||||
toast.error("Veröffentlichung fehlgeschlagen");
|
||||
}
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
|
||||
Reference in new issue
Block a user