fix: admin auth for prod Neon Auth deployment
Deploy to route.crispygoat.com / deploy (push) Successful in 3m59s
Deploy to route.crispygoat.com / deploy (push) Successful in 3m59s
- getAdminUser now properly supports platform_admin with 0 brand links and loads full brand_ids - createAdminUser action now inserts into admin_user_brands join table - Admin layout surfaces the signed-in email on Access Denied - AdminAccessDenied links to /login instead of dead-end /admin - Main dashboard uses direct pool query instead of dead supabase shim - Improved provision-admin.ts script for prod bootstrap (loads .env.production too)
This commit is contained in:
@@ -4,6 +4,7 @@ import { getAdminUser } from "@/lib/admin-permissions";
|
||||
import { getActiveBrandId } from "@/lib/brand-scope";
|
||||
import { listBrandsForAdmin } from "@/actions/brands";
|
||||
import { redirect } from "next/navigation";
|
||||
import { getSession } from "@/lib/auth";
|
||||
import "@/styles/admin-design-system.css";
|
||||
import { ToastProvider } from "@/components/admin/Toast";
|
||||
import { ToastContainer } from "@/components/admin/ToastContainer";
|
||||
@@ -47,13 +48,25 @@ export default async function AdminLayout({ children }: { children: React.ReactN
|
||||
);
|
||||
}
|
||||
|
||||
// Not authenticated
|
||||
// Not authenticated / not provisioned
|
||||
if (!adminUser) {
|
||||
// Best-effort: surface the Neon Auth identity so the user (or support) knows
|
||||
// which account was checked. getAdminUser already logged details.
|
||||
let attemptedEmail: string | null = null;
|
||||
try {
|
||||
const { data: session } = await getSession();
|
||||
attemptedEmail = session?.user?.email ?? null;
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
const message = attemptedEmail
|
||||
? `Your account (${attemptedEmail}) does not have admin access. Contact a platform administrator to be provisioned.`
|
||||
: "Your account does not have admin access.";
|
||||
return (
|
||||
<ToastProviderWrapper>
|
||||
<AdminSidebar userRole={null} />
|
||||
<div className="min-h-screen lg:pl-60 admin-section" style={{ backgroundColor: "var(--admin-bg)" }}>
|
||||
<AdminAccessDenied message="Your account does not have admin access." />
|
||||
<AdminAccessDenied message={message} />
|
||||
</div>
|
||||
</ToastProviderWrapper>
|
||||
);
|
||||
|
||||
+10
-9
@@ -1,10 +1,10 @@
|
||||
import Link from "next/link";
|
||||
import { supabase } from "@/lib/supabase";
|
||||
import { getAdminUser } from "@/lib/admin-permissions";
|
||||
import { getActiveBrandId } from "@/lib/brand-scope";
|
||||
import { isFeatureEnabled } from "@/lib/feature-flags";
|
||||
import { getBillingOverview } from "@/actions/billing/billing-overview";
|
||||
import DashboardClient from "@/components/admin/DashboardClient";
|
||||
import { pool } from "@/lib/db";
|
||||
|
||||
const TUXEDO_BRAND_ID = "64294306-5f42-463d-a5e8-2ad6c81a96de";
|
||||
|
||||
@@ -31,17 +31,18 @@ export default async function AdminPage() {
|
||||
// so a transient DB/network failure can't crash the whole admin page.
|
||||
let dashboardBrandId: string | null = adminUser ? await getActiveBrandId(adminUser) : null;
|
||||
if (!dashboardBrandId && adminUser?.role === "platform_admin") {
|
||||
// Direct pg query (the supabase shim returns empty results).
|
||||
// This ensures a platform_admin sees real dashboard stats even on first login
|
||||
// before they have chosen an active brand.
|
||||
try {
|
||||
const { data: firstBrand } = await supabase
|
||||
.from("brands")
|
||||
.select("id")
|
||||
.limit(1)
|
||||
.single();
|
||||
if (firstBrand?.id) {
|
||||
dashboardBrandId = String(firstBrand.id);
|
||||
const { rows } = await pool.query<{ id: string }>(
|
||||
`SELECT id FROM brands ORDER BY created_at ASC LIMIT 1`
|
||||
);
|
||||
if (rows[0]?.id) {
|
||||
dashboardBrandId = rows[0].id;
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("[admin/page] supabase brands lookup failed:", err);
|
||||
console.error("[admin/page] brands lookup failed:", err);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user