admin_lsac 2.0.30 → 2.0.32
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/README.md +3 -394
- package/dist/esm/src/components/AdminPage.js +14 -1
- package/dist/esm/src/testSchemas/course.js +22 -6
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -24,6 +24,8 @@ If your project is TypeScript-based, you need to use the line from below as well
|
|
|
24
24
|
|
|
25
25
|
## How to add routes to the admin panel's pages?
|
|
26
26
|
|
|
27
|
+
You will need to create an array of schemas (if more than one schema) for your own admin panel.
|
|
28
|
+
|
|
27
29
|
```
|
|
28
30
|
{your_array.map((page, index) => (
|
|
29
31
|
<Route
|
|
@@ -54,8 +56,6 @@ If your project is TypeScript-based, you need to use the line from below as well
|
|
|
54
56
|
) : null}
|
|
55
57
|
```
|
|
56
58
|
|
|
57
|
-
You will need to create an array of schemas for your own admin panel.
|
|
58
|
-
|
|
59
59
|
## What is a schema?
|
|
60
60
|
|
|
61
61
|
```
|
|
@@ -142,397 +142,6 @@ The operations for the rowActions and actions will generally (not mandatory) hav
|
|
|
142
142
|
- arrayField = arrays of data
|
|
143
143
|
- objectField = an object containing more fields/inputs, or even other objects (some cool recursion)
|
|
144
144
|
|
|
145
|
-
#### Schema example
|
|
146
|
-
|
|
147
|
-
```
|
|
148
|
-
import React from 'react';
|
|
149
|
-
import axios from 'axios';
|
|
150
|
-
import * as Yup from 'yup';
|
|
151
|
-
import { PageSchema } from '../schema';
|
|
152
|
-
|
|
153
|
-
const domain = 'http://localhost:3001';
|
|
154
|
-
|
|
155
|
-
// Please try to modularize the code for your schemas though
|
|
156
|
-
|
|
157
|
-
export const coursesPage: PageSchema = {
|
|
158
|
-
name: 'Courses',
|
|
159
|
-
|
|
160
|
-
// 1. Data retrieval: fetch all courses from backend
|
|
161
|
-
getRequest: async () => {
|
|
162
|
-
const res = await axios.get(`${domain}/courses`);
|
|
163
|
-
return res.data; // returns an array of course objects
|
|
164
|
-
},
|
|
165
|
-
|
|
166
|
-
// 2. Table fields: define columns for the courses table
|
|
167
|
-
tableFields: [
|
|
168
|
-
{
|
|
169
|
-
name: 'Course ID',
|
|
170
|
-
// The ID should be first column to allow rowActions to reference it (if rowActions needed)
|
|
171
|
-
access: (obj: any) => obj.id,
|
|
172
|
-
},
|
|
173
|
-
{
|
|
174
|
-
name: 'Name',
|
|
175
|
-
access: (obj: any) => obj.name,
|
|
176
|
-
},
|
|
177
|
-
{
|
|
178
|
-
name: 'Level',
|
|
179
|
-
access: (obj: any) => obj.level,
|
|
180
|
-
},
|
|
181
|
-
{
|
|
182
|
-
name: 'Credits',
|
|
183
|
-
access: (obj: any) => obj.credits,
|
|
184
|
-
},
|
|
185
|
-
{
|
|
186
|
-
name: 'Start Date',
|
|
187
|
-
access: (obj: any) => (obj.startDate ? new Date(obj.startDate).toLocaleDateString() : 'N/A'),
|
|
188
|
-
},
|
|
189
|
-
{
|
|
190
|
-
name: 'Instructor',
|
|
191
|
-
// Display instructor name (assuming obj.instructor has { name, email })
|
|
192
|
-
access: (obj: any) => (obj.instructor ? obj.instructor.name : 'N/A'),
|
|
193
|
-
},
|
|
194
|
-
{
|
|
195
|
-
name: 'Course Image',
|
|
196
|
-
// Use React.createElement to render an image
|
|
197
|
-
access: (obj: any) =>
|
|
198
|
-
obj.coverImage
|
|
199
|
-
? React.createElement('img', {
|
|
200
|
-
src: `${domain}${obj.coverImage}`,
|
|
201
|
-
alt: 'Cover Image',
|
|
202
|
-
style: { width: '100px' },
|
|
203
|
-
})
|
|
204
|
-
: 'No Image',
|
|
205
|
-
},
|
|
206
|
-
],
|
|
207
|
-
|
|
208
|
-
// 3. Global actions (top-level page actions)
|
|
209
|
-
actions: [
|
|
210
|
-
{
|
|
211
|
-
name: 'Ping Server',
|
|
212
|
-
// No form, no arguments: simple request to '/ping'
|
|
213
|
-
onSubmit: async () => {
|
|
214
|
-
const res = await axios.get(`${domain}/courses/ping`);
|
|
215
|
-
return res.data;
|
|
216
|
-
},
|
|
217
|
-
},
|
|
218
|
-
|
|
219
|
-
{
|
|
220
|
-
name: 'Create Course',
|
|
221
|
-
// Form with one argument (formValues) on submit
|
|
222
|
-
fields: [
|
|
223
|
-
{
|
|
224
|
-
// Course Name
|
|
225
|
-
name: 'name',
|
|
226
|
-
label: 'Course Name',
|
|
227
|
-
type: 'string',
|
|
228
|
-
validationSchema: Yup.string().required('Name is required'),
|
|
229
|
-
},
|
|
230
|
-
{
|
|
231
|
-
// Level (Undergraduate/Graduate dropdown)
|
|
232
|
-
name: 'level',
|
|
233
|
-
label: 'Level',
|
|
234
|
-
type: 'dropdown',
|
|
235
|
-
validationSchema: Yup.string().required('Level is required'),
|
|
236
|
-
// 'options' would have the same effect as 'getOptions', but not written as a function
|
|
237
|
-
getOptions: async () => [
|
|
238
|
-
{ value: 'Undergraduate', display: 'Undergraduate' },
|
|
239
|
-
{ value: 'Graduate', display: 'Graduate' },
|
|
240
|
-
],
|
|
241
|
-
},
|
|
242
|
-
{
|
|
243
|
-
// Credits
|
|
244
|
-
name: 'credits',
|
|
245
|
-
label: 'Credits',
|
|
246
|
-
type: 'number',
|
|
247
|
-
validationSchema: Yup.number().required('Credits are required'),
|
|
248
|
-
},
|
|
249
|
-
{
|
|
250
|
-
// Start Date
|
|
251
|
-
name: 'startDate',
|
|
252
|
-
label: 'Start Date',
|
|
253
|
-
type: 'date',
|
|
254
|
-
validationSchema: Yup.date().required('Start date is required'),
|
|
255
|
-
},
|
|
256
|
-
{
|
|
257
|
-
// Instructor (object with name and email)
|
|
258
|
-
name: 'instructor',
|
|
259
|
-
label: 'Instructor',
|
|
260
|
-
type: 'object',
|
|
261
|
-
fields: [
|
|
262
|
-
{
|
|
263
|
-
name: 'name',
|
|
264
|
-
label: 'Instructor Name',
|
|
265
|
-
type: 'string',
|
|
266
|
-
validationSchema: Yup.string().required('Instructor name is required'),
|
|
267
|
-
},
|
|
268
|
-
{
|
|
269
|
-
name: 'email',
|
|
270
|
-
label: 'Instructor Email',
|
|
271
|
-
type: 'string',
|
|
272
|
-
validationSchema: Yup.string()
|
|
273
|
-
.email('Invalid email')
|
|
274
|
-
.required('Instructor email is required'),
|
|
275
|
-
},
|
|
276
|
-
],
|
|
277
|
-
},
|
|
278
|
-
{
|
|
279
|
-
// Schedule (array of sessions with date and topic)
|
|
280
|
-
name: 'schedule',
|
|
281
|
-
label: 'Schedule',
|
|
282
|
-
type: 'array',
|
|
283
|
-
element: {
|
|
284
|
-
name: '', // no specific name for array elements
|
|
285
|
-
label: '', // (fields below define each session)
|
|
286
|
-
type: 'object',
|
|
287
|
-
fields: [
|
|
288
|
-
{
|
|
289
|
-
name: 'date',
|
|
290
|
-
label: 'Date',
|
|
291
|
-
type: 'date',
|
|
292
|
-
validationSchema: Yup.date().required('Session date is required'),
|
|
293
|
-
},
|
|
294
|
-
{
|
|
295
|
-
name: 'topic',
|
|
296
|
-
label: 'Topic',
|
|
297
|
-
type: 'string',
|
|
298
|
-
validationSchema: Yup.string().required('Session topic is required'),
|
|
299
|
-
},
|
|
300
|
-
],
|
|
301
|
-
},
|
|
302
|
-
},
|
|
303
|
-
{
|
|
304
|
-
// Cover Image (file upload)
|
|
305
|
-
name: 'coverImage',
|
|
306
|
-
label: 'Cover Image',
|
|
307
|
-
type: 'file',
|
|
308
|
-
validationSchema: Yup.mixed().required('Cover image is required'),
|
|
309
|
-
},
|
|
310
|
-
],
|
|
311
|
-
onSubmit: async (formValues: any) => {
|
|
312
|
-
// Create a new course
|
|
313
|
-
const data = new FormData();
|
|
314
|
-
Object.entries(formValues).forEach(([k, v]) => {
|
|
315
|
-
if (k === 'coverImage') {
|
|
316
|
-
if (v instanceof File) data.append('coverImage', v);
|
|
317
|
-
} else if (k === 'schedule') {
|
|
318
|
-
data.append('schedule', JSON.stringify(v));
|
|
319
|
-
} else if (k === 'instructor') {
|
|
320
|
-
const instructor = v as { name: string; email: string };
|
|
321
|
-
data.append('instructor[name]', instructor.name);
|
|
322
|
-
data.append('instructor[email]', instructor.email);
|
|
323
|
-
} else {
|
|
324
|
-
data.append(k, v as any);
|
|
325
|
-
}
|
|
326
|
-
});
|
|
327
|
-
|
|
328
|
-
await axios.post(`${domain}/courses`, data, {
|
|
329
|
-
headers: { 'Content-Type': 'multipart/form-data' },
|
|
330
|
-
});
|
|
331
|
-
},
|
|
332
|
-
},
|
|
333
|
-
],
|
|
334
|
-
|
|
335
|
-
// 4. Row-specific actions (actions for each course row)
|
|
336
|
-
rowActions: [
|
|
337
|
-
{
|
|
338
|
-
name: 'Delete Course',
|
|
339
|
-
// No form; onSubmit uses one argument (rowId) to delete that course
|
|
340
|
-
onSubmit: async (courseId: number) => {
|
|
341
|
-
const res = await axios.delete(`${domain}/courses/${courseId}`);
|
|
342
|
-
return res.data;
|
|
343
|
-
},
|
|
344
|
-
},
|
|
345
|
-
|
|
346
|
-
{
|
|
347
|
-
name: 'Archive Course',
|
|
348
|
-
// No form; onSubmit uses two arguments (rowId and rowObject) to archive
|
|
349
|
-
onSubmit: async (courseId: number, course: any) => {
|
|
350
|
-
console.log(course); // you can do whatever you want with the course information
|
|
351
|
-
const res = await axios.patch(`${domain}/courses/${courseId}/archive`);
|
|
352
|
-
return res.data;
|
|
353
|
-
},
|
|
354
|
-
},
|
|
355
|
-
|
|
356
|
-
{
|
|
357
|
-
name: 'Edit Basic Info',
|
|
358
|
-
// Form (name, level, credits) with two arguments onSubmit (formValues, rowId)
|
|
359
|
-
fields: [
|
|
360
|
-
{
|
|
361
|
-
name: 'name',
|
|
362
|
-
label: 'Course Name',
|
|
363
|
-
type: 'string',
|
|
364
|
-
validationSchema: Yup.string().required('Name is required'),
|
|
365
|
-
},
|
|
366
|
-
{
|
|
367
|
-
name: 'level',
|
|
368
|
-
label: 'Level',
|
|
369
|
-
type: 'dropdown',
|
|
370
|
-
validationSchema: Yup.string().required('Level is required'),
|
|
371
|
-
getOptions: async () => [
|
|
372
|
-
{ value: 'Undergraduate', display: 'Undergraduate' },
|
|
373
|
-
{ value: 'Graduate', display: 'Graduate' },
|
|
374
|
-
],
|
|
375
|
-
},
|
|
376
|
-
{
|
|
377
|
-
name: 'credits',
|
|
378
|
-
label: 'Credits',
|
|
379
|
-
type: 'number',
|
|
380
|
-
validationSchema: Yup.number().required('Credits are required'),
|
|
381
|
-
},
|
|
382
|
-
],
|
|
383
|
-
onSubmit: async (formValues: any, courseId: number) => {
|
|
384
|
-
// Update course basic info
|
|
385
|
-
const res = await axios.patch(`${domain}/courses/${courseId}`, formValues);
|
|
386
|
-
return res.data;
|
|
387
|
-
},
|
|
388
|
-
},
|
|
389
|
-
|
|
390
|
-
{
|
|
391
|
-
name: 'Edit Full Course',
|
|
392
|
-
// Form (all course fields) with three arguments onSubmit (formValues, rowId, rowObject)
|
|
393
|
-
fields: [
|
|
394
|
-
{
|
|
395
|
-
name: 'name',
|
|
396
|
-
label: 'Course Name',
|
|
397
|
-
type: 'string',
|
|
398
|
-
validationSchema: Yup.string().required('Name is required'),
|
|
399
|
-
},
|
|
400
|
-
{
|
|
401
|
-
name: 'level',
|
|
402
|
-
label: 'Level',
|
|
403
|
-
type: 'dropdown',
|
|
404
|
-
validationSchema: Yup.string().required('Level is required'),
|
|
405
|
-
getOptions: async () => [
|
|
406
|
-
{ value: 'Undergraduate', display: 'Undergraduate' },
|
|
407
|
-
{ value: 'Graduate', display: 'Graduate' },
|
|
408
|
-
],
|
|
409
|
-
},
|
|
410
|
-
{
|
|
411
|
-
name: 'credits',
|
|
412
|
-
label: 'Credits',
|
|
413
|
-
type: 'number',
|
|
414
|
-
validationSchema: Yup.number().required('Credits are required'),
|
|
415
|
-
},
|
|
416
|
-
{
|
|
417
|
-
name: 'startDate',
|
|
418
|
-
label: 'Start Date',
|
|
419
|
-
type: 'date',
|
|
420
|
-
validationSchema: Yup.date().required('Start date is required'),
|
|
421
|
-
},
|
|
422
|
-
{
|
|
423
|
-
name: 'instructor',
|
|
424
|
-
label: 'Instructor',
|
|
425
|
-
type: 'object',
|
|
426
|
-
fields: [
|
|
427
|
-
{
|
|
428
|
-
name: 'name',
|
|
429
|
-
label: 'Instructor Name',
|
|
430
|
-
type: 'string',
|
|
431
|
-
validationSchema: Yup.string().required('Instructor name is required'),
|
|
432
|
-
},
|
|
433
|
-
{
|
|
434
|
-
name: 'email',
|
|
435
|
-
label: 'Instructor Email',
|
|
436
|
-
type: 'string',
|
|
437
|
-
validationSchema: Yup.string()
|
|
438
|
-
.email('Invalid email')
|
|
439
|
-
.required('Instructor email is required'),
|
|
440
|
-
},
|
|
441
|
-
],
|
|
442
|
-
},
|
|
443
|
-
{
|
|
444
|
-
name: 'schedule',
|
|
445
|
-
label: 'Schedule',
|
|
446
|
-
type: 'array',
|
|
447
|
-
element: {
|
|
448
|
-
name: '',
|
|
449
|
-
label: '',
|
|
450
|
-
type: 'object',
|
|
451
|
-
fields: [
|
|
452
|
-
{
|
|
453
|
-
name: 'date',
|
|
454
|
-
label: 'Date',
|
|
455
|
-
type: 'date',
|
|
456
|
-
validationSchema: Yup.date().required('Session date is required'),
|
|
457
|
-
},
|
|
458
|
-
{
|
|
459
|
-
name: 'topic',
|
|
460
|
-
label: 'Topic',
|
|
461
|
-
type: 'string',
|
|
462
|
-
validationSchema: Yup.string().required('Session topic is required'),
|
|
463
|
-
},
|
|
464
|
-
],
|
|
465
|
-
},
|
|
466
|
-
},
|
|
467
|
-
{
|
|
468
|
-
name: 'coverImage',
|
|
469
|
-
label: 'Cover Image',
|
|
470
|
-
type: 'file',
|
|
471
|
-
validationSchema: Yup.mixed().required('Cover image is required'),
|
|
472
|
-
},
|
|
473
|
-
],
|
|
474
|
-
// Use initialise to fetch current course data and pre-fill the form
|
|
475
|
-
initialise: async (courseId: any) => {
|
|
476
|
-
const res = await axios.get(`${domain}/courses/${courseId}`);
|
|
477
|
-
return res.data; // return course object to populate the form
|
|
478
|
-
},
|
|
479
|
-
onSubmit: async (formValues: any, courseId: number, course: any) => {
|
|
480
|
-
// Update full course details
|
|
481
|
-
// Please note that you can include "initialise" and not the "rowObject" (or vice versa)
|
|
482
|
-
console.log(course); // you can do whatever you want with the course information
|
|
483
|
-
const data = new FormData();
|
|
484
|
-
|
|
485
|
-
data.append('name', formValues.name);
|
|
486
|
-
data.append('level', formValues.level);
|
|
487
|
-
data.append('credits', formValues.credits);
|
|
488
|
-
data.append('startDate', formValues.startDate);
|
|
489
|
-
|
|
490
|
-
data.append('instructor[name]', formValues.instructor.name);
|
|
491
|
-
data.append('instructor[email]', formValues.instructor.email);
|
|
492
|
-
|
|
493
|
-
data.append('schedule', JSON.stringify(formValues.schedule));
|
|
494
|
-
|
|
495
|
-
if (formValues.coverImage) {
|
|
496
|
-
data.append('coverImage', formValues.coverImage);
|
|
497
|
-
}
|
|
498
|
-
|
|
499
|
-
await axios.patch(`${domain}/courses/${courseId}`, data, {
|
|
500
|
-
headers: { 'Content-Type': 'multipart/form-data' },
|
|
501
|
-
});
|
|
502
|
-
},
|
|
503
|
-
},
|
|
504
|
-
],
|
|
505
|
-
};
|
|
506
|
-
```
|
|
507
|
-
|
|
508
|
-
#### An array of schemas must be defined as below:
|
|
509
|
-
|
|
510
|
-
```
|
|
511
|
-
const your_array = [
|
|
512
|
-
{
|
|
513
|
-
// schema 1
|
|
514
|
-
},
|
|
515
|
-
{
|
|
516
|
-
// schema 2
|
|
517
|
-
},
|
|
518
|
-
...
|
|
519
|
-
];
|
|
520
|
-
```
|
|
521
|
-
|
|
522
|
-
- In TypeScript, it would slightly change:
|
|
523
|
-
|
|
524
|
-
```
|
|
525
|
-
const your_array: PageSchema[] = [
|
|
526
|
-
{
|
|
527
|
-
// schema 1
|
|
528
|
-
},
|
|
529
|
-
{
|
|
530
|
-
// schema 2
|
|
531
|
-
},
|
|
532
|
-
...
|
|
533
|
-
];
|
|
534
|
-
```
|
|
535
|
-
|
|
536
145
|
## Websockets Support
|
|
537
146
|
|
|
538
147
|
For real-time changes, this NPM module uses socket.io-client library to implement WebSockets. So, on the server-side, you will have to use socket.io library, and provide an `apiURL` as shown above.
|
|
@@ -547,4 +156,4 @@ For every entity you describe through a page schema, getRequest and onSubmit fun
|
|
|
547
156
|
|
|
548
157
|
## Learn More
|
|
549
158
|
|
|
550
|
-
For more details, especially about page schemas, contact the owner. :)
|
|
159
|
+
For more details, especially about page schemas, contact the owner (LSAC Bucuresti). :)
|
|
@@ -60,6 +60,13 @@ export function AdminPage({ pages, selectedPage, basePath, apiURL = '', }) {
|
|
|
60
60
|
const [rowObject, setRowObject] = useState([]);
|
|
61
61
|
const [refetch, setRefetch] = useState(false);
|
|
62
62
|
const [socket, setSocket] = useState(io);
|
|
63
|
+
const [pagination, setPagination] = useState(() => {
|
|
64
|
+
const saved = localStorage.getItem('tablePageSize');
|
|
65
|
+
return {
|
|
66
|
+
pageIndex: 0,
|
|
67
|
+
pageSize: saved ? parseInt(saved) : 10,
|
|
68
|
+
};
|
|
69
|
+
});
|
|
63
70
|
if (apiURL !== '') {
|
|
64
71
|
newSocket(setSocket, apiURL);
|
|
65
72
|
}
|
|
@@ -99,7 +106,13 @@ export function AdminPage({ pages, selectedPage, basePath, apiURL = '', }) {
|
|
|
99
106
|
overflowY: 'visible',
|
|
100
107
|
overflowX: 'auto',
|
|
101
108
|
},
|
|
102
|
-
}, enableColumnResizing: true, enableColumnPinning: true, enableGlobalFilterModes: true, enablePagination:
|
|
109
|
+
}, enableColumnResizing: true, enableColumnPinning: true, enableGlobalFilterModes: true, enablePagination: true, paginationDisplayMode: "pages", onPaginationChange: (updater) => {
|
|
110
|
+
setPagination((prev) => {
|
|
111
|
+
const next = typeof updater === 'function' ? updater(prev) : updater;
|
|
112
|
+
localStorage.setItem('tablePageSize', String(next.pageSize));
|
|
113
|
+
return next;
|
|
114
|
+
});
|
|
115
|
+
}, state: { pagination }, enableRowVirtualization: false, renderTopToolbarCustomActions: () => (_jsx(ChakraProvider, { children: _jsx(Stack, { margin: 0, padding: 2, direction: "row", wrap: "wrap", children: selectedPage.actions.map((action, index) => (_jsx(Button, { colorScheme: "blue", onClick: () => {
|
|
103
116
|
setSelectedAction(action);
|
|
104
117
|
onOpen();
|
|
105
118
|
}, children: action.name }, index))) }) })), enableRowActions: true, renderRowActionMenuItems: (obj) => {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
1
|
import axios from 'axios';
|
|
2
|
+
import React from 'react';
|
|
3
3
|
import * as Yup from 'yup';
|
|
4
4
|
const domain = 'http://localhost:3001';
|
|
5
5
|
// Please try to modularize the code for your schemas though
|
|
@@ -35,7 +35,6 @@ export const coursesPage = {
|
|
|
35
35
|
},
|
|
36
36
|
{
|
|
37
37
|
name: 'Instructor',
|
|
38
|
-
// Display instructor name (assuming obj.instructor has { name, email })
|
|
39
38
|
access: (obj) => (obj.instructor ? obj.instructor.name : 'N/A'),
|
|
40
39
|
},
|
|
41
40
|
{
|
|
@@ -125,8 +124,8 @@ export const coursesPage = {
|
|
|
125
124
|
label: 'Schedule',
|
|
126
125
|
type: 'array',
|
|
127
126
|
element: {
|
|
128
|
-
name: '', // no specific name for array elements
|
|
129
|
-
label: '', //
|
|
127
|
+
name: '', // no specific name required for array elements, it will not be displayed
|
|
128
|
+
label: '', // no specific label required for array elements, it will not be displayed
|
|
130
129
|
type: 'object',
|
|
131
130
|
fields: [
|
|
132
131
|
{
|
|
@@ -172,9 +171,25 @@ export const coursesPage = {
|
|
|
172
171
|
data.append(k, v);
|
|
173
172
|
}
|
|
174
173
|
});
|
|
175
|
-
await axios.post(`${domain}/courses`, data, {
|
|
174
|
+
const res = await axios.post(`${domain}/courses`, data, {
|
|
176
175
|
headers: { 'Content-Type': 'multipart/form-data' },
|
|
177
176
|
});
|
|
177
|
+
return res.data;
|
|
178
|
+
},
|
|
179
|
+
},
|
|
180
|
+
{
|
|
181
|
+
name: 'Download All Course Photos',
|
|
182
|
+
// Example for downloading different objects (photos, CVs etc.)
|
|
183
|
+
onSubmit: async () => {
|
|
184
|
+
const res = await axios.get(`${domain}/courses/photos/download`, { responseType: 'blob' });
|
|
185
|
+
const url = window.URL.createObjectURL(new Blob([res.data], { type: 'application/zip' }));
|
|
186
|
+
const link = document.createElement('a');
|
|
187
|
+
link.href = url;
|
|
188
|
+
link.download = 'courses-images.zip';
|
|
189
|
+
document.body.appendChild(link);
|
|
190
|
+
link.click();
|
|
191
|
+
link.remove();
|
|
192
|
+
window.URL.revokeObjectURL(url);
|
|
178
193
|
},
|
|
179
194
|
},
|
|
180
195
|
],
|
|
@@ -334,9 +349,10 @@ export const coursesPage = {
|
|
|
334
349
|
if (formValues.coverImage) {
|
|
335
350
|
data.append('coverImage', formValues.coverImage);
|
|
336
351
|
}
|
|
337
|
-
await axios.patch(`${domain}/courses/${courseId}`, data, {
|
|
352
|
+
const res = await axios.patch(`${domain}/courses/${courseId}`, data, {
|
|
338
353
|
headers: { 'Content-Type': 'multipart/form-data' },
|
|
339
354
|
});
|
|
355
|
+
return res.data;
|
|
340
356
|
},
|
|
341
357
|
},
|
|
342
358
|
],
|