feat(admin): add v2 layout with AdminShell and offline dispatcher
This commit is contained in:
@@ -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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user