@commercetools-frontend-extensions/operations 0.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (158) hide show
  1. package/CHANGELOG.md +55 -0
  2. package/README.md +169 -0
  3. package/babel.config.js +6 -0
  4. package/dist/commercetools-frontend-extensions-operations.cjs.d.ts +2 -0
  5. package/dist/commercetools-frontend-extensions-operations.cjs.dev.js +2469 -0
  6. package/dist/commercetools-frontend-extensions-operations.cjs.js +7 -0
  7. package/dist/commercetools-frontend-extensions-operations.cjs.prod.js +2461 -0
  8. package/dist/commercetools-frontend-extensions-operations.esm.js +2316 -0
  9. package/dist/declarations/src/@api/export-operations.d.ts +5 -0
  10. package/dist/declarations/src/@api/fetcher.d.ts +17 -0
  11. package/dist/declarations/src/@api/file-upload.d.ts +3 -0
  12. package/dist/declarations/src/@api/import-containers.d.ts +35 -0
  13. package/dist/declarations/src/@api/import-operations.d.ts +6 -0
  14. package/dist/declarations/src/@api/index.d.ts +8 -0
  15. package/dist/declarations/src/@api/process-file.d.ts +3 -0
  16. package/dist/declarations/src/@api/test-fixtures.d.ts +272 -0
  17. package/dist/declarations/src/@api/urls.d.ts +44 -0
  18. package/dist/declarations/src/@components/file-drop-area/active-drag-drop-area.d.ts +10 -0
  19. package/dist/declarations/src/@components/file-drop-area/disabled-drop-area.d.ts +5 -0
  20. package/dist/declarations/src/@components/file-drop-area/drop-area-wrapper.d.ts +11 -0
  21. package/dist/declarations/src/@components/file-drop-area/enabled-drop-area.d.ts +7 -0
  22. package/dist/declarations/src/@components/file-drop-area/file-drop-area.d.ts +14 -0
  23. package/dist/declarations/src/@components/file-drop-area/file-dropped-area.d.ts +6 -0
  24. package/dist/declarations/src/@components/file-drop-area/index.d.ts +7 -0
  25. package/dist/declarations/src/@components/file-drop-area/styles.d.ts +9 -0
  26. package/dist/declarations/src/@components/icons/file-icon.d.ts +2 -0
  27. package/dist/declarations/src/@components/icons/index.d.ts +2 -0
  28. package/dist/declarations/src/@components/icons/lock-icon.d.ts +2 -0
  29. package/dist/declarations/src/@components/index.d.ts +6 -0
  30. package/dist/declarations/src/@components/info-box/index.d.ts +1 -0
  31. package/dist/declarations/src/@components/info-box/info-box.d.ts +7 -0
  32. package/dist/declarations/src/@components/upload-separator/index.d.ts +1 -0
  33. package/dist/declarations/src/@components/upload-separator/upload-separator.d.ts +12 -0
  34. package/dist/declarations/src/@components/upload-settings/index.d.ts +1 -0
  35. package/dist/declarations/src/@components/upload-settings/upload-settings.d.ts +11 -0
  36. package/dist/declarations/src/@components/uploading-modal/index.d.ts +1 -0
  37. package/dist/declarations/src/@components/uploading-modal/uploading-modal.d.ts +12 -0
  38. package/dist/declarations/src/@constants/delimiters.d.ts +8 -0
  39. package/dist/declarations/src/@constants/import-tags.d.ts +7 -0
  40. package/dist/declarations/src/@constants/index.d.ts +4 -0
  41. package/dist/declarations/src/@constants/resource-links.d.ts +10 -0
  42. package/dist/declarations/src/@constants/upload-limits.d.ts +10 -0
  43. package/dist/declarations/src/@errors/http-error.d.ts +6 -0
  44. package/dist/declarations/src/@errors/index.d.ts +8 -0
  45. package/dist/declarations/src/@errors/invalid-response-error.d.ts +3 -0
  46. package/dist/declarations/src/@errors/no-resources-to-export-error.d.ts +3 -0
  47. package/dist/declarations/src/@errors/project-key-not-available-error.d.ts +3 -0
  48. package/dist/declarations/src/@errors/query-predicate-error.d.ts +4 -0
  49. package/dist/declarations/src/@errors/unexpected-column-error.d.ts +3 -0
  50. package/dist/declarations/src/@errors/unexpected-operation-state-error.d.ts +4 -0
  51. package/dist/declarations/src/@errors/unexpected-resource-type-error.d.ts +3 -0
  52. package/dist/declarations/src/@hooks/index.d.ts +5 -0
  53. package/dist/declarations/src/@hooks/use-fetch-export-operations.d.ts +15 -0
  54. package/dist/declarations/src/@hooks/use-fetch-import-container-details.d.ts +15 -0
  55. package/dist/declarations/src/@hooks/use-fetch-import-operations.d.ts +16 -0
  56. package/dist/declarations/src/@hooks/use-fetch-import-summaries.d.ts +20 -0
  57. package/dist/declarations/src/@hooks/use-import-container-upload.d.ts +18 -0
  58. package/dist/declarations/src/@types/api.d.ts +13 -0
  59. package/dist/declarations/src/@types/basic-error-data-type.d.ts +5 -0
  60. package/dist/declarations/src/@types/export-operation.d.ts +95 -0
  61. package/dist/declarations/src/@types/file-upload.d.ts +63 -0
  62. package/dist/declarations/src/@types/import-container.d.ts +53 -0
  63. package/dist/declarations/src/@types/import-operation.d.ts +13 -0
  64. package/dist/declarations/src/@types/import-states.d.ts +9 -0
  65. package/dist/declarations/src/@types/import-summary.d.ts +15 -0
  66. package/dist/declarations/src/@types/index.d.ts +9 -0
  67. package/dist/declarations/src/@types/shared.d.ts +7 -0
  68. package/dist/declarations/src/@utils/error-mapping.d.ts +19 -0
  69. package/dist/declarations/src/@utils/file-upload.d.ts +46 -0
  70. package/dist/declarations/src/@utils/form.d.ts +1 -0
  71. package/dist/declarations/src/@utils/format.d.ts +5 -0
  72. package/dist/declarations/src/@utils/import-container.d.ts +8 -0
  73. package/dist/declarations/src/@utils/index.d.ts +6 -0
  74. package/dist/declarations/src/@utils/url.d.ts +6 -0
  75. package/dist/declarations/src/index.d.ts +26 -0
  76. package/index.js +1 -0
  77. package/jest.test.config.js +11 -0
  78. package/package.json +63 -0
  79. package/src/@api/export-operations.ts +26 -0
  80. package/src/@api/fetcher.spec.ts +51 -0
  81. package/src/@api/fetcher.ts +127 -0
  82. package/src/@api/file-upload.spec.ts +83 -0
  83. package/src/@api/file-upload.ts +46 -0
  84. package/src/@api/import-containers.ts +256 -0
  85. package/src/@api/import-operations.ts +33 -0
  86. package/src/@api/index.ts +8 -0
  87. package/src/@api/process-file.spec.ts +74 -0
  88. package/src/@api/process-file.ts +53 -0
  89. package/src/@api/test-fixtures.ts +772 -0
  90. package/src/@api/urls.ts +118 -0
  91. package/src/@components/file-drop-area/active-drag-drop-area.tsx +33 -0
  92. package/src/@components/file-drop-area/disabled-drop-area.tsx +17 -0
  93. package/src/@components/file-drop-area/drop-area-wrapper.tsx +38 -0
  94. package/src/@components/file-drop-area/enabled-drop-area.tsx +27 -0
  95. package/src/@components/file-drop-area/file-drop-area.tsx +74 -0
  96. package/src/@components/file-drop-area/file-dropped-area.tsx +29 -0
  97. package/src/@components/file-drop-area/index.ts +7 -0
  98. package/src/@components/file-drop-area/styles.ts +67 -0
  99. package/src/@components/icons/file-icon.tsx +30 -0
  100. package/src/@components/icons/index.ts +2 -0
  101. package/src/@components/icons/lock-icon.tsx +34 -0
  102. package/src/@components/index.ts +6 -0
  103. package/src/@components/info-box/index.ts +1 -0
  104. package/src/@components/info-box/info-box.tsx +23 -0
  105. package/src/@components/upload-separator/index.ts +1 -0
  106. package/src/@components/upload-separator/upload-separator.tsx +61 -0
  107. package/src/@components/upload-settings/index.ts +1 -0
  108. package/src/@components/upload-settings/upload-settings.tsx +36 -0
  109. package/src/@components/uploading-modal/index.ts +1 -0
  110. package/src/@components/uploading-modal/uploading-modal.tsx +64 -0
  111. package/src/@constants/delimiters.ts +14 -0
  112. package/src/@constants/import-tags.ts +9 -0
  113. package/src/@constants/index.ts +4 -0
  114. package/src/@constants/resource-links.ts +61 -0
  115. package/src/@constants/upload-limits.ts +11 -0
  116. package/src/@errors/http-error.ts +17 -0
  117. package/src/@errors/index.ts +8 -0
  118. package/src/@errors/invalid-response-error.ts +6 -0
  119. package/src/@errors/no-resources-to-export-error.ts +6 -0
  120. package/src/@errors/project-key-not-available-error.ts +6 -0
  121. package/src/@errors/query-predicate-error.ts +10 -0
  122. package/src/@errors/unexpected-column-error.ts +6 -0
  123. package/src/@errors/unexpected-operation-state-error.ts +8 -0
  124. package/src/@errors/unexpected-resource-type-error.ts +6 -0
  125. package/src/@hooks/index.ts +5 -0
  126. package/src/@hooks/messages.ts +11 -0
  127. package/src/@hooks/use-fetch-export-operations.ts +34 -0
  128. package/src/@hooks/use-fetch-import-container-details.ts +31 -0
  129. package/src/@hooks/use-fetch-import-operations.ts +42 -0
  130. package/src/@hooks/use-fetch-import-summaries.ts +47 -0
  131. package/src/@hooks/use-fetch.spec.ts +76 -0
  132. package/src/@hooks/use-fetch.ts +80 -0
  133. package/src/@hooks/use-import-container-upload.spec.ts +294 -0
  134. package/src/@hooks/use-import-container-upload.ts +126 -0
  135. package/src/@types/api.ts +14 -0
  136. package/src/@types/basic-error-data-type.ts +5 -0
  137. package/src/@types/export-operation.ts +144 -0
  138. package/src/@types/file-upload.ts +81 -0
  139. package/src/@types/import-container.ts +104 -0
  140. package/src/@types/import-operation.ts +31 -0
  141. package/src/@types/import-states.ts +9 -0
  142. package/src/@types/import-summary.ts +22 -0
  143. package/src/@types/index.ts +9 -0
  144. package/src/@types/shared.ts +52 -0
  145. package/src/@utils/error-mapping.spec.ts +126 -0
  146. package/src/@utils/error-mapping.ts +39 -0
  147. package/src/@utils/file-upload.spec.ts +151 -0
  148. package/src/@utils/file-upload.ts +150 -0
  149. package/src/@utils/form.ts +20 -0
  150. package/src/@utils/format.spec.ts +62 -0
  151. package/src/@utils/format.ts +53 -0
  152. package/src/@utils/import-container.spec.ts +26 -0
  153. package/src/@utils/import-container.ts +34 -0
  154. package/src/@utils/index.ts +6 -0
  155. package/src/@utils/url.spec.ts +75 -0
  156. package/src/@utils/url.ts +18 -0
  157. package/src/index.ts +27 -0
  158. package/tsconfig.json +9 -0
@@ -0,0 +1,294 @@
1
+ import { renderHook, waitFor, act } from '@testing-library/react'
2
+ import {
3
+ createImportContainerForFileUpload,
4
+ deleteImportContainer,
5
+ uploadFileForImport,
6
+ } from '../@api'
7
+ import { ProjectKeyNotAvailableError } from '../@errors'
8
+ import { encodeFileNameWithTimestampToContainerKey } from '../@utils'
9
+ import {
10
+ useImportContainerUpload,
11
+ type UseImportContainerUploadConfig,
12
+ } from './use-import-container-upload'
13
+
14
+ jest.mock('../@api')
15
+ jest.mock('../@utils')
16
+
17
+ const mockCreateImportContainerForFileUpload =
18
+ createImportContainerForFileUpload as jest.MockedFunction<
19
+ typeof createImportContainerForFileUpload
20
+ >
21
+ const mockUploadFileForImport = uploadFileForImport as jest.MockedFunction<
22
+ typeof uploadFileForImport
23
+ >
24
+ const mockDeleteImportContainer = deleteImportContainer as jest.MockedFunction<
25
+ typeof deleteImportContainer
26
+ >
27
+ const mockEncodeFileNameWithTimestampToContainerKey =
28
+ encodeFileNameWithTimestampToContainerKey as jest.MockedFunction<
29
+ typeof encodeFileNameWithTimestampToContainerKey
30
+ >
31
+
32
+ describe('useImportContainerUpload', () => {
33
+ const projectKey = 'test-with-big-data'
34
+ const importContainerKey = 'test-container-key'
35
+ const mockFile = new File(['test content'], 'test.csv', { type: 'text/csv' })
36
+ const mockXhr = { abort: jest.fn() } as unknown as XMLHttpRequest
37
+ const mockFileUploadResponse = {
38
+ results: [],
39
+ invalid: 0,
40
+ valid: 10,
41
+ fileName: 'test.csv',
42
+ itemsCount: 10,
43
+ rowsCount: 10,
44
+ columnsCount: 2,
45
+ columns: ['key', 'value'],
46
+ ignoredColumns: [],
47
+ }
48
+
49
+ beforeEach(() => {
50
+ jest.clearAllMocks()
51
+ mockEncodeFileNameWithTimestampToContainerKey.mockReturnValue(
52
+ importContainerKey
53
+ )
54
+ mockCreateImportContainerForFileUpload.mockResolvedValue(undefined)
55
+ mockDeleteImportContainer.mockResolvedValue(undefined)
56
+ })
57
+
58
+ it('should throw "ProjectKeyNotAvailableError" if "projectKey" is not available', async () => {
59
+ const { result } = renderHook(() =>
60
+ // @ts-ignore
61
+ useImportContainerUpload({ projectKey: undefined })
62
+ )
63
+
64
+ await expect(
65
+ result.current.upload({
66
+ file: mockFile,
67
+ resourceType: 'product',
68
+ onSuccess: jest.fn(),
69
+ })
70
+ ).rejects.toThrow(ProjectKeyNotAvailableError)
71
+ })
72
+
73
+ it('should create container and upload file successfully', async () => {
74
+ const onSuccess = jest.fn()
75
+ const onProgress = jest.fn()
76
+
77
+ mockUploadFileForImport.mockImplementation(({ onSuccess: _onSuccess }) => {
78
+ _onSuccess(mockFileUploadResponse)
79
+ return mockXhr
80
+ })
81
+
82
+ const { result } = renderHook(() =>
83
+ useImportContainerUpload({ projectKey })
84
+ )
85
+
86
+ const uploadConfig: UseImportContainerUploadConfig = {
87
+ file: mockFile,
88
+ resourceType: 'product',
89
+ settings: { format: 'CSV' },
90
+ onSuccess,
91
+ onProgress,
92
+ }
93
+
94
+ await act(async () => {
95
+ await result.current.upload(uploadConfig)
96
+ })
97
+
98
+ expect(mockCreateImportContainerForFileUpload).toHaveBeenCalledWith({
99
+ importContainerDraft: {
100
+ key: importContainerKey,
101
+ resourceType: 'product',
102
+ tags: ['source:file-upload'],
103
+ settings: { format: 'CSV' },
104
+ },
105
+ projectKey,
106
+ })
107
+
108
+ expect(mockUploadFileForImport).toHaveBeenCalledWith({
109
+ projectKey,
110
+ importContainerKey,
111
+ resourceType: 'product',
112
+ file: mockFile,
113
+ onSuccess: expect.any(Function),
114
+ onProgress: expect.any(Function),
115
+ onError: expect.any(Function),
116
+ })
117
+
118
+ expect(onSuccess).toHaveBeenCalledWith(
119
+ mockFileUploadResponse,
120
+ importContainerKey
121
+ )
122
+ })
123
+
124
+ it('should delete container on upload error', async () => {
125
+ const onError = jest.fn()
126
+ const uploadError = new Error('Upload failed')
127
+
128
+ mockUploadFileForImport.mockImplementation(({ onError: _onError }) => {
129
+ _onError?.(uploadError)
130
+ return mockXhr
131
+ })
132
+
133
+ const { result } = renderHook(() =>
134
+ useImportContainerUpload({ projectKey })
135
+ )
136
+
137
+ await act(async () => {
138
+ await result.current.upload({
139
+ file: mockFile,
140
+ resourceType: 'product',
141
+ onSuccess: jest.fn(),
142
+ onError,
143
+ })
144
+ })
145
+
146
+ await waitFor(() => {
147
+ expect(mockDeleteImportContainer).toHaveBeenCalledWith({
148
+ projectKey,
149
+ importContainerKey,
150
+ })
151
+ })
152
+ expect(onError).toHaveBeenCalledWith(uploadError)
153
+ })
154
+
155
+ it('should delete container if upload throws before starting', async () => {
156
+ const error = new Error('Container creation failed')
157
+ mockCreateImportContainerForFileUpload.mockRejectedValue(error)
158
+
159
+ const { result } = renderHook(() =>
160
+ useImportContainerUpload({ projectKey })
161
+ )
162
+
163
+ await expect(
164
+ act(async () => {
165
+ await result.current.upload({
166
+ file: mockFile,
167
+ resourceType: 'product',
168
+ onSuccess: jest.fn(),
169
+ })
170
+ })
171
+ ).rejects.toThrow(error)
172
+
173
+ expect(mockDeleteImportContainer).toHaveBeenCalledWith({
174
+ projectKey,
175
+ importContainerKey,
176
+ })
177
+ })
178
+
179
+ it('should update progress during upload', async () => {
180
+ const onProgress = jest.fn()
181
+
182
+ mockUploadFileForImport.mockImplementation(
183
+ ({ onProgress: _onProgress }) => {
184
+ _onProgress?.(50)
185
+ _onProgress?.(100)
186
+ return mockXhr
187
+ }
188
+ )
189
+
190
+ const { result } = renderHook(() =>
191
+ useImportContainerUpload({ projectKey })
192
+ )
193
+
194
+ await act(async () => {
195
+ await result.current.upload({
196
+ file: mockFile,
197
+ resourceType: 'product',
198
+ onSuccess: jest.fn(),
199
+ onProgress,
200
+ })
201
+ })
202
+
203
+ expect(onProgress).toHaveBeenCalledWith(50)
204
+ expect(onProgress).toHaveBeenCalledWith(100)
205
+ })
206
+
207
+ it('should update isUploading state correctly', async () => {
208
+ mockUploadFileForImport.mockImplementation(({ onSuccess }) => {
209
+ setTimeout(() => onSuccess(mockFileUploadResponse), 100)
210
+ return mockXhr
211
+ })
212
+
213
+ const { result } = renderHook(() =>
214
+ useImportContainerUpload({ projectKey })
215
+ )
216
+
217
+ expect(result.current.isUploading).toBe(false)
218
+
219
+ act(() => {
220
+ result.current.upload({
221
+ file: mockFile,
222
+ resourceType: 'product',
223
+ onSuccess: jest.fn(),
224
+ })
225
+ })
226
+
227
+ expect(result.current.isUploading).toBe(true)
228
+
229
+ await waitFor(() => {
230
+ expect(result.current.isUploading).toBe(false)
231
+ })
232
+ })
233
+
234
+ it('should abort upload when abort is called', async () => {
235
+ mockUploadFileForImport.mockReturnValue(mockXhr)
236
+
237
+ const { result } = renderHook(() =>
238
+ useImportContainerUpload({ projectKey })
239
+ )
240
+
241
+ act(() => {
242
+ result.current.upload({
243
+ file: mockFile,
244
+ resourceType: 'product',
245
+ onSuccess: jest.fn(),
246
+ })
247
+ })
248
+
249
+ await waitFor(() => {
250
+ expect(mockUploadFileForImport).toHaveBeenCalled()
251
+ })
252
+
253
+ act(() => {
254
+ result.current.abort()
255
+ })
256
+
257
+ expect(mockXhr.abort).toHaveBeenCalled()
258
+ })
259
+
260
+ it('should work without optional settings', async () => {
261
+ const onSuccess = jest.fn()
262
+
263
+ mockUploadFileForImport.mockImplementation(({ onSuccess: _onSuccess }) => {
264
+ _onSuccess(mockFileUploadResponse)
265
+ return mockXhr
266
+ })
267
+
268
+ const { result } = renderHook(() =>
269
+ useImportContainerUpload({ projectKey })
270
+ )
271
+
272
+ await act(async () => {
273
+ await result.current.upload({
274
+ file: mockFile,
275
+ resourceType: 'product',
276
+ onSuccess,
277
+ })
278
+ })
279
+
280
+ expect(mockCreateImportContainerForFileUpload).toHaveBeenCalledWith({
281
+ importContainerDraft: {
282
+ key: importContainerKey,
283
+ resourceType: 'product',
284
+ tags: ['source:file-upload'],
285
+ },
286
+ projectKey,
287
+ })
288
+
289
+ expect(onSuccess).toHaveBeenCalledWith(
290
+ mockFileUploadResponse,
291
+ importContainerKey
292
+ )
293
+ })
294
+ })
@@ -0,0 +1,126 @@
1
+ import React from 'react'
2
+ import type { ResourceTypeId } from '@commercetools/importapi-sdk'
3
+ import {
4
+ createImportContainerForFileUpload,
5
+ deleteImportContainer,
6
+ uploadFileForImport,
7
+ } from '../@api'
8
+ import { ProjectKeyNotAvailableError } from '../@errors'
9
+ import { TAG_KEY_SOURCE_FILE_UPLOAD } from '../@constants'
10
+ import { encodeFileNameWithTimestampToContainerKey } from '../@utils'
11
+ import type {
12
+ FileUploadResponse,
13
+ ExtendedImportContainerDraft,
14
+ } from '../@types'
15
+
16
+ export type UseImportContainerUploadConfig = {
17
+ file: File
18
+ resourceType: ResourceTypeId
19
+ settings?: ExtendedImportContainerDraft['settings']
20
+ onSuccess: (response: FileUploadResponse, importContainerKey: string) => void
21
+ onError?: (error: unknown) => void
22
+ onProgress?: (progress: number) => void
23
+ }
24
+
25
+ export const useImportContainerUpload = ({
26
+ projectKey,
27
+ }: {
28
+ projectKey: string
29
+ }) => {
30
+ const [isUploading, setIsUploading] = React.useState(false)
31
+ const [progress, setProgress] = React.useState(0)
32
+ const xhrRef = React.useRef<XMLHttpRequest | null>(null)
33
+
34
+ const upload = async ({
35
+ file,
36
+ resourceType,
37
+ settings,
38
+ onSuccess,
39
+ onError,
40
+ onProgress,
41
+ }: UseImportContainerUploadConfig): Promise<XMLHttpRequest> => {
42
+ if (!projectKey) {
43
+ throw new ProjectKeyNotAvailableError()
44
+ }
45
+
46
+ setIsUploading(true)
47
+ setProgress(0)
48
+
49
+ const importContainerKey = encodeFileNameWithTimestampToContainerKey(
50
+ file.name
51
+ )
52
+
53
+ try {
54
+ await createImportContainerForFileUpload({
55
+ importContainerDraft: {
56
+ key: importContainerKey,
57
+ resourceType,
58
+ tags: [TAG_KEY_SOURCE_FILE_UPLOAD],
59
+ ...(settings ? { settings } : {}),
60
+ },
61
+ projectKey,
62
+ })
63
+
64
+ const xhr = uploadFileForImport({
65
+ projectKey,
66
+ importContainerKey,
67
+ resourceType,
68
+ file,
69
+ onSuccess: (response) => {
70
+ setIsUploading(false)
71
+ setProgress(100)
72
+ onSuccess(response, importContainerKey)
73
+ },
74
+ onProgress: (prog) => {
75
+ setProgress(prog)
76
+ onProgress?.(prog)
77
+ },
78
+ onError: async (error) => {
79
+ try {
80
+ await deleteImportContainer({
81
+ projectKey,
82
+ importContainerKey,
83
+ })
84
+ } catch {
85
+ // Ignore cleanup errors - container will be cleaned up by TTL retention policy
86
+ // Cleanup errors are unlikely unless there is a network issue or container was removed externally
87
+ }
88
+
89
+ setIsUploading(false)
90
+ setProgress(0)
91
+ onError?.(error)
92
+ },
93
+ })
94
+
95
+ xhrRef.current = xhr
96
+ return xhr
97
+ } catch (error) {
98
+ try {
99
+ await deleteImportContainer({
100
+ projectKey,
101
+ importContainerKey,
102
+ })
103
+ } catch {
104
+ // Ignore cleanup errors - container will be cleaned up by TTL retention policy
105
+ // Cleanup errors are unlikely unless there is a network issue or container was removed externally
106
+ }
107
+
108
+ setIsUploading(false)
109
+ setProgress(0)
110
+ throw error
111
+ }
112
+ }
113
+
114
+ const abort = () => {
115
+ xhrRef.current?.abort()
116
+ setIsUploading(false)
117
+ setProgress(0)
118
+ }
119
+
120
+ return {
121
+ upload,
122
+ abort,
123
+ isUploading,
124
+ progress,
125
+ }
126
+ }
@@ -0,0 +1,14 @@
1
+ type METHOD = 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH'
2
+
3
+ export type ApiConfig = {
4
+ headers?: Record<string, string | null>
5
+ method?: METHOD
6
+ proxy?: string
7
+ abortSignal?: AbortSignal
8
+ }
9
+
10
+ export type Fetcher = {
11
+ url: string
12
+ payload?: BodyInit
13
+ config?: ApiConfig
14
+ }
@@ -0,0 +1,5 @@
1
+ export interface BasicErrorDataType {
2
+ message: string
3
+ code: string
4
+ [key: string]: unknown
5
+ }
@@ -0,0 +1,144 @@
1
+ import type { ResourceTypeId } from '@commercetools/importapi-sdk'
2
+ import { hasRequiredFields } from './shared'
3
+
4
+ export type ExportOperationState =
5
+ | 'processing'
6
+ | 'completed'
7
+ | 'failed'
8
+ | 'queued'
9
+
10
+ export type ResourceType = ResourceTypeId
11
+
12
+ export type FileFormat = 'json' | 'csv'
13
+
14
+ export type Separator = {
15
+ columns?: string
16
+ arrayValues?: string
17
+ decimal?: string
18
+ }
19
+
20
+ export type ExportOperation = {
21
+ id: string
22
+ fileName: string
23
+ createdAt: string
24
+ resourceType: ResourceType
25
+ state: ExportOperationState
26
+ completedPercentage: number
27
+ resourceCount: number
28
+ fileFormat: FileFormat
29
+ fields: string[]
30
+ locales: string[]
31
+ query?: string
32
+ filters?: {
33
+ filters: string[]
34
+ fullText?: {
35
+ text: string
36
+ locale: string
37
+ }
38
+ }
39
+ fillRows?: boolean
40
+ excelCompatible?: boolean
41
+ dryRun?: boolean
42
+ separator?: Separator
43
+ }
44
+
45
+ export interface PaginatedExportOperationResponse {
46
+ results: ExportOperation[]
47
+ limit: number
48
+ offset: number
49
+ total: number
50
+ count: number
51
+ }
52
+
53
+ export type DownloadFile = {
54
+ uri: string
55
+ }
56
+
57
+ export type ExportOperationQueryParams = {
58
+ limit: number
59
+ offset: number
60
+ }
61
+
62
+ export function assertPaginatedExportOperationResponse(
63
+ maybePaginatedExportOperationResponseOrExportOperationsResponse: unknown
64
+ ): asserts maybePaginatedExportOperationResponseOrExportOperationsResponse is PaginatedExportOperationResponse {
65
+ const requiredFields = ['results', 'total']
66
+ if (
67
+ hasRequiredFields(
68
+ maybePaginatedExportOperationResponseOrExportOperationsResponse,
69
+ requiredFields
70
+ )
71
+ )
72
+ return
73
+ throw new Error('Invalid response')
74
+ }
75
+
76
+ export function assertExportOperationsDownloadFileResponse(
77
+ maybeExportOperationDownloadFileResponse: unknown
78
+ ): asserts maybeExportOperationDownloadFileResponse is DownloadFile {
79
+ const requiredFields = ['uri']
80
+ if (
81
+ hasRequiredFields(maybeExportOperationDownloadFileResponse, requiredFields)
82
+ )
83
+ return
84
+ throw new Error('Invalid response')
85
+ }
86
+
87
+ export type QueryFilter = {
88
+ query?: Record<string, unknown>
89
+ }
90
+
91
+ export type SearchFilter = {
92
+ filters?: string[]
93
+ fullText?: {
94
+ text: string
95
+ locale: string
96
+ }
97
+ }
98
+
99
+ export interface ExportApiRequest {
100
+ projectKey: string
101
+ resourceType: string
102
+ fileName: string
103
+ fileFormat: string
104
+ fields: string[]
105
+ locales?: string[]
106
+ filters?: QueryFilter | SearchFilter
107
+ where?: string
108
+ fullText?: {
109
+ text: string
110
+ locale: string
111
+ }
112
+ fillRows?: boolean
113
+ excelCompatible?: boolean
114
+ separator?: Separator
115
+ }
116
+
117
+ export interface ExportApiSuccessResponse {
118
+ id: string
119
+ state: string
120
+ resourceType: string
121
+ resourceCount: number
122
+ createdAt: string
123
+ }
124
+
125
+ export interface ExportApiErrorResponse {
126
+ statusCode: number
127
+ error: string
128
+ errors: ValidationError[]
129
+ message: string
130
+ }
131
+
132
+ export type ValidationErrorCode =
133
+ | 'DuplicateFields'
134
+ | 'NotSupportedField'
135
+ | 'FieldDefinitionNotFound'
136
+ | 'AttributeDefinitionNotFound'
137
+ | 'LocalizedFieldWithoutLocale'
138
+ | 'IncompleteField'
139
+
140
+ export type ValidationError = {
141
+ code: ValidationErrorCode
142
+ message: string
143
+ field?: string
144
+ }
@@ -0,0 +1,81 @@
1
+ import type { ResourceTypeId } from '@commercetools/importapi-sdk'
2
+
3
+ export type FileUploadError = {
4
+ title: string
5
+ description: string
6
+ }
7
+
8
+ export type UploadFileErrorRow = {
9
+ id: string
10
+ row?: number
11
+ index?: number
12
+ code: string
13
+ field: string
14
+ validationMessage: string
15
+ }
16
+
17
+ export type RowError = {
18
+ code: string
19
+ message: string
20
+ field: string
21
+ }
22
+
23
+ export type RowErrorsResponse = {
24
+ row?: number
25
+ index?: number
26
+ errors: Array<RowError>
27
+ }
28
+
29
+ export type FileUploadResponse = {
30
+ results: Array<RowErrorsResponse>
31
+ invalid: number
32
+ valid: number
33
+ fileName: string
34
+ itemsCount: number
35
+ rowsCount: number
36
+ columnsCount: number
37
+ columns: Array<string>
38
+ ignoredColumns: Array<string>
39
+ }
40
+
41
+ export interface FileUploadRequestParameters {
42
+ projectKey: string
43
+ importContainerKey: string
44
+ resourceType: ResourceTypeId
45
+ file: File
46
+ abortSignal?: AbortSignal
47
+ onSuccess: (data: FileUploadResponse) => void
48
+ onProgress: (progress: number) => void
49
+ onError: (error: Error) => void
50
+ }
51
+
52
+ export interface ProcessUploadedFileRequestParameters {
53
+ projectKey: string
54
+ importContainerKey: string
55
+ resourceType: ResourceTypeId
56
+ action?: 'delete' // Currently only 'delete' action is supported
57
+ }
58
+
59
+ export type ProcessFileResponse = {
60
+ message: string
61
+ }
62
+
63
+ export type MissingCsvFieldIdentifierError = {
64
+ code: string
65
+ message: string
66
+ rowValue: Record<string, unknown>
67
+ metadata: {
68
+ row: number
69
+ }
70
+ }
71
+
72
+ export type DecimalSeparator = '.' | ','
73
+
74
+ export type DropAreaState =
75
+ | 'default'
76
+ | 'active-drag'
77
+ | 'ready-for-drop'
78
+ | 'file-dropped'
79
+ | 'disabled'
80
+ | 'invalid'
81
+ | 'is-parsing'