|
| 1 | +import { getUserRoleAsync } from './utils/getUserRole'; |
| 2 | + |
| 3 | +const ADMIN_ONLY_COLLECTIONS = ['Settings', 'Branding', 'Internationalization', 'Navbar', 'Pages']; |
| 4 | + |
| 5 | +/** |
| 6 | + * Apply role-based UI restrictions to the TinaCMS admin. |
| 7 | + * Cosmetic only — backend enforcement is the security layer. |
| 8 | + */ |
| 9 | +export const applyRoleRestrictions = async (cms: any) => { |
| 10 | + const { isAdmin, userId } = await getUserRoleAsync(cms); |
| 11 | + |
| 12 | + if (typeof document === 'undefined') return; |
| 13 | + |
| 14 | + document.body.dataset.tinaRole = isAdmin ? 'admin' : 'editor'; |
| 15 | + if (userId) { |
| 16 | + document.body.dataset.tinaUserId = userId; |
| 17 | + } |
| 18 | + |
| 19 | + if (isAdmin) return; |
| 20 | + |
| 21 | + // Form-level read-only behavior is CSS-only to avoid MutationObserver loops. |
| 22 | + // The data-tina-read-only attribute is set by OwnershipNotice.tsx. |
| 23 | + const styleId = 'tina-role-restrictions'; |
| 24 | + if (!document.getElementById(styleId)) { |
| 25 | + const style = document.createElement('style'); |
| 26 | + style.id = styleId; |
| 27 | + style.textContent = ` |
| 28 | + /* Disable all interactive elements inside the form scroll area */ |
| 29 | + body[data-tina-read-only="true"] .relative.w-full.flex-1.overflow-hidden button, |
| 30 | + body[data-tina-read-only="true"] .relative.w-full.flex-1.overflow-hidden input, |
| 31 | + body[data-tina-read-only="true"] .relative.w-full.flex-1.overflow-hidden select, |
| 32 | + body[data-tina-read-only="true"] .relative.w-full.flex-1.overflow-hidden textarea, |
| 33 | + body[data-tina-read-only="true"] .relative.w-full.flex-1.overflow-hidden [contenteditable="true"], |
| 34 | + body[data-tina-read-only="true"] .relative.w-full.flex-1.overflow-hidden [role="textbox"], |
| 35 | + body[data-tina-read-only="true"] .relative.w-full.flex-1.overflow-hidden [role="combobox"], |
| 36 | + body[data-tina-read-only="true"] .relative.w-full.flex-1.overflow-hidden [role="radio"], |
| 37 | + body[data-tina-read-only="true"] .relative.w-full.flex-1.overflow-hidden [role="toolbar"], |
| 38 | + body[data-tina-read-only="true"] .relative.w-full.flex-1.overflow-hidden [class*="cursor-pointer"] { |
| 39 | + pointer-events: none !important; |
| 40 | + opacity: 0.5; |
| 41 | + } |
| 42 | +
|
| 43 | + /* Hide the status dot SVG in the form header bar */ |
| 44 | + body[data-tina-read-only="true"] .border-b.border-gray-100 > svg { |
| 45 | + display: none; |
| 46 | + } |
| 47 | + /* Show lock icon in its place */ |
| 48 | + body[data-tina-read-only="true"] .border-b.border-gray-100:has(nav[aria-label="breadcrumb"])::after { |
| 49 | + content: "\\1F512"; |
| 50 | + font-size: 14px; |
| 51 | + } |
| 52 | + `; |
| 53 | + document.head.appendChild(style); |
| 54 | + } |
| 55 | + |
| 56 | + // MutationObserver only for sidebar link locking — no form manipulation. |
| 57 | + if (typeof MutationObserver === 'undefined') return; |
| 58 | + |
| 59 | + const lockSidebarItems = () => { |
| 60 | + const links = document.querySelectorAll('a[href*="#/collections/"], a[href*="/admin"]'); |
| 61 | + |
| 62 | + links.forEach((link: Element) => { |
| 63 | + const el = link as HTMLAnchorElement; |
| 64 | + const text = el.textContent?.trim(); |
| 65 | + if (!text || !ADMIN_ONLY_COLLECTIONS.includes(text)) return; |
| 66 | + if (el.getAttribute('data-role-locked')) return; |
| 67 | + |
| 68 | + el.setAttribute('data-role-locked', 'true'); |
| 69 | + el.style.color = '#9ca3af'; |
| 70 | + el.style.opacity = '0.6'; |
| 71 | + el.style.cursor = 'default'; |
| 72 | + el.style.pointerEvents = 'none'; |
| 73 | + el.setAttribute('aria-disabled', 'true'); |
| 74 | + el.removeAttribute('href'); |
| 75 | + |
| 76 | + if (!el.textContent?.startsWith('\u{1F512}')) { |
| 77 | + el.textContent = `\u{1F512} ${text}`; |
| 78 | + } |
| 79 | + }); |
| 80 | + }; |
| 81 | + |
| 82 | + lockSidebarItems(); |
| 83 | + const observer = new MutationObserver(lockSidebarItems); |
| 84 | + observer.observe(document.body, { childList: true, subtree: true }); |
| 85 | +}; |
0 commit comments