Skip to content

Support permissions in CMS Starter #211

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: fix/cms-starter-clean-up
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 8 additions & 8 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions starters/cms/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
"typecheck": "tsc"
},
"dependencies": {
"framer-plugin": "^3.0.0",
"framer-plugin": "^3.1.0-beta.3",
"react": "^18.3.1",
"react-dom": "^18.3.1"
},
Expand All @@ -34,7 +34,7 @@
"typescript": "^5.7.3",
"typescript-eslint": "^8.25.0",
"vite": "^6.2.4",
"vite-plugin-framer": "^1.0.6",
"vite-plugin-framer": "^1.0.7",
"vite-plugin-mkcert": "^1.17.7"
}
}
23 changes: 18 additions & 5 deletions starters/cms/src/FieldMapping.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import { type ManagedCollectionFieldInput, framer, type ManagedCollection } from "framer-plugin"
import { type ManagedCollectionFieldInput, framer, type ManagedCollection, useIsAllowedTo } from "framer-plugin"
import { useEffect, useState } from "react"
import { type DataSource, dataSourceOptions, mergeFieldsWithExistingFields, syncCollection } from "./data"
import { type DataSource, dataSourceOptions, mergeFieldsWithExistingFields, syncCollection, syncMethods } from "./data"

interface FieldMappingRowProps {
field: ManagedCollectionFieldInput
originalFieldName: string | undefined
isIgnored: boolean
onToggleDisabled: (fieldId: string) => void
onNameChange: (fieldId: string, name: string) => void
disabled: boolean
}

function FieldMappingRow({
Expand All @@ -16,13 +17,15 @@ function FieldMappingRow({
isIgnored,
onToggleDisabled,
onNameChange,
disabled,
}: FieldMappingRowProps) {
return (
<>
<button
type="button"
className={`source-field ${isIgnored ? "ignored" : ""}`}
onClick={() => onToggleDisabled(field.id)}
disabled={disabled}
>
<input type="checkbox" checked={!isIgnored} tabIndex={-1} readOnly />
<span>{originalFieldName ?? field.id}</span>
Expand All @@ -39,7 +42,7 @@ function FieldMappingRow({
</svg>
<input
type="text"
disabled={isIgnored}
disabled={isIgnored || disabled}
placeholder={field.id}
value={field.name}
onChange={event => onNameChange(field.id, event.target.value)}
Expand Down Expand Up @@ -135,6 +138,8 @@ export function FieldMapping({ collection, dataSource, initialSlugFieldId }: Fie
})
}

const isAllowedToManage = useIsAllowedTo("ManagedCollection.setFields", ...syncMethods)

const handleSubmit = async (event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault()

Expand All @@ -149,8 +154,11 @@ export function FieldMapping({ collection, dataSource, initialSlugFieldId }: Fie
try {
setStatus("syncing-collection")

const fieldsToSync = fields.filter(field => !ignoredFieldIds.has(field.id))
const fieldsToSync = fields
.filter(field => !ignoredFieldIds.has(field.id))
.map(field => ({ ...field, name: field.name.trim() || field.id }))

await collection.setFields(fieldsToSync)
await syncCollection(collection, dataSource, fieldsToSync, selectedSlugField)
await framer.closePlugin("Synchronization successful", { variant: "success" })
} catch (error) {
Expand Down Expand Up @@ -188,6 +196,7 @@ export function FieldMapping({ collection, dataSource, initialSlugFieldId }: Fie
if (!selectedField) return
setSelectedSlugField(selectedField)
}}
disabled={!isAllowedToManage}
>
{possibleSlugFields.map(possibleSlugField => {
return (
Expand All @@ -210,13 +219,17 @@ export function FieldMapping({ collection, dataSource, initialSlugFieldId }: Fie
isIgnored={ignoredFieldIds.has(field.id)}
onToggleDisabled={toggleFieldDisabledState}
onNameChange={changeFieldName}
disabled={!isAllowedToManage}
/>
))}
</div>

<footer>
<hr />
<button disabled={isSyncing}>
<button
disabled={isSyncing || !isAllowedToManage}
title={isAllowedToManage ? undefined : "Insufficient permissions"}
>
{isSyncing ? <div className="framer-spinner" /> : `Import ${dataSourceName}`}
</button>
</footer>
Expand Down
19 changes: 12 additions & 7 deletions starters/cms/src/data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
framer,
type ManagedCollection,
type ManagedCollectionItemInput,
type ProtectedMethod,
} from "framer-plugin"

export const PLUGIN_KEYS = {
Expand Down Expand Up @@ -101,11 +102,6 @@ export async function syncCollection(
fields: readonly ManagedCollectionFieldInput[],
slugField: ManagedCollectionFieldInput
) {
const sanitizedFields = fields.map(field => ({
...field,
name: field.name.trim() || field.id,
}))

const items: ManagedCollectionItemInput[] = []
const unsyncedItems = new Set(await collection.getItemIds())

Expand All @@ -123,7 +119,7 @@ export async function syncCollection(

const fieldData: FieldDataInput = {}
for (const [fieldName, value] of Object.entries(item)) {
const field = sanitizedFields.find(field => field.id === fieldName)
const field = fields.find(field => field.id === fieldName)

// Field is in the data but skipped based on selected fields.
if (!field) continue
Expand All @@ -141,14 +137,19 @@ export async function syncCollection(
})
}

await collection.setFields(sanitizedFields)
await collection.removeItems(Array.from(unsyncedItems))
await collection.addItems(items)

await collection.setPluginData(PLUGIN_KEYS.DATA_SOURCE_ID, dataSource.id)
await collection.setPluginData(PLUGIN_KEYS.SLUG_FIELD_ID, slugField.id)
}

export const syncMethods = [
"ManagedCollection.removeItems",
"ManagedCollection.addItems",
"ManagedCollection.setPluginData",
] as const satisfies ProtectedMethod[]

export async function syncExistingCollection(
collection: ManagedCollection,
previousDataSourceId: string | null,
Expand All @@ -162,6 +163,10 @@ export async function syncExistingCollection(
return { didSync: false }
}

if (!framer.isAllowedTo(...syncMethods)) {
return { didSync: false }
}

try {
const dataSource = await getDataSource(previousDataSourceId)
const existingFields = await collection.getFields()
Expand Down