Skip to content

Commit d25f0a9

Browse files
committed
fix: permission handling working
1 parent 333031c commit d25f0a9

8 files changed

Lines changed: 435 additions & 251 deletions

File tree

packages/imagekit-editor-dev/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "imagekit-editor-dev",
3-
"version": "2.2.0-stage-30-04-2026.6",
3+
"version": "2.2.0-stage-30-04-2026.11",
44
"description": "AI Image Editor powered by ImageKit",
55
"scripts": {
66
"prepack": "yarn build",

packages/imagekit-editor-dev/src/ImageKitEditor.tsx

Lines changed: 28 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ import React, {
99
} from "react"
1010
import { EditorLayout, EditorWrapper } from "./components/editor"
1111
import type { HeaderProps } from "./components/header"
12+
import type { GetTemplatePermissions } from "./context/TemplatePermissionsContext"
13+
import { TemplatePermissionsContextProvider } from "./context/TemplatePermissionsContext"
1214
import { TemplateStorageContextProvider } from "./context/TemplateStorageContext"
1315
import {
1416
applyTemplateStorageAccessFailure,
@@ -89,13 +91,25 @@ interface EditorProps<Metadata extends RequiredMetadata = RequiredMetadata> {
8991
* Omit or pass `null` to disable template sync UI.
9092
*/
9193
templateStorage?: TemplateStorageProvider | null
94+
/**
95+
* Host-controlled, per-template permissions for template management UI.
96+
* If omitted, the editor defaults to allowing all actions.
97+
*/
98+
getTemplatePermissions?: GetTemplatePermissions
9299
}
93100

94101
function ImageKitEditorImpl<M extends RequiredMetadata>(
95102
props: EditorProps<M>,
96103
ref: React.Ref<ImageKitEditorRef>,
97104
) {
98-
const { theme, initialImages, signer, focusObjects, templateStorage } = props
105+
const {
106+
theme,
107+
initialImages,
108+
signer,
109+
focusObjects,
110+
templateStorage,
111+
getTemplatePermissions,
112+
} = props
99113
const {
100114
addImage,
101115
addImages,
@@ -217,15 +231,19 @@ function ImageKitEditorImpl<M extends RequiredMetadata>(
217231
return (
218232
<React.StrictMode>
219233
<ChakraProvider cssVarsRoot="#ik-editor" theme={mergedThemes} resetCSS>
220-
<TemplateStorageContextProvider provider={resolvedProvider}>
221-
<EditorWrapper>
222-
<EditorLayout
223-
onAddImage={props.onAddImage}
224-
onClose={handleOnClose}
225-
exportOptions={props.exportOptions}
226-
/>
227-
</EditorWrapper>
228-
</TemplateStorageContextProvider>
234+
<TemplatePermissionsContextProvider
235+
getTemplatePermissions={getTemplatePermissions}
236+
>
237+
<TemplateStorageContextProvider provider={resolvedProvider}>
238+
<EditorWrapper>
239+
<EditorLayout
240+
onAddImage={props.onAddImage}
241+
onClose={handleOnClose}
242+
exportOptions={props.exportOptions}
243+
/>
244+
</EditorWrapper>
245+
</TemplateStorageContextProvider>
246+
</TemplatePermissionsContextProvider>
229247
</ChakraProvider>
230248
</React.StrictMode>
231249
)

packages/imagekit-editor-dev/src/components/header/SettingsModal.tsx

Lines changed: 25 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { PiX } from "@react-icons/all-files/pi/PiX"
66
import type React from "react"
77
import { useEffect, useMemo, useRef, useState } from "react"
88
import Select, { type StylesConfig } from "react-select"
9+
import { useTemplatePermissions } from "../../context/TemplatePermissionsContext"
910
import { useTemplateStorage } from "../../context/TemplateStorageContext"
1011
import { applyTemplateStorageAccessFailure } from "../../storage/templateAccessError"
1112
import type { TemplateRecord } from "../../storage/types"
@@ -55,6 +56,7 @@ export function SettingsModal({
5556
onDeleted,
5657
}: SettingsModalProps) {
5758
const provider = useTemplateStorage()
59+
const permissions = useTemplatePermissions(data)
5860
const templateStorageWriteBlocked = useEditorStore(
5961
(s) => s.templateStorageWriteBlocked,
6062
)
@@ -73,50 +75,11 @@ export function SettingsModal({
7375
const [localVisibility, setLocalVisibility] = useState<Visibility>(() =>
7476
visibilityFromRecord(data),
7577
)
76-
// Whether the current user is allowed to change visibility (only the creator can).
77-
const [canChangeVisibility, setCanChangeVisibility] = useState(true)
7878

7979
const [isDeleting, setIsDeleting] = useState(false)
8080
const [isSaving, setIsSaving] = useState(false)
8181
const [showDeleteConfirm, setShowDeleteConfirm] = useState(false)
8282

83-
// Fetch authoritative visibility + creator check from the API.
84-
useEffect(() => {
85-
let cancelled = false
86-
87-
if (!provider) {
88-
setCanChangeVisibility(true)
89-
return
90-
}
91-
92-
provider
93-
.getTemplate(data.id)
94-
.then((record) => {
95-
if (cancelled) return
96-
if (!record) {
97-
setCanChangeVisibility(true)
98-
return
99-
}
100-
setLocalVisibility(visibilityFromRecord(record))
101-
const session = provider.getCurrentUserSession() as {
102-
id?: string
103-
} | null
104-
setCanChangeVisibility(record.createdBy.userId === session?.id)
105-
})
106-
.catch((err) => {
107-
if (cancelled) return
108-
if (
109-
applyTemplateStorageAccessFailure(err, { denyTemplateStorageAccess })
110-
) {
111-
onCloseRef.current()
112-
}
113-
})
114-
115-
return () => {
116-
cancelled = true
117-
}
118-
}, [provider, data.id, denyTemplateStorageAccess])
119-
12083
// Close on Escape
12184
useEffect(() => {
12285
const handleKeyDown = (e: KeyboardEvent) => {
@@ -194,13 +157,15 @@ export function SettingsModal({
194157
fontSize: "12px",
195158
minHeight: "32px",
196159
borderColor: "#E2E8F0",
197-
backgroundColor: canChangeVisibility ? base.backgroundColor : "#F7FAFC",
198-
opacity: canChangeVisibility ? 1 : 0.6,
160+
backgroundColor: permissions.changeVisibility
161+
? base.backgroundColor
162+
: "#F7FAFC",
163+
opacity: permissions.changeVisibility ? 1 : 0.6,
199164
}),
200165
menu: (base) => ({ ...base, zIndex: 10 }),
201166
option: (base) => ({ ...base, fontSize: "12px" }),
202167
}),
203-
[canChangeVisibility],
168+
[permissions.changeVisibility],
204169
)
205170

206171
// Get height of the current viewport
@@ -276,6 +241,7 @@ export function SettingsModal({
276241
}
277242
placeholder="Enter template name"
278243
fontSize="sm"
244+
isDisabled={!permissions.rename}
279245
/>
280246
</Box>
281247

@@ -299,7 +265,7 @@ export function SettingsModal({
299265
: "Only to me",
300266
}}
301267
onChange={(option) => {
302-
if (!canChangeVisibility || !option) return
268+
if (!permissions.changeVisibility || !option) return
303269
setLocalVisibility(option.value as Visibility)
304270
}}
305271
options={[
@@ -319,7 +285,7 @@ export function SettingsModal({
319285
)}
320286
styles={selectStyles}
321287
isSearchable={false}
322-
isDisabled={!canChangeVisibility}
288+
isDisabled={!permissions.changeVisibility}
323289
/>
324290
</Box>
325291
</FlexAny>
@@ -464,12 +430,19 @@ export function SettingsModal({
464430
}
465431
fontSize="sm"
466432
fontWeight="medium"
467-
onClick={
468-
isDeleting || isSaving || showDeleteConfirm
433+
onClick={() => {
434+
if (!permissions.delete) return
435+
436+
return isDeleting || isSaving || showDeleteConfirm
469437
? undefined
470438
: () => setShowDeleteConfirm(true)
471-
}
439+
}}
472440
aria-disabled={isDeleting || isSaving || showDeleteConfirm}
441+
disabled={!permissions.delete}
442+
_disabled={{
443+
color: "gray.400",
444+
cursor: "not-allowed",
445+
}}
473446
>
474447
<Icon as={PiTrash} boxSize={5} />
475448
{isDeleting ? "Deleting…" : "Delete"}
@@ -538,6 +511,11 @@ export function SettingsModal({
538511
isSaving ||
539512
templateStorageWriteBlocked
540513
}
514+
disabled={!permissions.save}
515+
_disabled={{
516+
bg: "blue.200",
517+
cursor: "not-allowed",
518+
}}
541519
>
542520
{isSaving ? "Saving…" : "Save"}
543521
</Box>

0 commit comments

Comments
 (0)