fix(admin): wrap post-auth data loads in try/catch to surface and survive SC render errors
Deploy to route.crispygoat.com / deploy (push) Successful in 3m9s
Deploy to route.crispygoat.com / deploy (push) Successful in 3m9s
The /admin page was throwing a Server Components render error (digest
4266906817) in production. The exact throw point was not visible from
the browser (Next.js hides it for security) and server logs were not
available at debugging time.
The layout and page both call data-loading functions (getActiveBrandId,
listBrandsForAdmin, supabase brands lookup) that could throw on a
transient DB/network failure, and these calls had no try/catch. A
single failed call would crash the entire admin shell.
Wrap each call in try/catch with console.error so:
1. The page renders with sensible defaults if a call fails
2. The actual error is logged server-side (visible via the digest in
the admin error boundary or PM2/Docker logs)
3. The admin shell stays functional even if a single data source is
down
No behavior change on the happy path.
This commit is contained in:
@@ -64,13 +64,25 @@ export default async function AdminLayout({ children }: { children: React.ReactN
|
|||||||
redirect("/change-password");
|
redirect("/change-password");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Resolve the active brand (URL > cookie > legacy > first of brand_ids)
|
// Resolve the active brand (URL > cookie > legacy > first of brand_ids).
|
||||||
const activeBrandId = await getActiveBrandId(adminUser);
|
// Wrapped in try/catch so a transient brand-resolution failure can't
|
||||||
|
// crash the whole admin shell — we fall back to null (no active brand).
|
||||||
|
let activeBrandId: string | null = null;
|
||||||
|
try {
|
||||||
|
activeBrandId = await getActiveBrandId(adminUser);
|
||||||
|
} catch (err) {
|
||||||
|
console.error("[admin/layout] getActiveBrandId failed:", err);
|
||||||
|
}
|
||||||
|
|
||||||
// Fetch accessible brands for the sidebar BrandSelector. We do this
|
// Fetch accessible brands for the sidebar BrandSelector. Wrapped in
|
||||||
// unconditionally — `listBrandsForAdmin` is cheap and the sidebar
|
// try/catch so the sidebar renders empty rather than crashing the page
|
||||||
// decides whether to show the dropdown.
|
// if the brands query fails.
|
||||||
const brands = await listBrandsForAdmin();
|
let brands: Awaited<ReturnType<typeof listBrandsForAdmin>> = [];
|
||||||
|
try {
|
||||||
|
brands = await listBrandsForAdmin();
|
||||||
|
} catch (err) {
|
||||||
|
console.error("[admin/layout] listBrandsForAdmin failed:", err);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ToastProviderWrapper>
|
<ToastProviderWrapper>
|
||||||
|
|||||||
+13
-8
@@ -27,16 +27,21 @@ export default async function AdminPage() {
|
|||||||
// Resolve active brand via the canonical resolver (URL > cookie > legacy brand_id
|
// Resolve active brand via the canonical resolver (URL > cookie > legacy brand_id
|
||||||
// > first of brand_ids). For platform_admin in dev mode this is null, so we
|
// > first of brand_ids). For platform_admin in dev mode this is null, so we
|
||||||
// fall back to the first brand in the brands table to keep the dashboard's
|
// fall back to the first brand in the brands table to keep the dashboard's
|
||||||
// "Active Products" stat in sync with the billing page.
|
// "Active Products" stat in sync with the billing page. Wrapped in try/catch
|
||||||
|
// so a transient DB/network failure can't crash the whole admin page.
|
||||||
let dashboardBrandId: string | null = adminUser ? await getActiveBrandId(adminUser) : null;
|
let dashboardBrandId: string | null = adminUser ? await getActiveBrandId(adminUser) : null;
|
||||||
if (!dashboardBrandId && adminUser?.role === "platform_admin") {
|
if (!dashboardBrandId && adminUser?.role === "platform_admin") {
|
||||||
const { data: firstBrand } = await supabase
|
try {
|
||||||
.from("brands")
|
const { data: firstBrand } = await supabase
|
||||||
.select("id")
|
.from("brands")
|
||||||
.limit(1)
|
.select("id")
|
||||||
.single();
|
.limit(1)
|
||||||
if (firstBrand?.id) {
|
.single();
|
||||||
dashboardBrandId = firstBrand.id;
|
if (firstBrand?.id) {
|
||||||
|
dashboardBrandId = firstBrand.id;
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error("[admin/page] supabase brands lookup failed:", err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user