diff --git a/src/app/(frontend)/layout.tsx b/src/app/(frontend)/layout.tsx index ef40b39..fde806f 100644 --- a/src/app/(frontend)/layout.tsx +++ b/src/app/(frontend)/layout.tsx @@ -20,10 +20,11 @@ import { CommandPalette } from "@/components/command-palette/CommandPalette"; import { CommandPaletteProvider } from "@/components/command-palette/CommandPaletteContext"; import { KeyboardShortcutsProvider } from "@/components/command-palette/KeyboardShortcutsProvider"; import { isSuperuser } from "@/utils/access-control/hasPermission"; -import { Rank } from "@/payload-types"; +import { Rank, Shim } from "@/payload-types"; import { cookies } from "next/headers"; import { IMPERSONATION_ACTIVE_COOKIE } from "@/lib/impersonation"; import { ImpersonationBanner } from "@/components/frontend/impersonation/ImpersonationBanner"; +import { ShimLoader } from "@/components/frontend/shims/ShimLoader"; // Skip static prerendering of this layout (and all pages under (frontend)/). // The layout calls `getPayload()` at render time to resolve the current user, @@ -67,6 +68,9 @@ export default async function RootLayout(props: { children: React.ReactNode }) { let isLogistics = false; let currentRank = ""; + const shimsGlobal = await payload.findGlobal({ slug: "shims" }); + const shimEntries = (shimsGlobal.shims ?? []) as NonNullable; + if (user) { [isIntelligence, isLogistics] = await Promise.all([ hasIntelligenceQualification(payload, user).catch(() => false), @@ -152,6 +156,11 @@ export default async function RootLayout(props: { children: React.ReactNode }) { {user && } {isLogistics && } + ); diff --git a/src/app/(payload)/admin/importMap.js b/src/app/(payload)/admin/importMap.js index 05043c4..704732c 100644 --- a/src/app/(payload)/admin/importMap.js +++ b/src/app/(payload)/admin/importMap.js @@ -11,6 +11,7 @@ import { default as default_2d468cc5cf0b647ffca3201592c20825 } from '@/component import { default as default_7fa52484aec46e988e9cfb5e86c0c759 } from '@/components/admin/narrative-flow/NarrativeFlowEditor' import { SelectField as SelectField_4490b89d4413c1ffaecdacfe72efaf73 } from '@ai-stack/payloadcms/client' import { PromptEditorField as PromptEditorField_4490b89d4413c1ffaecdacfe72efaf73 } from '@ai-stack/payloadcms/client' +import { default as default_b16af798585cda7cfed92f7b1dd0c707 } from '@/components/admin/shims/ShimContentField' import { default as default_2f6c866ab4d4d51b3789f090b84b85c7 } from '@/components/static/VersionOverlay' import { default as default_508743824696caae2894ebf34c2e68bb } from '@/components/graphics/AppIcon' import { default as default_e752fee78378294ff03f91dba0a75a04 } from '@/components/graphics/AppLogo' @@ -34,6 +35,7 @@ export const importMap = { "@/components/admin/narrative-flow/NarrativeFlowEditor#default": default_7fa52484aec46e988e9cfb5e86c0c759, "@ai-stack/payloadcms/client#SelectField": SelectField_4490b89d4413c1ffaecdacfe72efaf73, "@ai-stack/payloadcms/client#PromptEditorField": PromptEditorField_4490b89d4413c1ffaecdacfe72efaf73, + "@/components/admin/shims/ShimContentField#default": default_b16af798585cda7cfed92f7b1dd0c707, "@/components/static/VersionOverlay#default": default_2f6c866ab4d4d51b3789f090b84b85c7, "@/components/graphics/AppIcon#default": default_508743824696caae2894ebf34c2e68bb, "@/components/graphics/AppLogo#default": default_e752fee78378294ff03f91dba0a75a04, diff --git a/src/collections/Shims.ts b/src/collections/Shims.ts index 002f5b7..42ca4be 100644 --- a/src/collections/Shims.ts +++ b/src/collections/Shims.ts @@ -4,8 +4,8 @@ import { requirePermission } from "@/utils/access-control/hasPermission"; export const Shims: GlobalConfig = { slug: "shims", access: { - update: requirePermission("shims:update"), read: requirePermission("shims:read"), + update: requirePermission("shims:update"), }, fields: [ { @@ -17,22 +17,137 @@ export const Shims: GlobalConfig = { type: "text", required: true, unique: true, + admin: { + description: "Unique identifier for this shim. Used internally for stable hashing.", + }, }, { name: "summary", type: "text", + admin: { + description: "Brief description of what this shim does (for admin reference).", + }, + }, + { + name: "enabled", + type: "checkbox", + defaultValue: false, + admin: { + description: "Disabled shims are never injected, regardless of targeting rules.", + }, + }, + { + name: "type", + type: "select", + options: [ + { label: "CSS", value: "css" }, + { label: "JavaScript", value: "js" }, + ], + required: true, + defaultValue: "css", }, { name: "content", type: "code", required: true, + admin: { + components: { + Field: "@/components/admin/shims/ShimContentField", + }, + description: + "CSS or JS code to inject. CSS is added as a