@orion-studios/payload-studio 0.5.0-beta.97 → 0.5.0-beta.99

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -69,7 +69,7 @@ export const globals = [
69
69
 
70
70
  - Major platforms (Facebook, Instagram, X, LinkedIn, YouTube, TikTok, Pinterest, Snapchat)
71
71
  - URL field for each platform
72
- - Two icon choices per platform (`Simple Icons` and `Font Awesome Brands`)
72
+ - Four icon choices per platform (`Simple Icons`, `Font Awesome Brands`, `Tabler Brands`, `Remix Icons`)
73
73
 
74
74
  You can fetch and normalize these links in Next.js:
75
75
 
@@ -2280,6 +2280,81 @@ function WelcomeHeader({
2280
2280
  // src/admin/components/studio/AdminStudioDashboard.tsx
2281
2281
  var import_ui3 = require("@payloadcms/ui");
2282
2282
 
2283
+ // src/shared/studioSections.ts
2284
+ var studioRoles = /* @__PURE__ */ new Set(["admin", "editor", "client"]);
2285
+ var isRecord = (value) => Boolean(value) && typeof value === "object" && !Array.isArray(value);
2286
+ var isAbsoluteExternalURL = (value) => /^[a-zA-Z][a-zA-Z\d+\-.]*:/.test(value) || value.startsWith("//");
2287
+ var normalizePathLikeValue = (value) => {
2288
+ const trimmed = value.trim();
2289
+ if (!trimmed) {
2290
+ return "";
2291
+ }
2292
+ if (isAbsoluteExternalURL(trimmed)) {
2293
+ return trimmed;
2294
+ }
2295
+ const withLeadingSlash = trimmed.startsWith("/") ? trimmed : `/${trimmed}`;
2296
+ const normalized = withLeadingSlash.replace(/\/+$/, "");
2297
+ return normalized || "/";
2298
+ };
2299
+ var normalizeStringArray = (value) => {
2300
+ if (!Array.isArray(value)) {
2301
+ return [];
2302
+ }
2303
+ return value.filter((entry) => typeof entry === "string").map((entry) => normalizePathLikeValue(entry)).filter((entry) => entry.length > 0);
2304
+ };
2305
+ var normalizeRoles = (value) => {
2306
+ if (!Array.isArray(value)) {
2307
+ return void 0;
2308
+ }
2309
+ const roles = value.filter((entry) => typeof entry === "string" && studioRoles.has(entry));
2310
+ return roles.length > 0 ? roles : void 0;
2311
+ };
2312
+ var normalizeCard = (value) => {
2313
+ if (!isRecord(value) || typeof value.title !== "string") {
2314
+ return void 0;
2315
+ }
2316
+ const title = value.title.trim();
2317
+ if (!title) {
2318
+ return void 0;
2319
+ }
2320
+ return {
2321
+ title,
2322
+ ...typeof value.description === "string" && value.description.trim().length > 0 ? { description: value.description.trim() } : {}
2323
+ };
2324
+ };
2325
+ var resolveStudioSections = (value) => {
2326
+ if (!Array.isArray(value)) {
2327
+ return [];
2328
+ }
2329
+ const sections = [];
2330
+ const seen = /* @__PURE__ */ new Set();
2331
+ for (const entry of value) {
2332
+ if (!isRecord(entry) || typeof entry.id !== "string" || typeof entry.label !== "string") {
2333
+ continue;
2334
+ }
2335
+ const id = entry.id.trim();
2336
+ const label = entry.label.trim();
2337
+ if (!id || !label || seen.has(id)) {
2338
+ continue;
2339
+ }
2340
+ const href = typeof entry.href === "string" && entry.href.trim().length > 0 ? normalizePathLikeValue(entry.href) : isRecord(entry.view) && typeof entry.view.path === "string" ? normalizePathLikeValue(entry.view.path) : "";
2341
+ if (!href) {
2342
+ continue;
2343
+ }
2344
+ const matchPrefixes = Array.from(/* @__PURE__ */ new Set([href, ...normalizeStringArray(entry.matchPrefixes)]));
2345
+ sections.push({
2346
+ id,
2347
+ label,
2348
+ href,
2349
+ matchPrefixes,
2350
+ ...normalizeRoles(entry.roles) ? { roles: normalizeRoles(entry.roles) } : {},
2351
+ ...normalizeCard(entry.card) ? { card: normalizeCard(entry.card) } : {}
2352
+ });
2353
+ seen.add(id);
2354
+ }
2355
+ return sections;
2356
+ };
2357
+
2283
2358
  // src/admin/components/studio/adminPathUtils.ts
2284
2359
  var import_react11 = require("react");
2285
2360
  var DEFAULT_ADMIN_BASE_PATH = "/admin";
@@ -2322,10 +2397,10 @@ var detectAdminBasePath = (pathname, fallback = DEFAULT_ADMIN_BASE_PATH) => {
2322
2397
  }
2323
2398
  return normalizedFallback;
2324
2399
  };
2325
- var isAbsoluteExternalURL = (value) => /^[a-zA-Z][a-zA-Z\d+\-.]*:/.test(value) || value.startsWith("//");
2400
+ var isAbsoluteExternalURL2 = (value) => /^[a-zA-Z][a-zA-Z\d+\-.]*:/.test(value) || value.startsWith("//");
2326
2401
  var resolveAdminPath = (adminBasePath, targetPath) => {
2327
2402
  if (!targetPath) return adminBasePath;
2328
- if (isAbsoluteExternalURL(targetPath)) return targetPath;
2403
+ if (isAbsoluteExternalURL2(targetPath)) return targetPath;
2329
2404
  const normalizedBasePath = normalizeAdminBasePath(adminBasePath);
2330
2405
  const normalizedTargetPath = normalizePath(targetPath);
2331
2406
  if (normalizedTargetPath === "/admin") {
@@ -2376,14 +2451,30 @@ var getPropString = (props, key, fallback) => {
2376
2451
  }
2377
2452
  return fallback;
2378
2453
  };
2454
+ var getPropSections = (props, key) => {
2455
+ if (!props || typeof props !== "object") return [];
2456
+ const direct = resolveStudioSections(props[key]);
2457
+ if (direct.length > 0) return direct;
2458
+ const clientProps = props.clientProps;
2459
+ if (clientProps && typeof clientProps === "object") {
2460
+ return resolveStudioSections(clientProps[key]);
2461
+ }
2462
+ return [];
2463
+ };
2379
2464
  function AdminStudioDashboard(props) {
2380
2465
  const pagesCollectionSlug = getPropString(props, "pagesCollectionSlug", "pages");
2381
2466
  const mediaCollectionSlug = getPropString(props, "mediaCollectionSlug", "media");
2382
2467
  const globalsBasePath = getPropString(props, "globalsBasePath", "/studio-globals");
2468
+ const sections = getPropSections(props, "sections");
2383
2469
  const adminBasePath = useAdminBasePath();
2384
2470
  const resolvedGlobalsBasePath = resolveAdminPath(adminBasePath, globalsBasePath);
2385
2471
  const pagesPath = resolveAdminPath(adminBasePath, `/collections/${pagesCollectionSlug}`);
2386
2472
  const mediaPath = resolveAdminPath(adminBasePath, `/collections/${mediaCollectionSlug}`);
2473
+ const extensionCards = sections.filter((section) => section.card).map((section) => ({
2474
+ href: resolveAdminPath(adminBasePath, section.href),
2475
+ title: section.card?.title || section.label,
2476
+ description: section.card?.description || ""
2477
+ }));
2387
2478
  return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { style: { padding: "1.2rem 1.2rem 2.5rem" }, children: [
2388
2479
  /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_ui3.SetStepNav, { nav: [{ label: "Dashboard", url: adminBasePath }] }),
2389
2480
  /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("h1", { style: { fontSize: "1.6rem", margin: 0 }, children: "Studio" }),
@@ -2409,7 +2500,11 @@ function AdminStudioDashboard(props) {
2409
2500
  /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("a", { href: mediaPath, style: cardStyle, children: [
2410
2501
  /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("div", { style: { fontWeight: 900 }, children: "Media" }),
2411
2502
  /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("div", { style: { color: "var(--theme-elevation-600)", marginTop: "0.25rem" }, children: "Upload and manage images and files." })
2412
- ] })
2503
+ ] }),
2504
+ extensionCards.map((card) => /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("a", { href: card.href, style: cardStyle, children: [
2505
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("div", { style: { fontWeight: 900 }, children: card.title }),
2506
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("div", { style: { color: "var(--theme-elevation-600)", marginTop: "0.25rem" }, children: card.description })
2507
+ ] }, card.href))
2413
2508
  ]
2414
2509
  }
2415
2510
  )
@@ -2420,11 +2515,6 @@ function AdminStudioDashboard(props) {
2420
2515
  var import_react12 = require("react");
2421
2516
  var import_ui4 = require("@payloadcms/ui");
2422
2517
  var import_jsx_runtime15 = require("react/jsx-runtime");
2423
- var isAdmin = (user) => {
2424
- if (!user || typeof user !== "object") return false;
2425
- const role = user.role;
2426
- return typeof role === "string" && role === "admin";
2427
- };
2428
2518
  var getPropString2 = (props, key, fallback) => {
2429
2519
  if (!props || typeof props !== "object") return fallback;
2430
2520
  const direct = props[key];
@@ -2459,6 +2549,32 @@ var getPropStringArray = (props, key, fallback) => {
2459
2549
  }
2460
2550
  return fallback;
2461
2551
  };
2552
+ var getPropSections2 = (props, key) => {
2553
+ if (!props || typeof props !== "object") return [];
2554
+ const direct = resolveStudioSections(props[key]);
2555
+ if (direct.length > 0) return direct;
2556
+ const clientProps = props.clientProps;
2557
+ if (clientProps && typeof clientProps === "object") {
2558
+ return resolveStudioSections(clientProps[key]);
2559
+ }
2560
+ return [];
2561
+ };
2562
+ var roleCanAccessSection = (role, section) => {
2563
+ if (!section.roles || section.roles.length === 0) {
2564
+ return true;
2565
+ }
2566
+ if (!role) {
2567
+ return false;
2568
+ }
2569
+ return section.roles.includes(role);
2570
+ };
2571
+ var readUserRole = (user) => {
2572
+ if (!user || typeof user !== "object") {
2573
+ return void 0;
2574
+ }
2575
+ const role = user.role;
2576
+ return typeof role === "string" ? role : void 0;
2577
+ };
2462
2578
  function AdminStudioNav(props) {
2463
2579
  const { user } = (0, import_ui4.useAuth)();
2464
2580
  const brandName = getPropString2(props, "brandName", "Orion Studio");
@@ -2467,6 +2583,7 @@ function AdminStudioNav(props) {
2467
2583
  const mediaCollectionSlug = getPropString2(props, "mediaCollectionSlug", "media");
2468
2584
  const globalsBasePath = getPropString2(props, "globalsBasePath", "/studio-globals");
2469
2585
  const globalsExtraMatchPrefixes = getPropStringArray(props, "globalsExtraMatchPrefixes", []);
2586
+ const sections = getPropSections2(props, "sections");
2470
2587
  const compact = getPropBoolean(props, "compact", false);
2471
2588
  const adminBasePath = useAdminBasePath();
2472
2589
  const branding = useSiteBranding(brandName, logoUrl || void 0);
@@ -2486,35 +2603,38 @@ function AdminStudioNav(props) {
2486
2603
  (prefix) => resolveAdminPath(adminBasePath, prefix)
2487
2604
  );
2488
2605
  const dashboardPath = adminBasePath;
2606
+ const userRole = readUserRole(user);
2489
2607
  const links = (0, import_react12.useMemo)(
2490
- () => [
2491
- { href: dashboardPath, label: "Dashboard", matchPrefixes: [dashboardPath] },
2492
- {
2493
- href: pagesPath,
2494
- label: "Pages",
2495
- matchPrefixes: [pagesPath]
2496
- },
2497
- {
2498
- href: resolvedGlobalsBasePath,
2499
- label: "Globals",
2500
- matchPrefixes: [
2501
- resolvedGlobalsBasePath,
2502
- resolveAdminPath(adminBasePath, "/globals"),
2503
- ...resolvedGlobalsExtraMatchPrefixes
2504
- ]
2505
- },
2506
- {
2507
- href: mediaPath,
2508
- label: "Media",
2509
- matchPrefixes: [mediaPath]
2510
- },
2511
- {
2512
- href: usersPath,
2513
- label: "Admin Tools",
2514
- matchPrefixes: [usersPath],
2515
- adminOnly: true
2516
- }
2517
- ],
2608
+ () => {
2609
+ const defaultSections = [
2610
+ { id: "dashboard", href: dashboardPath, label: "Dashboard", matchPrefixes: [dashboardPath] },
2611
+ { id: "pages", href: pagesPath, label: "Pages", matchPrefixes: [pagesPath] },
2612
+ {
2613
+ id: "globals",
2614
+ href: resolvedGlobalsBasePath,
2615
+ label: "Globals",
2616
+ matchPrefixes: [
2617
+ resolvedGlobalsBasePath,
2618
+ resolveAdminPath(adminBasePath, "/globals"),
2619
+ ...resolvedGlobalsExtraMatchPrefixes
2620
+ ]
2621
+ },
2622
+ { id: "media", href: mediaPath, label: "Media", matchPrefixes: [mediaPath] },
2623
+ {
2624
+ id: "admin-tools",
2625
+ href: usersPath,
2626
+ label: "Admin Tools",
2627
+ matchPrefixes: [usersPath],
2628
+ roles: ["admin"]
2629
+ }
2630
+ ];
2631
+ const extensionSections = sections.map((section) => ({
2632
+ ...section,
2633
+ href: resolveAdminPath(adminBasePath, section.href),
2634
+ matchPrefixes: section.matchPrefixes.map((prefix) => resolveAdminPath(adminBasePath, prefix))
2635
+ }));
2636
+ return [...defaultSections, ...extensionSections];
2637
+ },
2518
2638
  [
2519
2639
  adminBasePath,
2520
2640
  dashboardPath,
@@ -2522,6 +2642,7 @@ function AdminStudioNav(props) {
2522
2642
  pagesPath,
2523
2643
  resolvedGlobalsBasePath,
2524
2644
  resolvedGlobalsExtraMatchPrefixes,
2645
+ sections,
2525
2646
  usersPath
2526
2647
  ]
2527
2648
  );
@@ -2580,7 +2701,7 @@ function AdminStudioNav(props) {
2580
2701
  ),
2581
2702
  !compact ? /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { style: { color: "var(--theme-elevation-600)", fontSize: "0.85rem" }, children: "Studio" }) : null
2582
2703
  ] }),
2583
- /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("nav", { style: { display: "grid", gap: "0.25rem" }, children: links.filter((link) => !link.adminOnly || isAdmin(user)).map((link) => {
2704
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("nav", { style: { display: "grid", gap: "0.25rem" }, children: links.filter((link) => roleCanAccessSection(userRole, link)).map((link) => {
2584
2705
  const active = link.href === dashboardPath ? pathname === dashboardPath : link.matchPrefixes.some((prefix) => pathname.startsWith(prefix));
2585
2706
  return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("a", { href: link.href, style: linkStyle(active), title: link.label, children: compact ? link.label.slice(0, 1) : link.label }, link.href);
2586
2707
  }) }),
@@ -2611,7 +2732,7 @@ function AdminStudioNav(props) {
2611
2732
  var import_react13 = require("react");
2612
2733
  var import_ui5 = require("@payloadcms/ui");
2613
2734
  var import_jsx_runtime16 = require("react/jsx-runtime");
2614
- var isAdmin2 = (user) => {
2735
+ var isAdmin = (user) => {
2615
2736
  if (!user || typeof user !== "object") return false;
2616
2737
  const role = user.role;
2617
2738
  return typeof role === "string" && role === "admin";
@@ -2677,7 +2798,7 @@ function AdminStudioPagesListView(props) {
2677
2798
  /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("h1", { style: { margin: 0 }, children: "Pages" }),
2678
2799
  /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("p", { style: { color: "var(--theme-elevation-600)", marginTop: "0.35rem" }, children: "Open a page to edit it in the custom editor." })
2679
2800
  ] }),
2680
- isAdmin2(user) ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
2801
+ isAdmin(user) ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
2681
2802
  "a",
2682
2803
  {
2683
2804
  href: `${rawPagesCollectionPath}/create`,
@@ -2787,7 +2908,7 @@ function AdminStudioPagesListView(props) {
2787
2908
  var import_react14 = require("react");
2788
2909
  var import_ui6 = require("@payloadcms/ui");
2789
2910
  var import_jsx_runtime17 = require("react/jsx-runtime");
2790
- var isAdmin3 = (user) => {
2911
+ var isAdmin2 = (user) => {
2791
2912
  if (!user || typeof user !== "object") return false;
2792
2913
  const role = user.role;
2793
2914
  return typeof role === "string" && role === "admin";
@@ -2848,7 +2969,7 @@ function AdminStudioPageEditView(props) {
2848
2969
  }
2849
2970
  setDidResolvePathFallback(true);
2850
2971
  }, [pageIDFromParams]);
2851
- const canPublish = isAdmin3(user) || isEditor(user);
2972
+ const canPublish = isAdmin2(user) || isEditor(user);
2852
2973
  const refreshUnpublishedState = async (id) => {
2853
2974
  try {
2854
2975
  const response = await fetch(
@@ -3570,7 +3691,7 @@ function AdminStudioMediaView(props) {
3570
3691
  // src/admin/components/studio/AdminStudioToolsView.tsx
3571
3692
  var import_ui10 = require("@payloadcms/ui");
3572
3693
  var import_jsx_runtime21 = require("react/jsx-runtime");
3573
- var isAdmin4 = (user) => {
3694
+ var isAdmin3 = (user) => {
3574
3695
  if (!user || typeof user !== "object") return false;
3575
3696
  const role = user.role;
3576
3697
  return typeof role === "string" && role === "admin";
@@ -3590,7 +3711,7 @@ function AdminStudioToolsView(props) {
3590
3711
  const { user } = (0, import_ui10.useAuth)();
3591
3712
  const adminBasePath = useAdminBasePath();
3592
3713
  const toolsPath = resolveAdminPath(adminBasePath, "/tools");
3593
- if (!isAdmin4(user)) {
3714
+ if (!isAdmin3(user)) {
3594
3715
  return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(import_jsx_runtime21.Fragment, { children: [
3595
3716
  /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_ui10.SetStepNav, { nav: [{ label: "Admin Tools", url: toolsPath }] }),
3596
3717
  /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("h1", { style: { margin: 0 }, children: "Admin Tools" }),
@@ -1097,6 +1097,81 @@ function WelcomeHeader({
1097
1097
  // src/admin/components/studio/AdminStudioDashboard.tsx
1098
1098
  import { SetStepNav } from "@payloadcms/ui";
1099
1099
 
1100
+ // src/shared/studioSections.ts
1101
+ var studioRoles = /* @__PURE__ */ new Set(["admin", "editor", "client"]);
1102
+ var isRecord = (value) => Boolean(value) && typeof value === "object" && !Array.isArray(value);
1103
+ var isAbsoluteExternalURL = (value) => /^[a-zA-Z][a-zA-Z\d+\-.]*:/.test(value) || value.startsWith("//");
1104
+ var normalizePathLikeValue = (value) => {
1105
+ const trimmed = value.trim();
1106
+ if (!trimmed) {
1107
+ return "";
1108
+ }
1109
+ if (isAbsoluteExternalURL(trimmed)) {
1110
+ return trimmed;
1111
+ }
1112
+ const withLeadingSlash = trimmed.startsWith("/") ? trimmed : `/${trimmed}`;
1113
+ const normalized = withLeadingSlash.replace(/\/+$/, "");
1114
+ return normalized || "/";
1115
+ };
1116
+ var normalizeStringArray = (value) => {
1117
+ if (!Array.isArray(value)) {
1118
+ return [];
1119
+ }
1120
+ return value.filter((entry) => typeof entry === "string").map((entry) => normalizePathLikeValue(entry)).filter((entry) => entry.length > 0);
1121
+ };
1122
+ var normalizeRoles = (value) => {
1123
+ if (!Array.isArray(value)) {
1124
+ return void 0;
1125
+ }
1126
+ const roles = value.filter((entry) => typeof entry === "string" && studioRoles.has(entry));
1127
+ return roles.length > 0 ? roles : void 0;
1128
+ };
1129
+ var normalizeCard = (value) => {
1130
+ if (!isRecord(value) || typeof value.title !== "string") {
1131
+ return void 0;
1132
+ }
1133
+ const title = value.title.trim();
1134
+ if (!title) {
1135
+ return void 0;
1136
+ }
1137
+ return {
1138
+ title,
1139
+ ...typeof value.description === "string" && value.description.trim().length > 0 ? { description: value.description.trim() } : {}
1140
+ };
1141
+ };
1142
+ var resolveStudioSections = (value) => {
1143
+ if (!Array.isArray(value)) {
1144
+ return [];
1145
+ }
1146
+ const sections = [];
1147
+ const seen = /* @__PURE__ */ new Set();
1148
+ for (const entry of value) {
1149
+ if (!isRecord(entry) || typeof entry.id !== "string" || typeof entry.label !== "string") {
1150
+ continue;
1151
+ }
1152
+ const id = entry.id.trim();
1153
+ const label = entry.label.trim();
1154
+ if (!id || !label || seen.has(id)) {
1155
+ continue;
1156
+ }
1157
+ const href = typeof entry.href === "string" && entry.href.trim().length > 0 ? normalizePathLikeValue(entry.href) : isRecord(entry.view) && typeof entry.view.path === "string" ? normalizePathLikeValue(entry.view.path) : "";
1158
+ if (!href) {
1159
+ continue;
1160
+ }
1161
+ const matchPrefixes = Array.from(/* @__PURE__ */ new Set([href, ...normalizeStringArray(entry.matchPrefixes)]));
1162
+ sections.push({
1163
+ id,
1164
+ label,
1165
+ href,
1166
+ matchPrefixes,
1167
+ ...normalizeRoles(entry.roles) ? { roles: normalizeRoles(entry.roles) } : {},
1168
+ ...normalizeCard(entry.card) ? { card: normalizeCard(entry.card) } : {}
1169
+ });
1170
+ seen.add(id);
1171
+ }
1172
+ return sections;
1173
+ };
1174
+
1100
1175
  // src/admin/components/studio/adminPathUtils.ts
1101
1176
  import { useEffect as useEffect6, useState as useState5 } from "react";
1102
1177
  var DEFAULT_ADMIN_BASE_PATH = "/admin";
@@ -1139,10 +1214,10 @@ var detectAdminBasePath = (pathname, fallback = DEFAULT_ADMIN_BASE_PATH) => {
1139
1214
  }
1140
1215
  return normalizedFallback;
1141
1216
  };
1142
- var isAbsoluteExternalURL = (value) => /^[a-zA-Z][a-zA-Z\d+\-.]*:/.test(value) || value.startsWith("//");
1217
+ var isAbsoluteExternalURL2 = (value) => /^[a-zA-Z][a-zA-Z\d+\-.]*:/.test(value) || value.startsWith("//");
1143
1218
  var resolveAdminPath = (adminBasePath, targetPath) => {
1144
1219
  if (!targetPath) return adminBasePath;
1145
- if (isAbsoluteExternalURL(targetPath)) return targetPath;
1220
+ if (isAbsoluteExternalURL2(targetPath)) return targetPath;
1146
1221
  const normalizedBasePath = normalizeAdminBasePath(adminBasePath);
1147
1222
  const normalizedTargetPath = normalizePath(targetPath);
1148
1223
  if (normalizedTargetPath === "/admin") {
@@ -1193,14 +1268,30 @@ var getPropString = (props, key, fallback) => {
1193
1268
  }
1194
1269
  return fallback;
1195
1270
  };
1271
+ var getPropSections = (props, key) => {
1272
+ if (!props || typeof props !== "object") return [];
1273
+ const direct = resolveStudioSections(props[key]);
1274
+ if (direct.length > 0) return direct;
1275
+ const clientProps = props.clientProps;
1276
+ if (clientProps && typeof clientProps === "object") {
1277
+ return resolveStudioSections(clientProps[key]);
1278
+ }
1279
+ return [];
1280
+ };
1196
1281
  function AdminStudioDashboard(props) {
1197
1282
  const pagesCollectionSlug = getPropString(props, "pagesCollectionSlug", "pages");
1198
1283
  const mediaCollectionSlug = getPropString(props, "mediaCollectionSlug", "media");
1199
1284
  const globalsBasePath = getPropString(props, "globalsBasePath", "/studio-globals");
1285
+ const sections = getPropSections(props, "sections");
1200
1286
  const adminBasePath = useAdminBasePath();
1201
1287
  const resolvedGlobalsBasePath = resolveAdminPath(adminBasePath, globalsBasePath);
1202
1288
  const pagesPath = resolveAdminPath(adminBasePath, `/collections/${pagesCollectionSlug}`);
1203
1289
  const mediaPath = resolveAdminPath(adminBasePath, `/collections/${mediaCollectionSlug}`);
1290
+ const extensionCards = sections.filter((section) => section.card).map((section) => ({
1291
+ href: resolveAdminPath(adminBasePath, section.href),
1292
+ title: section.card?.title || section.label,
1293
+ description: section.card?.description || ""
1294
+ }));
1204
1295
  return /* @__PURE__ */ jsxs9("div", { style: { padding: "1.2rem 1.2rem 2.5rem" }, children: [
1205
1296
  /* @__PURE__ */ jsx10(SetStepNav, { nav: [{ label: "Dashboard", url: adminBasePath }] }),
1206
1297
  /* @__PURE__ */ jsx10("h1", { style: { fontSize: "1.6rem", margin: 0 }, children: "Studio" }),
@@ -1226,7 +1317,11 @@ function AdminStudioDashboard(props) {
1226
1317
  /* @__PURE__ */ jsxs9("a", { href: mediaPath, style: cardStyle, children: [
1227
1318
  /* @__PURE__ */ jsx10("div", { style: { fontWeight: 900 }, children: "Media" }),
1228
1319
  /* @__PURE__ */ jsx10("div", { style: { color: "var(--theme-elevation-600)", marginTop: "0.25rem" }, children: "Upload and manage images and files." })
1229
- ] })
1320
+ ] }),
1321
+ extensionCards.map((card) => /* @__PURE__ */ jsxs9("a", { href: card.href, style: cardStyle, children: [
1322
+ /* @__PURE__ */ jsx10("div", { style: { fontWeight: 900 }, children: card.title }),
1323
+ /* @__PURE__ */ jsx10("div", { style: { color: "var(--theme-elevation-600)", marginTop: "0.25rem" }, children: card.description })
1324
+ ] }, card.href))
1230
1325
  ]
1231
1326
  }
1232
1327
  )
@@ -1237,11 +1332,6 @@ function AdminStudioDashboard(props) {
1237
1332
  import { useEffect as useEffect7, useMemo, useState as useState6 } from "react";
1238
1333
  import { Logout, useAuth } from "@payloadcms/ui";
1239
1334
  import { Fragment as Fragment2, jsx as jsx11, jsxs as jsxs10 } from "react/jsx-runtime";
1240
- var isAdmin = (user) => {
1241
- if (!user || typeof user !== "object") return false;
1242
- const role = user.role;
1243
- return typeof role === "string" && role === "admin";
1244
- };
1245
1335
  var getPropString2 = (props, key, fallback) => {
1246
1336
  if (!props || typeof props !== "object") return fallback;
1247
1337
  const direct = props[key];
@@ -1276,6 +1366,32 @@ var getPropStringArray = (props, key, fallback) => {
1276
1366
  }
1277
1367
  return fallback;
1278
1368
  };
1369
+ var getPropSections2 = (props, key) => {
1370
+ if (!props || typeof props !== "object") return [];
1371
+ const direct = resolveStudioSections(props[key]);
1372
+ if (direct.length > 0) return direct;
1373
+ const clientProps = props.clientProps;
1374
+ if (clientProps && typeof clientProps === "object") {
1375
+ return resolveStudioSections(clientProps[key]);
1376
+ }
1377
+ return [];
1378
+ };
1379
+ var roleCanAccessSection = (role, section) => {
1380
+ if (!section.roles || section.roles.length === 0) {
1381
+ return true;
1382
+ }
1383
+ if (!role) {
1384
+ return false;
1385
+ }
1386
+ return section.roles.includes(role);
1387
+ };
1388
+ var readUserRole = (user) => {
1389
+ if (!user || typeof user !== "object") {
1390
+ return void 0;
1391
+ }
1392
+ const role = user.role;
1393
+ return typeof role === "string" ? role : void 0;
1394
+ };
1279
1395
  function AdminStudioNav(props) {
1280
1396
  const { user } = useAuth();
1281
1397
  const brandName = getPropString2(props, "brandName", "Orion Studio");
@@ -1284,6 +1400,7 @@ function AdminStudioNav(props) {
1284
1400
  const mediaCollectionSlug = getPropString2(props, "mediaCollectionSlug", "media");
1285
1401
  const globalsBasePath = getPropString2(props, "globalsBasePath", "/studio-globals");
1286
1402
  const globalsExtraMatchPrefixes = getPropStringArray(props, "globalsExtraMatchPrefixes", []);
1403
+ const sections = getPropSections2(props, "sections");
1287
1404
  const compact = getPropBoolean(props, "compact", false);
1288
1405
  const adminBasePath = useAdminBasePath();
1289
1406
  const branding = useSiteBranding(brandName, logoUrl || void 0);
@@ -1303,35 +1420,38 @@ function AdminStudioNav(props) {
1303
1420
  (prefix) => resolveAdminPath(adminBasePath, prefix)
1304
1421
  );
1305
1422
  const dashboardPath = adminBasePath;
1423
+ const userRole = readUserRole(user);
1306
1424
  const links = useMemo(
1307
- () => [
1308
- { href: dashboardPath, label: "Dashboard", matchPrefixes: [dashboardPath] },
1309
- {
1310
- href: pagesPath,
1311
- label: "Pages",
1312
- matchPrefixes: [pagesPath]
1313
- },
1314
- {
1315
- href: resolvedGlobalsBasePath,
1316
- label: "Globals",
1317
- matchPrefixes: [
1318
- resolvedGlobalsBasePath,
1319
- resolveAdminPath(adminBasePath, "/globals"),
1320
- ...resolvedGlobalsExtraMatchPrefixes
1321
- ]
1322
- },
1323
- {
1324
- href: mediaPath,
1325
- label: "Media",
1326
- matchPrefixes: [mediaPath]
1327
- },
1328
- {
1329
- href: usersPath,
1330
- label: "Admin Tools",
1331
- matchPrefixes: [usersPath],
1332
- adminOnly: true
1333
- }
1334
- ],
1425
+ () => {
1426
+ const defaultSections = [
1427
+ { id: "dashboard", href: dashboardPath, label: "Dashboard", matchPrefixes: [dashboardPath] },
1428
+ { id: "pages", href: pagesPath, label: "Pages", matchPrefixes: [pagesPath] },
1429
+ {
1430
+ id: "globals",
1431
+ href: resolvedGlobalsBasePath,
1432
+ label: "Globals",
1433
+ matchPrefixes: [
1434
+ resolvedGlobalsBasePath,
1435
+ resolveAdminPath(adminBasePath, "/globals"),
1436
+ ...resolvedGlobalsExtraMatchPrefixes
1437
+ ]
1438
+ },
1439
+ { id: "media", href: mediaPath, label: "Media", matchPrefixes: [mediaPath] },
1440
+ {
1441
+ id: "admin-tools",
1442
+ href: usersPath,
1443
+ label: "Admin Tools",
1444
+ matchPrefixes: [usersPath],
1445
+ roles: ["admin"]
1446
+ }
1447
+ ];
1448
+ const extensionSections = sections.map((section) => ({
1449
+ ...section,
1450
+ href: resolveAdminPath(adminBasePath, section.href),
1451
+ matchPrefixes: section.matchPrefixes.map((prefix) => resolveAdminPath(adminBasePath, prefix))
1452
+ }));
1453
+ return [...defaultSections, ...extensionSections];
1454
+ },
1335
1455
  [
1336
1456
  adminBasePath,
1337
1457
  dashboardPath,
@@ -1339,6 +1459,7 @@ function AdminStudioNav(props) {
1339
1459
  pagesPath,
1340
1460
  resolvedGlobalsBasePath,
1341
1461
  resolvedGlobalsExtraMatchPrefixes,
1462
+ sections,
1342
1463
  usersPath
1343
1464
  ]
1344
1465
  );
@@ -1397,7 +1518,7 @@ function AdminStudioNav(props) {
1397
1518
  ),
1398
1519
  !compact ? /* @__PURE__ */ jsx11("div", { style: { color: "var(--theme-elevation-600)", fontSize: "0.85rem" }, children: "Studio" }) : null
1399
1520
  ] }),
1400
- /* @__PURE__ */ jsx11("nav", { style: { display: "grid", gap: "0.25rem" }, children: links.filter((link) => !link.adminOnly || isAdmin(user)).map((link) => {
1521
+ /* @__PURE__ */ jsx11("nav", { style: { display: "grid", gap: "0.25rem" }, children: links.filter((link) => roleCanAccessSection(userRole, link)).map((link) => {
1401
1522
  const active = link.href === dashboardPath ? pathname === dashboardPath : link.matchPrefixes.some((prefix) => pathname.startsWith(prefix));
1402
1523
  return /* @__PURE__ */ jsx11("a", { href: link.href, style: linkStyle(active), title: link.label, children: compact ? link.label.slice(0, 1) : link.label }, link.href);
1403
1524
  }) }),
@@ -1428,7 +1549,7 @@ function AdminStudioNav(props) {
1428
1549
  import { useEffect as useEffect8, useMemo as useMemo2, useState as useState7 } from "react";
1429
1550
  import { SetStepNav as SetStepNav2, useAuth as useAuth2 } from "@payloadcms/ui";
1430
1551
  import { Fragment as Fragment3, jsx as jsx12, jsxs as jsxs11 } from "react/jsx-runtime";
1431
- var isAdmin2 = (user) => {
1552
+ var isAdmin = (user) => {
1432
1553
  if (!user || typeof user !== "object") return false;
1433
1554
  const role = user.role;
1434
1555
  return typeof role === "string" && role === "admin";
@@ -1494,7 +1615,7 @@ function AdminStudioPagesListView(props) {
1494
1615
  /* @__PURE__ */ jsx12("h1", { style: { margin: 0 }, children: "Pages" }),
1495
1616
  /* @__PURE__ */ jsx12("p", { style: { color: "var(--theme-elevation-600)", marginTop: "0.35rem" }, children: "Open a page to edit it in the custom editor." })
1496
1617
  ] }),
1497
- isAdmin2(user) ? /* @__PURE__ */ jsx12(
1618
+ isAdmin(user) ? /* @__PURE__ */ jsx12(
1498
1619
  "a",
1499
1620
  {
1500
1621
  href: `${rawPagesCollectionPath}/create`,
@@ -1604,7 +1725,7 @@ function AdminStudioPagesListView(props) {
1604
1725
  import { useEffect as useEffect9, useMemo as useMemo3, useRef as useRef3, useState as useState8 } from "react";
1605
1726
  import { SetStepNav as SetStepNav3, toast, useAuth as useAuth3 } from "@payloadcms/ui";
1606
1727
  import { Fragment as Fragment4, jsx as jsx13, jsxs as jsxs12 } from "react/jsx-runtime";
1607
- var isAdmin3 = (user) => {
1728
+ var isAdmin2 = (user) => {
1608
1729
  if (!user || typeof user !== "object") return false;
1609
1730
  const role = user.role;
1610
1731
  return typeof role === "string" && role === "admin";
@@ -1665,7 +1786,7 @@ function AdminStudioPageEditView(props) {
1665
1786
  }
1666
1787
  setDidResolvePathFallback(true);
1667
1788
  }, [pageIDFromParams]);
1668
- const canPublish = isAdmin3(user) || isEditor(user);
1789
+ const canPublish = isAdmin2(user) || isEditor(user);
1669
1790
  const refreshUnpublishedState = async (id) => {
1670
1791
  try {
1671
1792
  const response = await fetch(
@@ -2387,7 +2508,7 @@ function AdminStudioMediaView(props) {
2387
2508
  // src/admin/components/studio/AdminStudioToolsView.tsx
2388
2509
  import { SetStepNav as SetStepNav7, useAuth as useAuth4 } from "@payloadcms/ui";
2389
2510
  import { Fragment as Fragment7, jsx as jsx17, jsxs as jsxs16 } from "react/jsx-runtime";
2390
- var isAdmin4 = (user) => {
2511
+ var isAdmin3 = (user) => {
2391
2512
  if (!user || typeof user !== "object") return false;
2392
2513
  const role = user.role;
2393
2514
  return typeof role === "string" && role === "admin";
@@ -2407,7 +2528,7 @@ function AdminStudioToolsView(props) {
2407
2528
  const { user } = useAuth4();
2408
2529
  const adminBasePath = useAdminBasePath();
2409
2530
  const toolsPath = resolveAdminPath(adminBasePath, "/tools");
2410
- if (!isAdmin4(user)) {
2531
+ if (!isAdmin3(user)) {
2411
2532
  return /* @__PURE__ */ jsxs16(Fragment7, { children: [
2412
2533
  /* @__PURE__ */ jsx17(SetStepNav7, { nav: [{ label: "Admin Tools", url: toolsPath }] }),
2413
2534
  /* @__PURE__ */ jsx17("h1", { style: { margin: 0 }, children: "Admin Tools" }),