@classytic/commerce-sdk 0.1.1
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.
- package/LICENSE +14 -0
- package/README.md +164 -0
- package/dist/adjustment-MNH3AT6S.js +5 -0
- package/dist/adjustment-MNH3AT6S.js.map +1 -0
- package/dist/analytics/index.d.ts +27 -0
- package/dist/analytics/index.js +6 -0
- package/dist/analytics/index.js.map +1 -0
- package/dist/analytics-DMcD-o8w.d.ts +76 -0
- package/dist/api-factory-B_h4RKBm.d.ts +280 -0
- package/dist/auth/index.d.ts +39 -0
- package/dist/auth/index.js +5 -0
- package/dist/auth/index.js.map +1 -0
- package/dist/catalog/index.d.ts +571 -0
- package/dist/catalog/index.js +9 -0
- package/dist/catalog/index.js.map +1 -0
- package/dist/chunk-24FDD6UR.js +75 -0
- package/dist/chunk-24FDD6UR.js.map +1 -0
- package/dist/chunk-2TF7QNYV.js +159 -0
- package/dist/chunk-2TF7QNYV.js.map +1 -0
- package/dist/chunk-2YAZ5WG6.js +479 -0
- package/dist/chunk-2YAZ5WG6.js.map +1 -0
- package/dist/chunk-36NLLAVH.js +177 -0
- package/dist/chunk-36NLLAVH.js.map +1 -0
- package/dist/chunk-3OYSJB3P.js +126 -0
- package/dist/chunk-3OYSJB3P.js.map +1 -0
- package/dist/chunk-5E57JODA.js +135 -0
- package/dist/chunk-5E57JODA.js.map +1 -0
- package/dist/chunk-7LZCW4VF.js +13 -0
- package/dist/chunk-7LZCW4VF.js.map +1 -0
- package/dist/chunk-ANYGZ6O5.js +830 -0
- package/dist/chunk-ANYGZ6O5.js.map +1 -0
- package/dist/chunk-AQAISI4F.js +183 -0
- package/dist/chunk-AQAISI4F.js.map +1 -0
- package/dist/chunk-B6MPVOV7.js +328 -0
- package/dist/chunk-B6MPVOV7.js.map +1 -0
- package/dist/chunk-CILP56G2.js +94 -0
- package/dist/chunk-CILP56G2.js.map +1 -0
- package/dist/chunk-ERQ52WHY.js +534 -0
- package/dist/chunk-ERQ52WHY.js.map +1 -0
- package/dist/chunk-FOTUJPM4.js +640 -0
- package/dist/chunk-FOTUJPM4.js.map +1 -0
- package/dist/chunk-IHCBBLLW.js +198 -0
- package/dist/chunk-IHCBBLLW.js.map +1 -0
- package/dist/chunk-J4JBQET2.js +76 -0
- package/dist/chunk-J4JBQET2.js.map +1 -0
- package/dist/chunk-L4OEI4VZ.js +123 -0
- package/dist/chunk-L4OEI4VZ.js.map +1 -0
- package/dist/chunk-LRV7MWWX.js +616 -0
- package/dist/chunk-LRV7MWWX.js.map +1 -0
- package/dist/chunk-N43VE355.js +126 -0
- package/dist/chunk-N43VE355.js.map +1 -0
- package/dist/chunk-PYYLHUV6.js +3 -0
- package/dist/chunk-PYYLHUV6.js.map +1 -0
- package/dist/chunk-QCTXAMLA.js +261 -0
- package/dist/chunk-QCTXAMLA.js.map +1 -0
- package/dist/chunk-RIKAPJNG.js +40 -0
- package/dist/chunk-RIKAPJNG.js.map +1 -0
- package/dist/chunk-U3XT35GZ.js +202 -0
- package/dist/chunk-U3XT35GZ.js.map +1 -0
- package/dist/chunk-W22WB3WZ.js +148 -0
- package/dist/chunk-W22WB3WZ.js.map +1 -0
- package/dist/chunk-WTIJMKML.js +27 -0
- package/dist/chunk-WTIJMKML.js.map +1 -0
- package/dist/chunk-X2CQFJPR.js +75 -0
- package/dist/chunk-X2CQFJPR.js.map +1 -0
- package/dist/chunk-YYFKLOKO.js +769 -0
- package/dist/chunk-YYFKLOKO.js.map +1 -0
- package/dist/client-Cs7E_usr.d.ts +113 -0
- package/dist/content/index.d.ts +309 -0
- package/dist/content/index.js +6 -0
- package/dist/content/index.js.map +1 -0
- package/dist/core/index.d.ts +166 -0
- package/dist/core/index.js +5 -0
- package/dist/core/index.js.map +1 -0
- package/dist/core/react.d.ts +107 -0
- package/dist/core/react.js +5 -0
- package/dist/core/react.js.map +1 -0
- package/dist/coupon-BZSZ0y3n.d.ts +129 -0
- package/dist/coupon-CDzL4bJG.d.ts +655 -0
- package/dist/crud.factory-DyKaPHcU.d.ts +181 -0
- package/dist/finance/index.d.ts +81 -0
- package/dist/finance/index.js +5 -0
- package/dist/finance/index.js.map +1 -0
- package/dist/finance-BJdfKRw0.d.ts +135 -0
- package/dist/index.d.ts +32 -0
- package/dist/index.js +29 -0
- package/dist/index.js.map +1 -0
- package/dist/inventory/index.d.ts +512 -0
- package/dist/inventory/index.js +16 -0
- package/dist/inventory/index.js.map +1 -0
- package/dist/inventory-B5pssqRx.d.ts +748 -0
- package/dist/logistics/index.d.ts +248 -0
- package/dist/logistics/index.js +7 -0
- package/dist/logistics/index.js.map +1 -0
- package/dist/logistics-CrpKadKE.d.ts +410 -0
- package/dist/media-CNLJK93J.d.ts +721 -0
- package/dist/movement-R3CERFAM.js +5 -0
- package/dist/movement-R3CERFAM.js.map +1 -0
- package/dist/order-B3dCvHgK.d.ts +360 -0
- package/dist/payment-BRboLqvU.d.ts +127 -0
- package/dist/payments/index.d.ts +55 -0
- package/dist/payments/index.js +6 -0
- package/dist/payments/index.js.map +1 -0
- package/dist/platform/index.d.ts +645 -0
- package/dist/platform/index.js +8 -0
- package/dist/platform/index.js.map +1 -0
- package/dist/pos-BCqkx2-K.d.ts +527 -0
- package/dist/product-p09zXkXB.d.ts +260 -0
- package/dist/purchase-54PER2PY.js +5 -0
- package/dist/purchase-54PER2PY.js.map +1 -0
- package/dist/request-MP6NV5ZE.js +5 -0
- package/dist/request-MP6NV5ZE.js.map +1 -0
- package/dist/sales/index.d.ts +587 -0
- package/dist/sales/index.js +9 -0
- package/dist/sales/index.js.map +1 -0
- package/dist/server.d.ts +23 -0
- package/dist/server.js +37 -0
- package/dist/server.js.map +1 -0
- package/dist/size-guide-DgjzjM5P.d.ts +554 -0
- package/dist/stock-2LP4HJSB.js +5 -0
- package/dist/stock-2LP4HJSB.js.map +1 -0
- package/dist/stock-CfrU5_Wr.d.ts +632 -0
- package/dist/supplier-BWJTRZ5Z.js +5 -0
- package/dist/supplier-BWJTRZ5Z.js.map +1 -0
- package/dist/transaction/index.d.ts +104 -0
- package/dist/transaction/index.js +8 -0
- package/dist/transaction/index.js.map +1 -0
- package/dist/transaction-Bf6WjYCh.d.ts +84 -0
- package/dist/transaction-dL3WW-er.d.ts +442 -0
- package/dist/transfer-4XSS6HWT.js +5 -0
- package/dist/transfer-4XSS6HWT.js.map +1 -0
- package/dist/user-data-DdLjAGwO.d.ts +132 -0
- package/package.json +147 -0
|
@@ -0,0 +1,534 @@
|
|
|
1
|
+
import { getToastHandler } from './chunk-U3XT35GZ.js';
|
|
2
|
+
import { handleApiRequest } from './chunk-IHCBBLLW.js';
|
|
3
|
+
import { useQuery, useQueryClient, useMutation } from '@tanstack/react-query';
|
|
4
|
+
|
|
5
|
+
// src/content/api/cms.ts
|
|
6
|
+
var CMS_BASE = "/api/v1/cms";
|
|
7
|
+
async function getCmsPage({
|
|
8
|
+
slug,
|
|
9
|
+
options = {}
|
|
10
|
+
}) {
|
|
11
|
+
if (!slug) throw new Error("Slug is required");
|
|
12
|
+
try {
|
|
13
|
+
return await handleApiRequest(
|
|
14
|
+
"GET",
|
|
15
|
+
`${CMS_BASE}/${slug}`,
|
|
16
|
+
options
|
|
17
|
+
);
|
|
18
|
+
} catch (error) {
|
|
19
|
+
if (error instanceof Error && error.message.includes("Document not found")) {
|
|
20
|
+
return {
|
|
21
|
+
success: false,
|
|
22
|
+
data: null,
|
|
23
|
+
message: "CMS page not found, using static fallback"
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
throw error;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
async function updateCmsPage({
|
|
30
|
+
slug,
|
|
31
|
+
token,
|
|
32
|
+
data,
|
|
33
|
+
options = {}
|
|
34
|
+
}) {
|
|
35
|
+
if (!slug) throw new Error("Slug is required");
|
|
36
|
+
return handleApiRequest(
|
|
37
|
+
"PATCH",
|
|
38
|
+
`${CMS_BASE}/${slug}`,
|
|
39
|
+
{ token, body: data, ...options }
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// src/content/api/media.ts
|
|
44
|
+
var MediaApi = class {
|
|
45
|
+
constructor(config = {}) {
|
|
46
|
+
this.baseUrl = `${config.basePath || "/api/v1"}/media`;
|
|
47
|
+
this.defaultCache = config.cache || "no-store";
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Upload single file
|
|
51
|
+
* POST /api/media/upload
|
|
52
|
+
*
|
|
53
|
+
* @example
|
|
54
|
+
* const file = event.target.files[0];
|
|
55
|
+
* const result = await mediaApi.upload({
|
|
56
|
+
* token: 'xxx',
|
|
57
|
+
* file,
|
|
58
|
+
* folder: 'products',
|
|
59
|
+
* alt: 'Product image'
|
|
60
|
+
* });
|
|
61
|
+
*/
|
|
62
|
+
async upload({
|
|
63
|
+
token,
|
|
64
|
+
file,
|
|
65
|
+
folder,
|
|
66
|
+
alt,
|
|
67
|
+
title,
|
|
68
|
+
options = {}
|
|
69
|
+
}) {
|
|
70
|
+
const formData = new FormData();
|
|
71
|
+
if (folder) formData.append("folder", folder);
|
|
72
|
+
if (alt) formData.append("alt", alt);
|
|
73
|
+
if (title) formData.append("title", title);
|
|
74
|
+
formData.append("file", file);
|
|
75
|
+
return handleApiRequest("POST", `${this.baseUrl}/upload`, {
|
|
76
|
+
token,
|
|
77
|
+
body: formData,
|
|
78
|
+
cache: this.defaultCache,
|
|
79
|
+
...options
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Upload multiple files (max 20)
|
|
84
|
+
* POST /api/media/upload-multiple
|
|
85
|
+
*
|
|
86
|
+
* @example
|
|
87
|
+
* const files = Array.from(event.target.files);
|
|
88
|
+
* const result = await mediaApi.uploadMultiple({
|
|
89
|
+
* token: 'xxx',
|
|
90
|
+
* files,
|
|
91
|
+
* folder: 'products'
|
|
92
|
+
* });
|
|
93
|
+
*/
|
|
94
|
+
async uploadMultiple({
|
|
95
|
+
token,
|
|
96
|
+
files,
|
|
97
|
+
folder,
|
|
98
|
+
options = {}
|
|
99
|
+
}) {
|
|
100
|
+
const formData = new FormData();
|
|
101
|
+
if (folder) formData.append("folder", folder);
|
|
102
|
+
files.forEach((file) => formData.append("files[]", file));
|
|
103
|
+
return handleApiRequest("POST", `${this.baseUrl}/upload-multiple`, {
|
|
104
|
+
token,
|
|
105
|
+
body: formData,
|
|
106
|
+
cache: this.defaultCache,
|
|
107
|
+
...options
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Get all media with filtering
|
|
112
|
+
* GET /api/media
|
|
113
|
+
*
|
|
114
|
+
* @example
|
|
115
|
+
* const result = await mediaApi.getAll({
|
|
116
|
+
* token: 'xxx',
|
|
117
|
+
* params: {
|
|
118
|
+
* folder: 'products',
|
|
119
|
+
* search: 'shirt',
|
|
120
|
+
* limit: 20,
|
|
121
|
+
* sort: '-createdAt'
|
|
122
|
+
* }
|
|
123
|
+
* });
|
|
124
|
+
*/
|
|
125
|
+
async getAll({
|
|
126
|
+
token,
|
|
127
|
+
params = {},
|
|
128
|
+
options = {}
|
|
129
|
+
} = {}) {
|
|
130
|
+
const cleanParams = Object.fromEntries(
|
|
131
|
+
Object.entries(params).filter(([, v]) => v !== void 0 && v !== null)
|
|
132
|
+
);
|
|
133
|
+
const queryString = new URLSearchParams(
|
|
134
|
+
cleanParams
|
|
135
|
+
).toString();
|
|
136
|
+
const url = queryString ? `${this.baseUrl}?${queryString}` : this.baseUrl;
|
|
137
|
+
return handleApiRequest("GET", url, {
|
|
138
|
+
token,
|
|
139
|
+
cache: this.defaultCache,
|
|
140
|
+
...options
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Get single media by ID
|
|
145
|
+
* GET /api/media/:id
|
|
146
|
+
*
|
|
147
|
+
* @example
|
|
148
|
+
* const result = await mediaApi.getById({ token: 'xxx', id: '123abc' });
|
|
149
|
+
*/
|
|
150
|
+
async getById({
|
|
151
|
+
token,
|
|
152
|
+
id,
|
|
153
|
+
options = {}
|
|
154
|
+
}) {
|
|
155
|
+
if (!id) throw new Error("ID is required");
|
|
156
|
+
return handleApiRequest("GET", `${this.baseUrl}/${id}`, {
|
|
157
|
+
token,
|
|
158
|
+
cache: this.defaultCache,
|
|
159
|
+
...options
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Update media metadata (alt, title)
|
|
164
|
+
* PATCH /api/media/:id
|
|
165
|
+
*
|
|
166
|
+
* @example
|
|
167
|
+
* await mediaApi.update({
|
|
168
|
+
* token: 'xxx',
|
|
169
|
+
* id: '123',
|
|
170
|
+
* data: { alt: 'New alt text', title: 'New title' }
|
|
171
|
+
* });
|
|
172
|
+
*/
|
|
173
|
+
async update({
|
|
174
|
+
token,
|
|
175
|
+
id,
|
|
176
|
+
data,
|
|
177
|
+
options = {}
|
|
178
|
+
}) {
|
|
179
|
+
if (!id) throw new Error("ID is required");
|
|
180
|
+
return handleApiRequest("PATCH", `${this.baseUrl}/${id}`, {
|
|
181
|
+
token,
|
|
182
|
+
body: data,
|
|
183
|
+
cache: this.defaultCache,
|
|
184
|
+
...options
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Delete single media
|
|
189
|
+
* DELETE /api/media/:id
|
|
190
|
+
*
|
|
191
|
+
* @example
|
|
192
|
+
* await mediaApi.delete({ token: 'xxx', id: '123' });
|
|
193
|
+
*/
|
|
194
|
+
async delete({
|
|
195
|
+
token,
|
|
196
|
+
id,
|
|
197
|
+
options = {}
|
|
198
|
+
}) {
|
|
199
|
+
if (!id) throw new Error("ID is required");
|
|
200
|
+
return handleApiRequest("DELETE", `${this.baseUrl}/${id}`, {
|
|
201
|
+
token,
|
|
202
|
+
cache: this.defaultCache,
|
|
203
|
+
...options
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* Bulk delete multiple files
|
|
208
|
+
* POST /api/media/bulk-delete
|
|
209
|
+
*
|
|
210
|
+
* @example
|
|
211
|
+
* await mediaApi.bulkDelete({
|
|
212
|
+
* token: 'xxx',
|
|
213
|
+
* ids: ['123', '456', '789']
|
|
214
|
+
* });
|
|
215
|
+
*/
|
|
216
|
+
async bulkDelete({
|
|
217
|
+
token,
|
|
218
|
+
ids,
|
|
219
|
+
options = {}
|
|
220
|
+
}) {
|
|
221
|
+
if (!ids.length) throw new Error("IDs array is required");
|
|
222
|
+
return handleApiRequest("POST", `${this.baseUrl}/bulk-delete`, {
|
|
223
|
+
token,
|
|
224
|
+
body: { ids },
|
|
225
|
+
cache: this.defaultCache,
|
|
226
|
+
...options
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* Move files to different folder
|
|
231
|
+
* POST /api/media/move
|
|
232
|
+
*
|
|
233
|
+
* @example
|
|
234
|
+
* await mediaApi.moveToFolder({
|
|
235
|
+
* token: 'xxx',
|
|
236
|
+
* ids: ['123', '456'],
|
|
237
|
+
* targetFolder: 'banners'
|
|
238
|
+
* });
|
|
239
|
+
*/
|
|
240
|
+
async moveToFolder({
|
|
241
|
+
token,
|
|
242
|
+
data,
|
|
243
|
+
options = {}
|
|
244
|
+
}) {
|
|
245
|
+
if (!data.ids.length) throw new Error("IDs array is required");
|
|
246
|
+
if (!data.targetFolder) throw new Error("Target folder is required");
|
|
247
|
+
return handleApiRequest("POST", `${this.baseUrl}/move`, {
|
|
248
|
+
token,
|
|
249
|
+
body: data,
|
|
250
|
+
cache: this.defaultCache,
|
|
251
|
+
...options
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
/**
|
|
255
|
+
* Get allowed folders
|
|
256
|
+
* GET /api/media/folders
|
|
257
|
+
*
|
|
258
|
+
* @example
|
|
259
|
+
* const result = await mediaApi.getFolders({ token: 'xxx' });
|
|
260
|
+
* // result.data = ['general', 'products', 'categories', ...]
|
|
261
|
+
*/
|
|
262
|
+
async getFolders({
|
|
263
|
+
token,
|
|
264
|
+
options = {}
|
|
265
|
+
}) {
|
|
266
|
+
return handleApiRequest("GET", `${this.baseUrl}/folders`, {
|
|
267
|
+
token,
|
|
268
|
+
cache: this.defaultCache,
|
|
269
|
+
...options
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
/**
|
|
273
|
+
* Helper: Get variant URL by name
|
|
274
|
+
* Falls back to original URL if variant not found
|
|
275
|
+
*
|
|
276
|
+
* @example
|
|
277
|
+
* const thumbnailUrl = mediaApi.getVariantUrl(media, 'thumbnail');
|
|
278
|
+
* <img src={thumbnailUrl} />
|
|
279
|
+
*/
|
|
280
|
+
getVariantUrl(media, variantName) {
|
|
281
|
+
return media.variants?.find((v) => v.name === variantName)?.url || media.url;
|
|
282
|
+
}
|
|
283
|
+
/**
|
|
284
|
+
* Helper: Get thumbnail URL
|
|
285
|
+
*/
|
|
286
|
+
getThumbnailUrl(media) {
|
|
287
|
+
return this.getVariantUrl(media, "thumbnail");
|
|
288
|
+
}
|
|
289
|
+
/**
|
|
290
|
+
* Helper: Get medium URL
|
|
291
|
+
*/
|
|
292
|
+
getMediumUrl(media) {
|
|
293
|
+
return this.getVariantUrl(media, "medium");
|
|
294
|
+
}
|
|
295
|
+
};
|
|
296
|
+
var mediaApi = new MediaApi();
|
|
297
|
+
var MEDIA_KEYS = {
|
|
298
|
+
all: ["media"],
|
|
299
|
+
lists: () => [...MEDIA_KEYS.all, "list"],
|
|
300
|
+
list: (params) => [...MEDIA_KEYS.lists(), params],
|
|
301
|
+
details: () => [...MEDIA_KEYS.all, "detail"],
|
|
302
|
+
detail: (id) => [...MEDIA_KEYS.details(), id],
|
|
303
|
+
folders: () => [...MEDIA_KEYS.all, "folders"]
|
|
304
|
+
};
|
|
305
|
+
function useMediaList(token, params = {}, options = {}) {
|
|
306
|
+
const {
|
|
307
|
+
enabled = true,
|
|
308
|
+
staleTime = 2 * 60 * 1e3,
|
|
309
|
+
gcTime = 5 * 60 * 1e3,
|
|
310
|
+
refetchOnWindowFocus = false
|
|
311
|
+
} = options;
|
|
312
|
+
return useQuery({
|
|
313
|
+
queryKey: MEDIA_KEYS.list(params),
|
|
314
|
+
queryFn: () => mediaApi.getAll({ token, params }),
|
|
315
|
+
enabled: enabled && !!token,
|
|
316
|
+
staleTime,
|
|
317
|
+
gcTime,
|
|
318
|
+
refetchOnWindowFocus
|
|
319
|
+
});
|
|
320
|
+
}
|
|
321
|
+
function useMediaDetail(token, id, options = {}) {
|
|
322
|
+
const { enabled = true, staleTime = 2 * 60 * 1e3, gcTime = 5 * 60 * 1e3 } = options;
|
|
323
|
+
return useQuery({
|
|
324
|
+
queryKey: MEDIA_KEYS.detail(id),
|
|
325
|
+
queryFn: () => mediaApi.getById({ token, id }),
|
|
326
|
+
enabled: enabled && !!token && !!id,
|
|
327
|
+
staleTime,
|
|
328
|
+
gcTime
|
|
329
|
+
});
|
|
330
|
+
}
|
|
331
|
+
function useMediaFolders(token, options = {}) {
|
|
332
|
+
const { enabled = true, staleTime = 10 * 60 * 1e3, gcTime = 15 * 60 * 1e3 } = options;
|
|
333
|
+
return useQuery({
|
|
334
|
+
queryKey: MEDIA_KEYS.folders(),
|
|
335
|
+
queryFn: () => mediaApi.getFolders({ token }),
|
|
336
|
+
enabled: enabled && !!token,
|
|
337
|
+
staleTime,
|
|
338
|
+
gcTime
|
|
339
|
+
});
|
|
340
|
+
}
|
|
341
|
+
function useMediaUpload(token) {
|
|
342
|
+
const queryClient = useQueryClient();
|
|
343
|
+
const toast = getToastHandler();
|
|
344
|
+
const mutation = useMutation({
|
|
345
|
+
mutationFn: async ({ files, folder }) => {
|
|
346
|
+
const response = await mediaApi.uploadMultiple({ token, files, folder });
|
|
347
|
+
return response.data;
|
|
348
|
+
},
|
|
349
|
+
onSuccess: () => {
|
|
350
|
+
queryClient.invalidateQueries({ queryKey: MEDIA_KEYS.lists() });
|
|
351
|
+
toast.success("Media uploaded successfully");
|
|
352
|
+
},
|
|
353
|
+
onError: (error) => {
|
|
354
|
+
toast.error(error.message || "Failed to upload media");
|
|
355
|
+
}
|
|
356
|
+
});
|
|
357
|
+
return {
|
|
358
|
+
upload: (params) => mutation.mutate(params),
|
|
359
|
+
uploadAsync: (params) => mutation.mutateAsync(params),
|
|
360
|
+
isUploading: mutation.isPending,
|
|
361
|
+
error: mutation.error
|
|
362
|
+
};
|
|
363
|
+
}
|
|
364
|
+
function useMediaBulkDelete(token) {
|
|
365
|
+
const queryClient = useQueryClient();
|
|
366
|
+
const toast = getToastHandler();
|
|
367
|
+
const mutation = useMutation({
|
|
368
|
+
mutationFn: async (ids) => {
|
|
369
|
+
const response = await mediaApi.bulkDelete({ token, ids });
|
|
370
|
+
return response.data;
|
|
371
|
+
},
|
|
372
|
+
onMutate: async (ids) => {
|
|
373
|
+
await queryClient.cancelQueries({ queryKey: MEDIA_KEYS.lists() });
|
|
374
|
+
const previousLists = queryClient.getQueriesData({ queryKey: MEDIA_KEYS.lists() });
|
|
375
|
+
queryClient.setQueriesData({ queryKey: MEDIA_KEYS.lists() }, (old) => {
|
|
376
|
+
const data = old;
|
|
377
|
+
if (!data?.docs) return old;
|
|
378
|
+
return {
|
|
379
|
+
...data,
|
|
380
|
+
docs: data.docs.filter((item) => !ids.includes(item._id)),
|
|
381
|
+
total: (data.total || 0) - ids.length
|
|
382
|
+
};
|
|
383
|
+
});
|
|
384
|
+
return { previousLists };
|
|
385
|
+
},
|
|
386
|
+
onError: (_error, _ids, context) => {
|
|
387
|
+
if (context?.previousLists) {
|
|
388
|
+
context.previousLists.forEach(([queryKey, data]) => {
|
|
389
|
+
queryClient.setQueryData(queryKey, data);
|
|
390
|
+
});
|
|
391
|
+
}
|
|
392
|
+
toast.error("Failed to delete media");
|
|
393
|
+
},
|
|
394
|
+
onSuccess: (data) => {
|
|
395
|
+
const deletedCount = data.success.length;
|
|
396
|
+
toast.success(`${deletedCount} item(s) deleted successfully`);
|
|
397
|
+
},
|
|
398
|
+
onSettled: () => {
|
|
399
|
+
queryClient.invalidateQueries({ queryKey: MEDIA_KEYS.lists() });
|
|
400
|
+
}
|
|
401
|
+
});
|
|
402
|
+
return {
|
|
403
|
+
bulkDelete: (ids) => mutation.mutate(ids),
|
|
404
|
+
bulkDeleteAsync: (ids) => mutation.mutateAsync(ids),
|
|
405
|
+
isDeleting: mutation.isPending,
|
|
406
|
+
error: mutation.error
|
|
407
|
+
};
|
|
408
|
+
}
|
|
409
|
+
function useMediaMove(token) {
|
|
410
|
+
const queryClient = useQueryClient();
|
|
411
|
+
const toast = getToastHandler();
|
|
412
|
+
const mutation = useMutation({
|
|
413
|
+
mutationFn: async (data) => {
|
|
414
|
+
const response = await mediaApi.moveToFolder({ token, data });
|
|
415
|
+
return response.data;
|
|
416
|
+
},
|
|
417
|
+
onSuccess: (data, variables) => {
|
|
418
|
+
queryClient.invalidateQueries({ queryKey: MEDIA_KEYS.lists() });
|
|
419
|
+
toast.success(`${data.modifiedCount} item(s) moved to ${variables.targetFolder}`);
|
|
420
|
+
},
|
|
421
|
+
onError: (error) => {
|
|
422
|
+
toast.error(error.message || "Failed to move media");
|
|
423
|
+
}
|
|
424
|
+
});
|
|
425
|
+
return {
|
|
426
|
+
move: (data) => mutation.mutate(data),
|
|
427
|
+
moveAsync: (data) => mutation.mutateAsync(data),
|
|
428
|
+
isMoving: mutation.isPending,
|
|
429
|
+
error: mutation.error
|
|
430
|
+
};
|
|
431
|
+
}
|
|
432
|
+
function useMediaUpdate(token) {
|
|
433
|
+
const queryClient = useQueryClient();
|
|
434
|
+
const toast = getToastHandler();
|
|
435
|
+
const mutation = useMutation({
|
|
436
|
+
mutationFn: async ({ id, data }) => {
|
|
437
|
+
const response = await mediaApi.update({ token, id, data });
|
|
438
|
+
return response.data;
|
|
439
|
+
},
|
|
440
|
+
onMutate: async ({ id, data }) => {
|
|
441
|
+
await queryClient.cancelQueries({ queryKey: MEDIA_KEYS.detail(id) });
|
|
442
|
+
const previous = queryClient.getQueryData(MEDIA_KEYS.detail(id));
|
|
443
|
+
queryClient.setQueryData(MEDIA_KEYS.detail(id), (old) => ({
|
|
444
|
+
...old,
|
|
445
|
+
...data
|
|
446
|
+
}));
|
|
447
|
+
return { previous, id };
|
|
448
|
+
},
|
|
449
|
+
onError: (_error, _variables, context) => {
|
|
450
|
+
if (context?.previous) {
|
|
451
|
+
queryClient.setQueryData(MEDIA_KEYS.detail(context.id), context.previous);
|
|
452
|
+
}
|
|
453
|
+
toast.error("Failed to update media");
|
|
454
|
+
},
|
|
455
|
+
onSuccess: () => {
|
|
456
|
+
toast.success("Media updated successfully");
|
|
457
|
+
},
|
|
458
|
+
onSettled: (_data, _error, variables) => {
|
|
459
|
+
queryClient.invalidateQueries({ queryKey: MEDIA_KEYS.detail(variables.id) });
|
|
460
|
+
queryClient.invalidateQueries({ queryKey: MEDIA_KEYS.lists() });
|
|
461
|
+
}
|
|
462
|
+
});
|
|
463
|
+
return {
|
|
464
|
+
update: (params) => mutation.mutate(params),
|
|
465
|
+
updateAsync: (params) => mutation.mutateAsync(params),
|
|
466
|
+
isUpdating: mutation.isPending,
|
|
467
|
+
error: mutation.error
|
|
468
|
+
};
|
|
469
|
+
}
|
|
470
|
+
function getMediaVariantUrl(media, variantName) {
|
|
471
|
+
return media.variants?.find((v) => v.name === variantName)?.url || media.url;
|
|
472
|
+
}
|
|
473
|
+
function getMediaThumbnailUrl(media) {
|
|
474
|
+
return getMediaVariantUrl(media, "thumbnail");
|
|
475
|
+
}
|
|
476
|
+
function getMediaMediumUrl(media) {
|
|
477
|
+
return getMediaVariantUrl(media, "medium");
|
|
478
|
+
}
|
|
479
|
+
var CMS_KEYS = {
|
|
480
|
+
all: ["cms"],
|
|
481
|
+
page: (slug) => [...CMS_KEYS.all, slug]
|
|
482
|
+
};
|
|
483
|
+
function useCMSPage(slug, options = {}) {
|
|
484
|
+
const { data, isLoading, isFetching, error, refetch } = useQuery({
|
|
485
|
+
queryKey: CMS_KEYS.page(slug),
|
|
486
|
+
queryFn: async () => {
|
|
487
|
+
const response = await getCmsPage({ slug });
|
|
488
|
+
return response.data;
|
|
489
|
+
},
|
|
490
|
+
enabled: !!slug && options.enabled !== false,
|
|
491
|
+
staleTime: options.staleTime ?? 5 * 60 * 1e3
|
|
492
|
+
// 5 minutes
|
|
493
|
+
});
|
|
494
|
+
return {
|
|
495
|
+
page: data || null,
|
|
496
|
+
isLoading,
|
|
497
|
+
isFetching,
|
|
498
|
+
error,
|
|
499
|
+
refetch
|
|
500
|
+
};
|
|
501
|
+
}
|
|
502
|
+
function useCMSUpdate(token) {
|
|
503
|
+
const queryClient = useQueryClient();
|
|
504
|
+
const toast = getToastHandler();
|
|
505
|
+
const mutation = useMutation({
|
|
506
|
+
mutationFn: async ({ slug, data }) => {
|
|
507
|
+
const response = await updateCmsPage({ slug, token, data });
|
|
508
|
+
return response.data;
|
|
509
|
+
},
|
|
510
|
+
onSuccess: (_, { slug }) => {
|
|
511
|
+
queryClient.invalidateQueries({ queryKey: CMS_KEYS.page(slug) });
|
|
512
|
+
toast.success("Page saved successfully");
|
|
513
|
+
if (typeof window !== "undefined") {
|
|
514
|
+
fetch("/revalidate", {
|
|
515
|
+
method: "POST",
|
|
516
|
+
headers: { "Content-Type": "application/json" },
|
|
517
|
+
body: JSON.stringify({ slug, type: "both" })
|
|
518
|
+
}).catch(() => {
|
|
519
|
+
});
|
|
520
|
+
}
|
|
521
|
+
},
|
|
522
|
+
onError: (error) => {
|
|
523
|
+
toast.error(error.message || "Failed to save page");
|
|
524
|
+
}
|
|
525
|
+
});
|
|
526
|
+
return {
|
|
527
|
+
updatePage: mutation.mutateAsync,
|
|
528
|
+
isUpdating: mutation.isPending
|
|
529
|
+
};
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
export { CMS_KEYS, MEDIA_KEYS, MediaApi, getCmsPage, getMediaMediumUrl, getMediaThumbnailUrl, getMediaVariantUrl, mediaApi, updateCmsPage, useCMSPage, useCMSUpdate, useMediaBulkDelete, useMediaDetail, useMediaFolders, useMediaList, useMediaMove, useMediaUpdate, useMediaUpload };
|
|
533
|
+
//# sourceMappingURL=chunk-ERQ52WHY.js.map
|
|
534
|
+
//# sourceMappingURL=chunk-ERQ52WHY.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/content/api/cms.ts","../src/content/api/media.ts","../src/content/hooks/media.ts","../src/content/hooks/cms.ts"],"names":["useQuery","useQueryClient","useMutation"],"mappings":"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","file":"chunk-ERQ52WHY.js","sourcesContent":["/**\r\n * CMS API - Slug-based content management\r\n *\r\n * Two endpoints only:\r\n * - GET /api/v1/cms/:slug - Fetch page (public)\r\n * - PATCH /api/v1/cms/:slug - Update page (admin, auto-creates if missing)\r\n */\r\n\r\nimport { handleApiRequest } from \"../../core/api-handler\";\r\nimport type { ApiResponse, RequestOptions } from \"../../core/api-factory\";\r\nimport type { CMSPage, CMSPagePayload } from \"../types/cms\";\r\n\r\nconst CMS_BASE = \"/api/v1/cms\";\r\n\r\n/**\r\n * Get CMS page by slug (public)\r\n * Returns page data or null if not found\r\n * Gracefully handles 404 errors by returning null instead of throwing\r\n */\r\nexport async function getCmsPage({\r\n slug,\r\n options = {},\r\n}: {\r\n slug: string;\r\n options?: RequestOptions;\r\n}): Promise<ApiResponse<CMSPage | null>> {\r\n if (!slug) throw new Error(\"Slug is required\");\r\n\r\n try {\r\n return await handleApiRequest<ApiResponse<CMSPage | null>>(\r\n \"GET\",\r\n `${CMS_BASE}/${slug}`,\r\n options\r\n );\r\n } catch (error) {\r\n // If document not found (404), return null data gracefully\r\n if (error instanceof Error && error.message.includes(\"Document not found\")) {\r\n return {\r\n success: false,\r\n data: null,\r\n message: \"CMS page not found, using static fallback\",\r\n };\r\n }\r\n // Re-throw other errors (network issues, server errors, etc.)\r\n throw error;\r\n }\r\n}\r\n\r\n/**\r\n * Update CMS page by slug (admin)\r\n * Backend auto-creates if page doesn't exist\r\n */\r\nexport async function updateCmsPage({\r\n slug,\r\n token,\r\n data,\r\n options = {},\r\n}: {\r\n slug: string;\r\n token: string;\r\n data: CMSPagePayload;\r\n options?: RequestOptions;\r\n}): Promise<ApiResponse<CMSPage>> {\r\n if (!slug) throw new Error(\"Slug is required\");\r\n\r\n return handleApiRequest<ApiResponse<CMSPage>>(\r\n \"PATCH\",\r\n `${CMS_BASE}/${slug}`,\r\n { token, body: data, ...options }\r\n );\r\n}\r\n","// Media API - Image upload and management\r\nimport {\r\n type ApiResponse,\r\n type PaginatedResponse,\r\n type RequestOptions,\r\n type DeleteResponse,\r\n} from \"../../core/api-factory\";\r\nimport { handleApiRequest } from \"../../core/api-handler\";\r\nimport type {\r\n Media,\r\n MediaFolder,\r\n MediaQueryParams,\r\n UpdateMediaPayload,\r\n BulkDeleteResult,\r\n MoveFilesPayload,\r\n MoveFilesResult,\r\n} from \"../types/media\";\r\n\r\ntype FetchOptions = Omit<RequestOptions, \"token\" | \"organizationId\">;\r\n\r\n/**\r\n * Media API - Image upload and management\r\n *\r\n * Endpoints:\r\n * - POST /api/media/upload (single file upload)\r\n * - POST /api/media/upload-multiple (multiple files)\r\n * - GET /api/media (list with filters)\r\n * - GET /api/media/:id (get single)\r\n * - PATCH /api/media/:id (update alt/title)\r\n * - DELETE /api/media/:id (delete single)\r\n * - GET /api/media/folders (get allowed folders)\r\n *\r\n * Features:\r\n * - Auto WebP conversion\r\n * - Auto variants: thumbnail (150x200), medium (600x800)\r\n * - Max size: 50MB\r\n * - S3 storage with CDN\r\n *\r\n * Usage Examples:\r\n * - mediaApi.upload({ token: 'xxx', file, folder: 'products' })\r\n * - mediaApi.getAll({ params: { folder: 'products', limit: 20 }})\r\n * - mediaApi.update({ token: 'xxx', id: '123', data: { alt: 'New alt' }})\r\n * - mediaApi.delete({ token: 'xxx', id: '123' })\r\n */\r\nclass MediaApi {\r\n private readonly baseUrl: string;\r\n private readonly defaultCache: RequestCache;\r\n\r\n constructor(config: { basePath?: string; cache?: RequestCache } = {}) {\r\n this.baseUrl = `${config.basePath || \"/api/v1\"}/media`;\r\n this.defaultCache = config.cache || \"no-store\";\r\n }\r\n\r\n /**\r\n * Upload single file\r\n * POST /api/media/upload\r\n *\r\n * @example\r\n * const file = event.target.files[0];\r\n * const result = await mediaApi.upload({\r\n * token: 'xxx',\r\n * file,\r\n * folder: 'products',\r\n * alt: 'Product image'\r\n * });\r\n */\r\n async upload({\r\n token,\r\n file,\r\n folder,\r\n alt,\r\n title,\r\n options = {},\r\n }: {\r\n token: string;\r\n file: File | Blob;\r\n folder?: MediaFolder;\r\n alt?: string;\r\n title?: string;\r\n options?: FetchOptions;\r\n }): Promise<ApiResponse<Media>> {\r\n const formData = new FormData();\r\n // Fields MUST come before files for Fastify multipart streaming parser\r\n if (folder) formData.append(\"folder\", folder);\r\n if (alt) formData.append(\"alt\", alt);\r\n if (title) formData.append(\"title\", title);\r\n formData.append(\"file\", file);\r\n\r\n return handleApiRequest(\"POST\", `${this.baseUrl}/upload`, {\r\n token,\r\n body: formData,\r\n cache: this.defaultCache,\r\n ...options,\r\n });\r\n }\r\n\r\n /**\r\n * Upload multiple files (max 20)\r\n * POST /api/media/upload-multiple\r\n *\r\n * @example\r\n * const files = Array.from(event.target.files);\r\n * const result = await mediaApi.uploadMultiple({\r\n * token: 'xxx',\r\n * files,\r\n * folder: 'products'\r\n * });\r\n */\r\n async uploadMultiple({\r\n token,\r\n files,\r\n folder,\r\n options = {},\r\n }: {\r\n token: string;\r\n files: (File | Blob)[];\r\n folder?: MediaFolder;\r\n options?: FetchOptions;\r\n }): Promise<ApiResponse<Media[]>> {\r\n const formData = new FormData();\r\n // Fields MUST come before files for Fastify multipart streaming parser\r\n if (folder) formData.append(\"folder\", folder);\r\n files.forEach((file) => formData.append(\"files[]\", file));\r\n\r\n return handleApiRequest(\"POST\", `${this.baseUrl}/upload-multiple`, {\r\n token,\r\n body: formData,\r\n cache: this.defaultCache,\r\n ...options,\r\n });\r\n }\r\n\r\n /**\r\n * Get all media with filtering\r\n * GET /api/media\r\n *\r\n * @example\r\n * const result = await mediaApi.getAll({\r\n * token: 'xxx',\r\n * params: {\r\n * folder: 'products',\r\n * search: 'shirt',\r\n * limit: 20,\r\n * sort: '-createdAt'\r\n * }\r\n * });\r\n */\r\n async getAll({\r\n token,\r\n params = {},\r\n options = {},\r\n }: {\r\n token: string;\r\n params?: MediaQueryParams;\r\n options?: FetchOptions;\r\n } = {} as any): Promise<PaginatedResponse<Media>> {\r\n // Filter out undefined/null values before creating query string\r\n const cleanParams = Object.fromEntries(\r\n Object.entries(params).filter(([, v]) => v !== undefined && v !== null)\r\n );\r\n const queryString = new URLSearchParams(\r\n cleanParams as Record<string, string>\r\n ).toString();\r\n const url = queryString ? `${this.baseUrl}?${queryString}` : this.baseUrl;\r\n\r\n return handleApiRequest(\"GET\", url, {\r\n token,\r\n cache: this.defaultCache,\r\n ...options,\r\n });\r\n }\r\n\r\n /**\r\n * Get single media by ID\r\n * GET /api/media/:id\r\n *\r\n * @example\r\n * const result = await mediaApi.getById({ token: 'xxx', id: '123abc' });\r\n */\r\n async getById({\r\n token,\r\n id,\r\n options = {},\r\n }: {\r\n token: string;\r\n id: string;\r\n options?: FetchOptions;\r\n }): Promise<ApiResponse<Media>> {\r\n if (!id) throw new Error(\"ID is required\");\r\n\r\n return handleApiRequest(\"GET\", `${this.baseUrl}/${id}`, {\r\n token,\r\n cache: this.defaultCache,\r\n ...options,\r\n });\r\n }\r\n\r\n /**\r\n * Update media metadata (alt, title)\r\n * PATCH /api/media/:id\r\n *\r\n * @example\r\n * await mediaApi.update({\r\n * token: 'xxx',\r\n * id: '123',\r\n * data: { alt: 'New alt text', title: 'New title' }\r\n * });\r\n */\r\n async update({\r\n token,\r\n id,\r\n data,\r\n options = {},\r\n }: {\r\n token: string;\r\n id: string;\r\n data: UpdateMediaPayload;\r\n options?: FetchOptions;\r\n }): Promise<ApiResponse<Media>> {\r\n if (!id) throw new Error(\"ID is required\");\r\n\r\n return handleApiRequest(\"PATCH\", `${this.baseUrl}/${id}`, {\r\n token,\r\n body: data,\r\n cache: this.defaultCache,\r\n ...options,\r\n });\r\n }\r\n\r\n /**\r\n * Delete single media\r\n * DELETE /api/media/:id\r\n *\r\n * @example\r\n * await mediaApi.delete({ token: 'xxx', id: '123' });\r\n */\r\n async delete({\r\n token,\r\n id,\r\n options = {},\r\n }: {\r\n token: string;\r\n id: string;\r\n options?: FetchOptions;\r\n }): Promise<DeleteResponse> {\r\n if (!id) throw new Error(\"ID is required\");\r\n\r\n return handleApiRequest(\"DELETE\", `${this.baseUrl}/${id}`, {\r\n token,\r\n cache: this.defaultCache,\r\n ...options,\r\n });\r\n }\r\n\r\n /**\r\n * Bulk delete multiple files\r\n * POST /api/media/bulk-delete\r\n *\r\n * @example\r\n * await mediaApi.bulkDelete({\r\n * token: 'xxx',\r\n * ids: ['123', '456', '789']\r\n * });\r\n */\r\n async bulkDelete({\r\n token,\r\n ids,\r\n options = {},\r\n }: {\r\n token: string;\r\n ids: string[];\r\n options?: FetchOptions;\r\n }): Promise<ApiResponse<BulkDeleteResult>> {\r\n if (!ids.length) throw new Error(\"IDs array is required\");\r\n\r\n return handleApiRequest(\"POST\", `${this.baseUrl}/bulk-delete`, {\r\n token,\r\n body: { ids },\r\n cache: this.defaultCache,\r\n ...options,\r\n });\r\n }\r\n\r\n /**\r\n * Move files to different folder\r\n * POST /api/media/move\r\n *\r\n * @example\r\n * await mediaApi.moveToFolder({\r\n * token: 'xxx',\r\n * ids: ['123', '456'],\r\n * targetFolder: 'banners'\r\n * });\r\n */\r\n async moveToFolder({\r\n token,\r\n data,\r\n options = {},\r\n }: {\r\n token: string;\r\n data: MoveFilesPayload;\r\n options?: FetchOptions;\r\n }): Promise<ApiResponse<MoveFilesResult>> {\r\n if (!data.ids.length) throw new Error(\"IDs array is required\");\r\n if (!data.targetFolder) throw new Error(\"Target folder is required\");\r\n\r\n return handleApiRequest(\"POST\", `${this.baseUrl}/move`, {\r\n token,\r\n body: data,\r\n cache: this.defaultCache,\r\n ...options,\r\n });\r\n }\r\n\r\n /**\r\n * Get allowed folders\r\n * GET /api/media/folders\r\n *\r\n * @example\r\n * const result = await mediaApi.getFolders({ token: 'xxx' });\r\n * // result.data = ['general', 'products', 'categories', ...]\r\n */\r\n async getFolders({\r\n token,\r\n options = {},\r\n }: {\r\n token: string;\r\n options?: FetchOptions;\r\n }): Promise<ApiResponse<MediaFolder[]>> {\r\n return handleApiRequest(\"GET\", `${this.baseUrl}/folders`, {\r\n token,\r\n cache: this.defaultCache,\r\n ...options,\r\n });\r\n }\r\n\r\n /**\r\n * Helper: Get variant URL by name\r\n * Falls back to original URL if variant not found\r\n *\r\n * @example\r\n * const thumbnailUrl = mediaApi.getVariantUrl(media, 'thumbnail');\r\n * <img src={thumbnailUrl} />\r\n */\r\n getVariantUrl(media: Media, variantName: string): string {\r\n return (\r\n media.variants?.find((v) => v.name === variantName)?.url || media.url\r\n );\r\n }\r\n\r\n /**\r\n * Helper: Get thumbnail URL\r\n */\r\n getThumbnailUrl(media: Media): string {\r\n return this.getVariantUrl(media, \"thumbnail\");\r\n }\r\n\r\n /**\r\n * Helper: Get medium URL\r\n */\r\n getMediumUrl(media: Media): string {\r\n return this.getVariantUrl(media, \"medium\");\r\n }\r\n\r\n}\r\n\r\n// Create and export a singleton instance\r\nexport const mediaApi = new MediaApi();\r\nexport { MediaApi };\r\n","\"use client\";\n\nimport { useQuery, useMutation, useQueryClient } from \"@tanstack/react-query\";\nimport { mediaApi } from \"../api/media\";\nimport { getToastHandler } from \"../../core/react/mutation.factory\";\nimport type {\n Media,\n MediaFolder,\n MediaQueryParams,\n UpdateMediaPayload,\n BulkDeleteResult,\n MoveFilesPayload,\n MoveFilesResult,\n} from \"../types/media\";\n\n// ============================================\n// Query Keys\n// ============================================\n\nexport const MEDIA_KEYS = {\n all: [\"media\"] as const,\n lists: () => [...MEDIA_KEYS.all, \"list\"] as const,\n list: (params: MediaQueryParams) => [...MEDIA_KEYS.lists(), params] as const,\n details: () => [...MEDIA_KEYS.all, \"detail\"] as const,\n detail: (id: string) => [...MEDIA_KEYS.details(), id] as const,\n folders: () => [...MEDIA_KEYS.all, \"folders\"] as const,\n};\n\n// ============================================\n// Types\n// ============================================\n\nexport interface UseMediaListOptions {\n enabled?: boolean;\n staleTime?: number;\n gcTime?: number;\n refetchOnWindowFocus?: boolean;\n}\n\nexport interface UseMediaUploadReturn {\n upload: (params: { files: (File | Blob)[]; folder?: MediaFolder }) => void;\n uploadAsync: (params: { files: (File | Blob)[]; folder?: MediaFolder }) => Promise<Media[]>;\n isUploading: boolean;\n error: Error | null;\n}\n\nexport interface UseMediaBulkDeleteReturn {\n bulkDelete: (ids: string[]) => void;\n bulkDeleteAsync: (ids: string[]) => Promise<BulkDeleteResult>;\n isDeleting: boolean;\n error: Error | null;\n}\n\nexport interface UseMediaMoveReturn {\n move: (data: MoveFilesPayload) => void;\n moveAsync: (data: MoveFilesPayload) => Promise<MoveFilesResult>;\n isMoving: boolean;\n error: Error | null;\n}\n\nexport interface UseMediaUpdateReturn {\n update: (params: { id: string; data: UpdateMediaPayload }) => void;\n updateAsync: (params: { id: string; data: UpdateMediaPayload }) => Promise<Media>;\n isUpdating: boolean;\n error: Error | null;\n}\n\n// ============================================\n// Query Hooks\n// ============================================\n\n/**\n * Hook to get media list with folder filtering\n *\n * @param token - Auth token (required)\n * @param params - Query params (folder, search, limit, etc.)\n * @param options - React Query options\n *\n * @example\n * ```tsx\n * function MediaLibrary() {\n * const { data, isLoading } = useMediaList(token, {\n * folder: 'products',\n * limit: 20,\n * });\n *\n * if (isLoading) return <Spinner />;\n *\n * return (\n * <div className=\"grid\">\n * {data?.docs?.map(media => (\n * <img key={media._id} src={media.variants?.thumbnail?.url || media.url} />\n * ))}\n * </div>\n * );\n * }\n * ```\n */\nexport function useMediaList(\n token: string | null,\n params: MediaQueryParams = {},\n options: UseMediaListOptions = {}\n) {\n const {\n enabled = true,\n staleTime = 2 * 60 * 1000,\n gcTime = 5 * 60 * 1000,\n refetchOnWindowFocus = false,\n } = options;\n\n return useQuery({\n queryKey: MEDIA_KEYS.list(params),\n queryFn: () => mediaApi.getAll({ token: token!, params }),\n enabled: enabled && !!token,\n staleTime,\n gcTime,\n refetchOnWindowFocus,\n });\n}\n\n/**\n * Hook to get single media by ID\n *\n * @param token - Auth token (required)\n * @param id - Media ID\n * @param options - React Query options\n *\n * @example\n * ```tsx\n * function MediaDetail({ id }) {\n * const { data, isLoading } = useMediaDetail(token, id);\n *\n * if (isLoading) return <Spinner />;\n *\n * return (\n * <div>\n * <img src={data?.data?.url} alt={data?.data?.alt} />\n * <p>{data?.data?.title}</p>\n * </div>\n * );\n * }\n * ```\n */\nexport function useMediaDetail(\n token: string | null,\n id: string | null,\n options: UseMediaListOptions = {}\n) {\n const { enabled = true, staleTime = 2 * 60 * 1000, gcTime = 5 * 60 * 1000 } = options;\n\n return useQuery({\n queryKey: MEDIA_KEYS.detail(id!),\n queryFn: () => mediaApi.getById({ token: token!, id: id! }),\n enabled: enabled && !!token && !!id,\n staleTime,\n gcTime,\n });\n}\n\n/**\n * Hook to get allowed folders\n *\n * @param token - Auth token (required)\n * @param options - React Query options\n *\n * @example\n * ```tsx\n * function FolderSelector() {\n * const { data, isLoading } = useMediaFolders(token);\n *\n * return (\n * <Select>\n * {data?.data?.map(folder => (\n * <SelectItem key={folder} value={folder}>{folder}</SelectItem>\n * ))}\n * </Select>\n * );\n * }\n * ```\n */\nexport function useMediaFolders(token: string | null, options: UseMediaListOptions = {}) {\n const { enabled = true, staleTime = 10 * 60 * 1000, gcTime = 15 * 60 * 1000 } = options;\n\n return useQuery({\n queryKey: MEDIA_KEYS.folders(),\n queryFn: () => mediaApi.getFolders({ token: token! }),\n enabled: enabled && !!token,\n staleTime,\n gcTime,\n });\n}\n\n// ============================================\n// Mutation Hooks\n// ============================================\n\n/**\n * Hook to upload multiple media files\n *\n * @param token - Auth token (required)\n *\n * @example\n * ```tsx\n * function MediaUploader() {\n * const { upload, isUploading } = useMediaUpload(token);\n *\n * const handleFiles = (e) => {\n * const files = Array.from(e.target.files);\n * upload({ files, folder: 'products' });\n * };\n *\n * return (\n * <input type=\"file\" multiple onChange={handleFiles} disabled={isUploading} />\n * );\n * }\n * ```\n */\nexport function useMediaUpload(token: string): UseMediaUploadReturn {\n const queryClient = useQueryClient();\n const toast = getToastHandler();\n\n const mutation = useMutation({\n mutationFn: async ({ files, folder }: { files: (File | Blob)[]; folder?: MediaFolder }) => {\n const response = await mediaApi.uploadMultiple({ token, files, folder });\n return response.data as Media[];\n },\n onSuccess: () => {\n queryClient.invalidateQueries({ queryKey: MEDIA_KEYS.lists() });\n toast.success(\"Media uploaded successfully\");\n },\n onError: (error: Error) => {\n toast.error(error.message || \"Failed to upload media\");\n },\n });\n\n return {\n upload: (params) => mutation.mutate(params),\n uploadAsync: (params) => mutation.mutateAsync(params),\n isUploading: mutation.isPending,\n error: mutation.error,\n };\n}\n\n/**\n * Hook to bulk delete media with optimistic updates\n *\n * @param token - Auth token (required)\n *\n * @example\n * ```tsx\n * function MediaManager({ selectedIds }) {\n * const { bulkDelete, isDeleting } = useMediaBulkDelete(token);\n *\n * return (\n * <Button\n * variant=\"destructive\"\n * onClick={() => bulkDelete(selectedIds)}\n * disabled={isDeleting}\n * >\n * Delete Selected\n * </Button>\n * );\n * }\n * ```\n */\nexport function useMediaBulkDelete(token: string): UseMediaBulkDeleteReturn {\n const queryClient = useQueryClient();\n const toast = getToastHandler();\n\n const mutation = useMutation<\n BulkDeleteResult,\n Error,\n string[],\n { previousLists: [unknown, unknown][] }\n >({\n mutationFn: async (ids: string[]) => {\n const response = await mediaApi.bulkDelete({ token, ids });\n return response.data as BulkDeleteResult;\n },\n onMutate: async (ids) => {\n await queryClient.cancelQueries({ queryKey: MEDIA_KEYS.lists() });\n\n const previousLists = queryClient.getQueriesData({ queryKey: MEDIA_KEYS.lists() });\n\n // Optimistically update cache\n queryClient.setQueriesData({ queryKey: MEDIA_KEYS.lists() }, (old: unknown) => {\n const data = old as { docs?: Media[]; total?: number } | undefined;\n if (!data?.docs) return old;\n return {\n ...data,\n docs: data.docs.filter((item: Media) => !ids.includes(item._id)),\n total: (data.total || 0) - ids.length,\n };\n });\n\n return { previousLists: previousLists as [unknown, unknown][] };\n },\n onError: (_error, _ids, context) => {\n if (context?.previousLists) {\n context.previousLists.forEach(([queryKey, data]) => {\n queryClient.setQueryData(queryKey as unknown[], data);\n });\n }\n toast.error(\"Failed to delete media\");\n },\n onSuccess: (data) => {\n const deletedCount = data.success.length;\n toast.success(`${deletedCount} item(s) deleted successfully`);\n },\n onSettled: () => {\n queryClient.invalidateQueries({ queryKey: MEDIA_KEYS.lists() });\n },\n });\n\n return {\n bulkDelete: (ids) => mutation.mutate(ids),\n bulkDeleteAsync: (ids) => mutation.mutateAsync(ids),\n isDeleting: mutation.isPending,\n error: mutation.error,\n };\n}\n\n/**\n * Hook to move media to different folder\n *\n * @param token - Auth token (required)\n *\n * @example\n * ```tsx\n * function MoveDialog({ selectedIds }) {\n * const { move, isMoving } = useMediaMove(token);\n *\n * const handleMove = (folder) => {\n * move({ ids: selectedIds, targetFolder: folder });\n * };\n *\n * return <FolderSelect onSelect={handleMove} disabled={isMoving} />;\n * }\n * ```\n */\nexport function useMediaMove(token: string): UseMediaMoveReturn {\n const queryClient = useQueryClient();\n const toast = getToastHandler();\n\n const mutation = useMutation<MoveFilesResult, Error, MoveFilesPayload>({\n mutationFn: async (data: MoveFilesPayload) => {\n const response = await mediaApi.moveToFolder({ token, data });\n return response.data as MoveFilesResult;\n },\n onSuccess: (data, variables) => {\n queryClient.invalidateQueries({ queryKey: MEDIA_KEYS.lists() });\n toast.success(`${data.modifiedCount} item(s) moved to ${variables.targetFolder}`);\n },\n onError: (error: Error) => {\n toast.error(error.message || \"Failed to move media\");\n },\n });\n\n return {\n move: (data) => mutation.mutate(data),\n moveAsync: (data) => mutation.mutateAsync(data),\n isMoving: mutation.isPending,\n error: mutation.error,\n };\n}\n\n/**\n * Hook to update media metadata\n *\n * @param token - Auth token (required)\n *\n * @example\n * ```tsx\n * function MediaEdit({ media }) {\n * const { update, isUpdating } = useMediaUpdate(token);\n * const [alt, setAlt] = useState(media.alt);\n *\n * const handleSave = () => {\n * update({ id: media._id, data: { alt } });\n * };\n *\n * return (\n * <>\n * <Input value={alt} onChange={e => setAlt(e.target.value)} />\n * <Button onClick={handleSave} disabled={isUpdating}>Save</Button>\n * </>\n * );\n * }\n * ```\n */\nexport function useMediaUpdate(token: string): UseMediaUpdateReturn {\n const queryClient = useQueryClient();\n const toast = getToastHandler();\n\n const mutation = useMutation({\n mutationFn: async ({ id, data }: { id: string; data: UpdateMediaPayload }) => {\n const response = await mediaApi.update({ token, id, data });\n return response.data as Media;\n },\n onMutate: async ({ id, data }) => {\n await queryClient.cancelQueries({ queryKey: MEDIA_KEYS.detail(id) });\n\n const previous = queryClient.getQueryData(MEDIA_KEYS.detail(id));\n\n queryClient.setQueryData(MEDIA_KEYS.detail(id), (old: unknown) => ({\n ...(old as object),\n ...data,\n }));\n\n return { previous, id };\n },\n onError: (_error, _variables, context) => {\n if (context?.previous) {\n queryClient.setQueryData(MEDIA_KEYS.detail(context.id), context.previous);\n }\n toast.error(\"Failed to update media\");\n },\n onSuccess: () => {\n toast.success(\"Media updated successfully\");\n },\n onSettled: (_data, _error, variables) => {\n queryClient.invalidateQueries({ queryKey: MEDIA_KEYS.detail(variables.id) });\n queryClient.invalidateQueries({ queryKey: MEDIA_KEYS.lists() });\n },\n });\n\n return {\n update: (params) => mutation.mutate(params),\n updateAsync: (params) => mutation.mutateAsync(params),\n isUpdating: mutation.isPending,\n error: mutation.error,\n };\n}\n\n// ============================================\n// Helper Functions (re-exported from API)\n// ============================================\n\n/**\n * Get variant URL by name, falls back to original URL\n */\nexport function getMediaVariantUrl(media: Media, variantName: string): string {\n return media.variants?.find((v) => v.name === variantName)?.url || media.url;\n}\n\n/**\n * Get thumbnail URL\n */\nexport function getMediaThumbnailUrl(media: Media): string {\n return getMediaVariantUrl(media, \"thumbnail\");\n}\n\n/**\n * Get medium URL\n */\nexport function getMediaMediumUrl(media: Media): string {\n return getMediaVariantUrl(media, \"medium\");\n}\n","\"use client\";\r\n\r\n/**\r\n * CMS Hooks\r\n *\r\n * React hooks for CMS page operations.\r\n */\r\n\r\nimport { useQuery, useMutation, useQueryClient } from \"@tanstack/react-query\";\r\nimport { getCmsPage, updateCmsPage } from \"../api/cms\";\r\nimport { getToastHandler } from \"../../core/react/mutation.factory\";\r\nimport type { CMSPage, CMSPagePayload } from \"../types/cms\";\r\n\r\n// ============================================\r\n// Query Keys\r\n// ============================================\r\n\r\nexport const CMS_KEYS = {\r\n all: [\"cms\"] as const,\r\n page: (slug: string) => [...CMS_KEYS.all, slug] as const,\r\n};\r\n\r\n// ============================================\r\n// Types\r\n// ============================================\r\n\r\ninterface QueryOptions {\r\n enabled?: boolean;\r\n staleTime?: number;\r\n}\r\n\r\nexport interface UseCMSPageReturn {\r\n page: CMSPage | null;\r\n isLoading: boolean;\r\n isFetching: boolean;\r\n error: Error | null;\r\n refetch: () => void;\r\n}\r\n\r\nexport interface UseCMSUpdateReturn {\r\n updatePage: (params: { slug: string; data: CMSPagePayload }) => Promise<CMSPage>;\r\n isUpdating: boolean;\r\n}\r\n\r\n// ============================================\r\n// Hooks\r\n// ============================================\r\n\r\n/**\r\n * Fetch CMS page by slug\r\n *\r\n * @param slug - Page slug (e.g., 'about', 'terms', 'privacy')\r\n * @param options - Query options\r\n *\r\n * @example\r\n * ```tsx\r\n * function AboutPage() {\r\n * const { page, isLoading } = useCMSPage('about');\r\n *\r\n * if (isLoading) return <Skeleton />;\r\n * if (!page) return <StaticAboutPage />;\r\n *\r\n * return (\r\n * <article>\r\n * <h1>{page.title}</h1>\r\n * <div dangerouslySetInnerHTML={{ __html: page.content }} />\r\n * </article>\r\n * );\r\n * }\r\n * ```\r\n */\r\nexport function useCMSPage(\r\n slug: string,\r\n options: QueryOptions = {}\r\n): UseCMSPageReturn {\r\n const { data, isLoading, isFetching, error, refetch } = useQuery({\r\n queryKey: CMS_KEYS.page(slug),\r\n queryFn: async () => {\r\n const response = await getCmsPage({ slug });\r\n return response.data;\r\n },\r\n enabled: !!slug && options.enabled !== false,\r\n staleTime: options.staleTime ?? 5 * 60 * 1000, // 5 minutes\r\n });\r\n\r\n return {\r\n page: data || null,\r\n isLoading,\r\n isFetching,\r\n error: error as Error | null,\r\n refetch,\r\n };\r\n}\r\n\r\n/**\r\n * Update CMS page (admin)\r\n * Backend auto-creates if page doesn't exist\r\n *\r\n * @param token - Admin token\r\n *\r\n * @example\r\n * ```tsx\r\n * function CMSEditor({ slug }) {\r\n * const { page } = useCMSPage(slug);\r\n * const { updatePage, isUpdating } = useCMSUpdate(token);\r\n *\r\n * const handleSave = async (content: string) => {\r\n * await updatePage({\r\n * slug,\r\n * data: { content, title: page?.title },\r\n * });\r\n * };\r\n *\r\n * return (\r\n * <Editor\r\n * content={page?.content || ''}\r\n * onSave={handleSave}\r\n * isSaving={isUpdating}\r\n * />\r\n * );\r\n * }\r\n * ```\r\n */\r\nexport function useCMSUpdate(token: string): UseCMSUpdateReturn {\r\n const queryClient = useQueryClient();\r\n const toast = getToastHandler();\r\n\r\n const mutation = useMutation({\r\n mutationFn: async ({ slug, data }: { slug: string; data: CMSPagePayload }) => {\r\n const response = await updateCmsPage({ slug, token, data });\r\n return response.data as CMSPage;\r\n },\r\n onSuccess: (_, { slug }) => {\r\n queryClient.invalidateQueries({ queryKey: CMS_KEYS.page(slug) });\r\n toast.success(\"Page saved successfully\");\r\n\r\n // Trigger ISR revalidation (optional)\r\n if (typeof window !== \"undefined\") {\r\n fetch(\"/revalidate\", {\r\n method: \"POST\",\r\n headers: { \"Content-Type\": \"application/json\" },\r\n body: JSON.stringify({ slug, type: \"both\" }),\r\n }).catch(() => {\r\n // Silently ignore revalidation errors\r\n });\r\n }\r\n },\r\n onError: (error: Error) => {\r\n toast.error(error.message || \"Failed to save page\");\r\n },\r\n });\r\n\r\n return {\r\n updatePage: mutation.mutateAsync,\r\n isUpdating: mutation.isPending,\r\n };\r\n}\r\n"]}
|