feat(admin): add v2 layout with AdminShell and offline dispatcher

This commit is contained in:
Tyler
2026-06-17 14:42:49 -06:00
parent 827b9ef318
commit d629cfe3d5
2 changed files with 176 additions and 0 deletions
+88
View File
@@ -0,0 +1,88 @@
import AdminShell from "@/components/admin/AdminShell";
import AdminAccessDenied from "@/components/admin/AdminAccessDenied";
import { getAdminUser } from "@/lib/admin-permissions";
import { getActiveBrandId } from "@/lib/brand-scope";
import { listBrandsForAdmin } from "@/actions/brands";
import { getEnabledAddons } from "@/actions/billing/stripe-portal";
import { ToastProvider } from "@/components/admin/Toast";
import { ToastContainer } from "@/components/admin/ToastContainer";
import { SmoothViewTransition } from "@/components/transitions/SmoothViewTransition";
import { RouteAnnouncer } from "@/components/transitions/RouteAnnouncer";
import { redirect } from "next/navigation";
export const dynamic = "force-dynamic";
function ToastProviderWrapper({ children }: { children: React.ReactNode }) {
return (
<ToastProvider>
{children}
<ToastContainer />
</ToastProvider>
);
}
export default async function AdminV2Layout({ children }: { children: React.ReactNode }) {
let adminUser = null;
try {
adminUser = await getAdminUser();
} catch {
return (
<ToastProviderWrapper>
<AdminAccessDenied message="Failed to verify authentication." />
</ToastProviderWrapper>
);
}
if (!adminUser) {
return (
<ToastProviderWrapper>
<AdminAccessDenied message="You don't have admin access." />
</ToastProviderWrapper>
);
}
if (adminUser.must_change_password) {
redirect("/change-password");
}
// Resolve brand + brands + addons with graceful fallbacks. A failure in
// any of these must not crash the whole shell — the AdminShell renders
// empty state (no brands) rather than a 500.
let activeBrandId: string | null = null;
try {
activeBrandId = await getActiveBrandId(adminUser);
} catch (err) {
console.error("[admin/v2/layout] getActiveBrandId failed:", err);
}
let brands: Awaited<ReturnType<typeof listBrandsForAdmin>> = [];
try {
brands = await listBrandsForAdmin();
} catch (err) {
console.error("[admin/v2/layout] listBrandsForAdmin failed:", err);
}
let enabledAddons: Record<string, boolean> = {};
if (activeBrandId) {
try {
enabledAddons = await getEnabledAddons(activeBrandId);
} catch (err) {
console.error("[admin/v2/layout] getEnabledAddons failed:", err);
}
}
return (
<ToastProviderWrapper>
<AdminShell
userRole={adminUser.role}
brandIds={adminUser.brand_ids}
activeBrandId={activeBrandId}
brands={brands}
enabledAddons={enabledAddons}
>
<SmoothViewTransition>{children}</SmoothViewTransition>
<RouteAnnouncer />
</AdminShell>
</ToastProviderWrapper>
);
}