Skip to content

Commit 6d1607d

Browse files
committed
Support permissions in CMS Starter
1 parent 9472e58 commit 6d1607d

File tree

4 files changed

+40
-22
lines changed

4 files changed

+40
-22
lines changed

package-lock.json

+8-8
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

starters/cms/package.json

+2-2
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
"typecheck": "tsc"
1515
},
1616
"dependencies": {
17-
"framer-plugin": "^3.0.0",
17+
"framer-plugin": "^3.1.0-beta.3",
1818
"react": "^18.3.1",
1919
"react-dom": "^18.3.1"
2020
},
@@ -34,7 +34,7 @@
3434
"typescript": "^5.7.3",
3535
"typescript-eslint": "^8.25.0",
3636
"vite": "^6.2.4",
37-
"vite-plugin-framer": "^1.0.6",
37+
"vite-plugin-framer": "^1.0.7",
3838
"vite-plugin-mkcert": "^1.17.7"
3939
}
4040
}

starters/cms/src/FieldMapping.tsx

+18-5
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
1-
import { type ManagedCollectionFieldInput, framer, type ManagedCollection } from "framer-plugin"
1+
import { type ManagedCollectionFieldInput, framer, type ManagedCollection, useIsAllowedTo } from "framer-plugin"
22
import { useEffect, useState } from "react"
3-
import { type DataSource, dataSourceOptions, mergeFieldsWithExistingFields, syncCollection } from "./data"
3+
import { type DataSource, dataSourceOptions, mergeFieldsWithExistingFields, syncCollection, syncMethods } from "./data"
44

55
interface FieldMappingRowProps {
66
field: ManagedCollectionFieldInput
77
originalFieldName: string | undefined
88
isIgnored: boolean
99
onToggleDisabled: (fieldId: string) => void
1010
onNameChange: (fieldId: string, name: string) => void
11+
disabled: boolean
1112
}
1213

1314
function FieldMappingRow({
@@ -16,13 +17,15 @@ function FieldMappingRow({
1617
isIgnored,
1718
onToggleDisabled,
1819
onNameChange,
20+
disabled,
1921
}: FieldMappingRowProps) {
2022
return (
2123
<>
2224
<button
2325
type="button"
2426
className={`source-field ${isIgnored ? "ignored" : ""}`}
2527
onClick={() => onToggleDisabled(field.id)}
28+
disabled={disabled}
2629
>
2730
<input type="checkbox" checked={!isIgnored} tabIndex={-1} readOnly />
2831
<span>{originalFieldName ?? field.id}</span>
@@ -39,7 +42,7 @@ function FieldMappingRow({
3942
</svg>
4043
<input
4144
type="text"
42-
disabled={isIgnored}
45+
disabled={isIgnored || disabled}
4346
placeholder={field.id}
4447
value={field.name}
4548
onChange={event => onNameChange(field.id, event.target.value)}
@@ -135,6 +138,8 @@ export function FieldMapping({ collection, dataSource, initialSlugFieldId }: Fie
135138
})
136139
}
137140

141+
const isAllowedToManage = useIsAllowedTo("ManagedCollection.setFields", ...syncMethods)
142+
138143
const handleSubmit = async (event: React.FormEvent<HTMLFormElement>) => {
139144
event.preventDefault()
140145

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

152-
const fieldsToSync = fields.filter(field => !ignoredFieldIds.has(field.id))
157+
const fieldsToSync = fields
158+
.filter(field => !ignoredFieldIds.has(field.id))
159+
.map(field => ({ ...field, name: field.name.trim() || field.id }))
153160

161+
await collection.setFields(fieldsToSync)
154162
await syncCollection(collection, dataSource, fieldsToSync, selectedSlugField)
155163
await framer.closePlugin("Synchronization successful", { variant: "success" })
156164
} catch (error) {
@@ -188,6 +196,7 @@ export function FieldMapping({ collection, dataSource, initialSlugFieldId }: Fie
188196
if (!selectedField) return
189197
setSelectedSlugField(selectedField)
190198
}}
199+
disabled={!isAllowedToManage}
191200
>
192201
{possibleSlugFields.map(possibleSlugField => {
193202
return (
@@ -210,13 +219,17 @@ export function FieldMapping({ collection, dataSource, initialSlugFieldId }: Fie
210219
isIgnored={ignoredFieldIds.has(field.id)}
211220
onToggleDisabled={toggleFieldDisabledState}
212221
onNameChange={changeFieldName}
222+
disabled={!isAllowedToManage}
213223
/>
214224
))}
215225
</div>
216226

217227
<footer>
218228
<hr />
219-
<button disabled={isSyncing}>
229+
<button
230+
disabled={isSyncing || !isAllowedToManage}
231+
title={isAllowedToManage ? undefined : "Insufficient permissions"}
232+
>
220233
{isSyncing ? <div className="framer-spinner" /> : `Import ${dataSourceName}`}
221234
</button>
222235
</footer>

starters/cms/src/data.ts

+12-7
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import {
44
framer,
55
type ManagedCollection,
66
type ManagedCollectionItemInput,
7+
type ProtectedMethod,
78
} from "framer-plugin"
89

910
export const PLUGIN_KEYS = {
@@ -101,11 +102,6 @@ export async function syncCollection(
101102
fields: readonly ManagedCollectionFieldInput[],
102103
slugField: ManagedCollectionFieldInput
103104
) {
104-
const sanitizedFields = fields.map(field => ({
105-
...field,
106-
name: field.name.trim() || field.id,
107-
}))
108-
109105
const items: ManagedCollectionItemInput[] = []
110106
const unsyncedItems = new Set(await collection.getItemIds())
111107

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

124120
const fieldData: FieldDataInput = {}
125121
for (const [fieldName, value] of Object.entries(item)) {
126-
const field = sanitizedFields.find(field => field.id === fieldName)
122+
const field = fields.find(field => field.id === fieldName)
127123

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

144-
await collection.setFields(sanitizedFields)
145140
await collection.removeItems(Array.from(unsyncedItems))
146141
await collection.addItems(items)
147142

148143
await collection.setPluginData(PLUGIN_KEYS.DATA_SOURCE_ID, dataSource.id)
149144
await collection.setPluginData(PLUGIN_KEYS.SLUG_FIELD_ID, slugField.id)
150145
}
151146

147+
export const syncMethods = [
148+
"ManagedCollection.removeItems",
149+
"ManagedCollection.addItems",
150+
"ManagedCollection.setPluginData",
151+
] as const satisfies ProtectedMethod[]
152+
152153
export async function syncExistingCollection(
153154
collection: ManagedCollection,
154155
previousDataSourceId: string | null,
@@ -162,6 +163,10 @@ export async function syncExistingCollection(
162163
return { didSync: false }
163164
}
164165

166+
if (!framer.isAllowedTo(...syncMethods)) {
167+
return { didSync: false }
168+
}
169+
165170
try {
166171
const dataSource = await getDataSource(previousDataSourceId)
167172
const existingFields = await collection.getFields()

0 commit comments

Comments
 (0)