From c90bf2111101b2b10074d76f7ed10598c6cb1aa3 Mon Sep 17 00:00:00 2001 From: zach Date: Fri, 17 Jul 2026 16:02:54 -0400 Subject: [PATCH] added locals for isAdmin, added dashboard/admin panel to menu, swapped search on dashboard page for inventory search and made catalogs an offcanvas below XL breakpoint --- src/assets/css/main.scss | 10 +++ src/components/NavItems.astro | 17 ++++- src/components/Search.astro | 100 ++++++++++++++++--------- src/middleware.ts | 68 +++++++++-------- src/pages/dashboard.astro | 133 +++++++++++++++------------------- 5 files changed, 185 insertions(+), 143 deletions(-) diff --git a/src/assets/css/main.scss b/src/assets/css/main.scss index bd9e858..d21cb3f 100644 --- a/src/assets/css/main.scss +++ b/src/assets/css/main.scss @@ -886,6 +886,16 @@ footer .logo-svg > svg { width: var(--logo-width, 8rem); } -------------------------------------------------- */ #gridView { row-gap: 1.5rem; } +@media (min-width: 1200px) { + .catalog-sidebar { + position: sticky; + top: var(--navbar-height, 4rem); + height: calc(100vh - var(--navbar-height, 4rem)); + overflow-y: auto; + z-index: 1; + } +} + .inv-grid-card { position: relative; height: 100%; diff --git a/src/components/NavItems.astro b/src/components/NavItems.astro index 8fa0c8a..88d8bd1 100644 --- a/src/components/NavItems.astro +++ b/src/components/NavItems.astro @@ -1,4 +1,8 @@ --- +// auth check for inventory management features +const { canAddInventory } = Astro.locals; +const { isAdmin } = Astro.locals; +// const canAddInventory = false; --- - + ) : ( + + )} \ No newline at end of file diff --git a/src/middleware.ts b/src/middleware.ts index 914744b..8ae9527 100644 --- a/src/middleware.ts +++ b/src/middleware.ts @@ -5,6 +5,7 @@ declare global { namespace App { interface Locals { canAddInventory: boolean; + isAdmin: boolean; } } } @@ -26,53 +27,31 @@ export const onRequest = clerkMiddleware(async (auth, context, next) => { } // ── Inventory visibility check ────────────────────────────────────────────── - // Resolves to true if the user belongs to the target org OR has the feature const canAddInventory = process.env.INVENTORY_ACCESS === 'true' || ( isAuthenticated && userId && ( - !!has({ permission: "org:feature:inventory_add" }) || // Clerk feature flag + !!has({ permission: "org:feature:inventory_add" }) || (await getUserOrgIds(context, userId)).includes(TARGET_ORG_ID) ) ); - - // Expose the flag to your Astro pages via locals context.locals.canAddInventory = Boolean(canAddInventory); + + // ── Admin check ────────────────────────────────────────────────────────── + // Computed on every request (not just /admin routes) so pages can use + // Astro.locals.isAdmin to conditionally show admin-only UI anywhere. + context.locals.isAdmin = isAuthenticated && userId + ? await checkIsAdmin(context, userId) + : false; // ── Admin route guard ─────────────────────────────────────────── if (isAdminRoute(context.request)) { if (!isAuthenticated || !userId) { return redirectToSignIn(); } - - try { - const client = await clerkClient(context); - const userOrgIds = await getUserOrgIds(context, userId); - const matchingOrgIds = userOrgIds.filter((id) => ADMIN_ORG_IDS.has(id)); - - if (matchingOrgIds.length === 0) { - return new Response(null, { status: 404 }); - } - - const membershipLists = await Promise.all( - matchingOrgIds.map((orgId) => - client.organizations.getOrganizationMembershipList({ organizationId: orgId }) - ) - ); - - const isAdmin = membershipLists.some((list) => - list.data.some( - (m) => m.publicUserData?.userId === userId && m.role === "org:admin" - ) - ); - - if (!isAdmin) { - return new Response(null, { status: 404 }); - } - } catch (e) { - console.error("Clerk membership check failed:", e); - return context.redirect("/"); + if (!context.locals.isAdmin) { + return new Response(null, { status: 404 }); } } @@ -89,4 +68,29 @@ async function getUserOrgIds(context: any, userId: string): Promise { console.error("Failed to fetch user org memberships:", e); return []; } +} + +// ── Helper: is this user an org:admin in any of ADMIN_ORG_IDS? ───────────── +async function checkIsAdmin(context: any, userId: string): Promise { + try { + const userOrgIds = await getUserOrgIds(context, userId); + const matchingOrgIds = userOrgIds.filter((id) => ADMIN_ORG_IDS.has(id)); + if (matchingOrgIds.length === 0) return false; + + const client = await clerkClient(context); + const membershipLists = await Promise.all( + matchingOrgIds.map((orgId) => + client.organizations.getOrganizationMembershipList({ organizationId: orgId }) + ) + ); + + return membershipLists.some((list) => + list.data.some( + (m) => m.publicUserData?.userId === userId && m.role === "org:admin" + ) + ); + } catch (e) { + console.error("Clerk membership check failed:", e); + return false; + } } \ No newline at end of file diff --git a/src/pages/dashboard.astro b/src/pages/dashboard.astro index 5174524..a145f96 100644 --- a/src/pages/dashboard.astro +++ b/src/pages/dashboard.astro @@ -1,8 +1,6 @@ --- -import Layout from "../layouts/Main.astro"; -import Footer from "../components/Footer.astro"; -import BackToTop from "../components/BackToTop.astro"; -import FirstEditionIcon from "../components/FirstEditionIcon.astro"; +export const prerender = false; +import Layout from '../layouts/Main.astro'; import { db } from '../db/index'; import { inventory, skus } from '../db/schema'; import { sql, sum, eq } from "drizzle-orm"; @@ -41,99 +39,89 @@ const catalogs = catalogRows --- -
- +
- -
-
- - Add cards +
+
+ + Add cards -
-
- - -
-
-
+
@@ -547,5 +535,4 @@ const catalogs = catalogRows })();
-