Skip to content

Commit 2d47232

Browse files
committed
fix: combined delete into a single flow
1 parent d25f0a9 commit 2d47232

6 files changed

Lines changed: 49 additions & 53 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.11",
3+
"version": "2.2.0-stage-30-04-2026.12",
44
"description": "AI Image Editor powered by ImageKit",
55
"scripts": {
66
"prepack": "yarn build",

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

Lines changed: 25 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { act, fireEvent, render, screen, waitFor } from "@testing-library/react"
33
import { beforeEach, describe, expect, it, vi } from "vitest"
44
import { TemplateStorageContextProvider } from "../../context/TemplateStorageContext"
55
import type { TemplateRecord } from "../../storage"
6+
import type { TemplateStorageProvider } from "../../storage/types"
67
import { useEditorStore } from "../../store"
78
import { SettingsModal } from "./SettingsModal"
89

@@ -31,36 +32,30 @@ function renderModal(opts: {
3132
data?: TemplateRecord
3233
onClose?: () => void
3334
onSaved?: (r: TemplateRecord) => void
34-
onDeleted?: () => void
35-
deleteTemplate?: ((id: string) => Promise<void>) | undefined
36-
// biome-ignore lint/suspicious/noExplicitAny: test stub
37-
saveTemplate?: (r: any) => Promise<TemplateRecord>
35+
onDeleteRequested?: ((id: string) => Promise<void>) | undefined
36+
saveTemplate?: (r: unknown) => Promise<TemplateRecord>
3837
}) {
3938
const data = opts.data ?? makeTemplate()
4039

41-
const provider = {
40+
const provider: TemplateStorageProvider = {
4241
getProviderName: () => "test",
4342
getCurrentUserSession: () => ({ id: "u1" }),
4443
listTemplates: async () => [],
4544
getTemplate: async () => data,
46-
// biome-ignore lint/suspicious/noExplicitAny: test stub
4745
saveTemplate:
48-
opts.saveTemplate ?? (async (_r: any) => makeTemplate({ id: "saved" })),
46+
opts.saveTemplate ??
47+
(async (_r: unknown) => makeTemplate({ id: "saved" })),
4948
setTemplatePinned: vi.fn(),
50-
...(opts.deleteTemplate !== undefined
51-
? { deleteTemplate: opts.deleteTemplate }
52-
: {}),
5349
}
5450

5551
return render(
5652
<ChakraProvider>
57-
{/* biome-ignore lint/suspicious/noExplicitAny: test stub */}
58-
<TemplateStorageContextProvider provider={provider as any}>
53+
<TemplateStorageContextProvider provider={provider}>
5954
<SettingsModal
6055
data={data}
6156
onClose={opts.onClose ?? vi.fn()}
6257
onSaved={opts.onSaved}
63-
onDeleted={opts.onDeleted}
58+
onDeleteRequested={opts.onDeleteRequested}
6459
/>
6560
</TemplateStorageContextProvider>
6661
</ChakraProvider>,
@@ -77,22 +72,22 @@ describe("SettingsModal", () => {
7772
})
7873

7974
describe("delete confirmation flow", () => {
80-
it("does NOT call deleteTemplate immediately when Delete is clicked", async () => {
81-
const deleteTemplate = vi.fn(async () => {})
82-
renderModal({ deleteTemplate })
75+
it("does NOT call onDeleteRequested immediately when Delete is clicked", async () => {
76+
const onDeleteRequested = vi.fn(async () => {})
77+
renderModal({ onDeleteRequested })
8378

8479
expect(await screen.findByText("Template Settings")).toBeTruthy()
8580

8681
act(() => {
8782
fireEvent.click(screen.getByText("Delete"))
8883
})
8984

90-
// deleteTemplate must NOT have been called yet
91-
expect(deleteTemplate).not.toHaveBeenCalled()
85+
// onDeleteRequested must NOT have been called yet
86+
expect(onDeleteRequested).not.toHaveBeenCalled()
9287
})
9388

9489
it("shows the confirmation panel after clicking Delete", async () => {
95-
renderModal({ deleteTemplate: vi.fn(async () => {}) })
90+
renderModal({ onDeleteRequested: vi.fn(async () => {}) })
9691

9792
expect(await screen.findByText("Template Settings")).toBeTruthy()
9893

@@ -106,9 +101,9 @@ describe("SettingsModal", () => {
106101
expect(screen.getByText("Yes, delete")).toBeTruthy()
107102
})
108103

109-
it("does NOT call deleteTemplate when confirmation is dismissed with Cancel", async () => {
110-
const deleteTemplate = vi.fn(async () => {})
111-
renderModal({ deleteTemplate })
104+
it("does NOT call onDeleteRequested when confirmation is dismissed with Cancel", async () => {
105+
const onDeleteRequested = vi.fn(async () => {})
106+
renderModal({ onDeleteRequested })
112107

113108
expect(await screen.findByText("Template Settings")).toBeTruthy()
114109

@@ -126,16 +121,15 @@ describe("SettingsModal", () => {
126121

127122
// Panel should be gone
128123
expect(screen.queryByText("Delete template?")).toBeNull()
129-
// deleteTemplate still must not have been called
130-
expect(deleteTemplate).not.toHaveBeenCalled()
124+
// onDeleteRequested still must not have been called
125+
expect(onDeleteRequested).not.toHaveBeenCalled()
131126
})
132127

133-
it("calls deleteTemplate only after clicking Yes, delete", async () => {
134-
const deleteTemplate = vi.fn(async () => {})
135-
const onDeleted = vi.fn()
128+
it("calls onDeleteRequested only after clicking Yes, delete", async () => {
129+
const onDeleteRequested = vi.fn(async () => {})
136130
const onClose = vi.fn()
137131

138-
renderModal({ deleteTemplate, onDeleted, onClose })
132+
renderModal({ onDeleteRequested, onClose })
139133

140134
expect(await screen.findByText("Template Settings")).toBeTruthy()
141135

@@ -152,16 +146,15 @@ describe("SettingsModal", () => {
152146
})
153147

154148
await waitFor(() => {
155-
expect(deleteTemplate).toHaveBeenCalledTimes(1)
156-
expect(deleteTemplate).toHaveBeenCalledWith("t-1")
149+
expect(onDeleteRequested).toHaveBeenCalledTimes(1)
150+
expect(onDeleteRequested).toHaveBeenCalledWith("t-1")
157151
})
158152

159-
expect(onDeleted).toHaveBeenCalledTimes(1)
160153
expect(onClose).toHaveBeenCalled()
161154
})
162155

163156
it("Delete button is disabled while confirmation panel is open", async () => {
164-
renderModal({ deleteTemplate: vi.fn(async () => {}) })
157+
renderModal({ onDeleteRequested: vi.fn(async () => {}) })
165158

166159
expect(await screen.findByText("Template Settings")).toBeTruthy()
167160

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

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,14 @@ export interface SettingsModalProps {
3131
onClose(): void
3232
/** Called with the updated record after a successful save. */
3333
onSaved?(updated: TemplateRecord): void
34-
/** Called after the template is successfully deleted. */
35-
onDeleted?(): void
34+
/**
35+
* Called after the user confirms deletion.
36+
*
37+
* `SettingsModal` does not delete templates itself. Callers must provide a
38+
* single delete implementation that can also handle required side-effects
39+
* (list refresh, editor reset, etc.).
40+
*/
41+
onDeleteRequested?(id: string): Promise<void> | void
3642
}
3743

3844
// ---------------------------------------------------------------------------
@@ -53,7 +59,7 @@ export function SettingsModal({
5359
data,
5460
onClose,
5561
onSaved,
56-
onDeleted,
62+
onDeleteRequested,
5763
}: SettingsModalProps) {
5864
const provider = useTemplateStorage()
5965
const permissions = useTemplatePermissions(data)
@@ -124,13 +130,12 @@ export function SettingsModal({
124130
// Delete
125131
// -------------------------------------------------------------------------
126132
const handleDeleteConfirmed = async () => {
127-
if (!provider || !provider.deleteTemplate) return
133+
if (!onDeleteRequested) return
128134

129135
setIsDeleting(true)
130136
setShowDeleteConfirm(false)
131137
try {
132-
await provider.deleteTemplate(data.id)
133-
onDeleted?.()
138+
await onDeleteRequested(data.id)
134139
onClose()
135140
} catch (err) {
136141
if (
@@ -410,8 +415,8 @@ export function SettingsModal({
410415
borderTopWidth="1px"
411416
borderColor="editorGray.300"
412417
>
413-
{/* Delete button — only shown when deleteTemplate is supported */}
414-
{provider?.deleteTemplate ? (
418+
{/* Delete button — only shown when deletion is supported */}
419+
{onDeleteRequested ? (
415420
<FlexAny>
416421
<Box
417422
as="button"
@@ -432,10 +437,8 @@ export function SettingsModal({
432437
fontWeight="medium"
433438
onClick={() => {
434439
if (!permissions.delete) return
435-
436-
return isDeleting || isSaving || showDeleteConfirm
437-
? undefined
438-
: () => setShowDeleteConfirm(true)
440+
if (isDeleting || isSaving || showDeleteConfirm) return
441+
setShowDeleteConfirm(true)
439442
}}
440443
aria-disabled={isDeleting || isSaving || showDeleteConfirm}
441444
disabled={!permissions.delete}

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -280,7 +280,9 @@ export const Header = ({
280280
templateIsPrivate: updated.isPrivate,
281281
})
282282
}}
283-
onDeleted={() => {
283+
onDeleteRequested={async (id) => {
284+
if (!provider?.deleteTemplate) return
285+
await provider.deleteTemplate(id)
284286
useEditorStore.getState().resetToNewTemplate()
285287
}}
286288
/>

packages/imagekit-editor-dev/src/components/templates/TemplatesLibraryView.tsx

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -213,7 +213,7 @@ export function TemplatesLibraryView({ onClose }: Props) {
213213
}
214214
}
215215

216-
const handleDeleteTemplate = useCallback(
216+
const deleteTemplateAndCleanup = useCallback(
217217
async (record: TemplateRecord) => {
218218
if (!provider) return
219219
if (!provider.deleteTemplate) return
@@ -621,7 +621,7 @@ export function TemplatesLibraryView({ onClose }: Props) {
621621
}
622622
onTogglePin={handleTogglePin}
623623
isPinning={pinningId === record.id}
624-
onDelete={handleDeleteTemplate}
624+
onDelete={deleteTemplateAndCleanup}
625625
onSettings={handleOpenSettings}
626626
isCurrent={isCurrent}
627627
isActive={isActive}
@@ -649,10 +649,8 @@ export function TemplatesLibraryView({ onClose }: Props) {
649649
prev.map((t) => (t.id === updated.id ? updated : t)),
650650
)
651651
}}
652-
onDeleted={() => {
653-
setTemplates((prev) =>
654-
prev.filter((t) => t.id !== settingsRecord.id),
655-
)
652+
onDeleteRequested={async () => {
653+
await deleteTemplateAndCleanup(settingsRecord)
656654
}}
657655
/>
658656
)}

packages/imagekit-editor/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",
3-
"version": "2.2.0-stage-30-04-2026.11",
3+
"version": "2.2.0-stage-30-04-2026.12",
44
"description": "Image Editor powered by ImageKit",
55
"main": "dist/index.cjs.js",
66
"module": "dist/index.es.js",

0 commit comments

Comments
 (0)