squarefi-bff-api-module 1.31.3 → 1.31.5
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/dist/api/index.d.ts +2 -0
- package/dist/api/index.js +2 -0
- package/dist/api/storage.d.ts +8 -0
- package/dist/api/storage.js +19 -0
- package/dist/api/types/types.d.ts +20 -0
- package/dist/utils/fileStorage.d.ts +6 -8
- package/dist/utils/fileStorage.js +8 -10
- package/package.json +1 -1
- package/src/api/index.ts +3 -0
- package/src/api/storage.ts +24 -0
- package/src/api/types/types.ts +26 -0
- package/src/utils/fileStorage.ts +7 -21
- package/docs/READY_TO_USE_COMPONENT.tsx +0 -395
package/dist/api/index.d.ts
CHANGED
|
@@ -9,6 +9,7 @@ import { kyc } from './kyc';
|
|
|
9
9
|
import { list } from './list';
|
|
10
10
|
import { orders } from './orders';
|
|
11
11
|
import { persona } from './persona';
|
|
12
|
+
import { storage } from './storage';
|
|
12
13
|
import { tenants } from './tenants';
|
|
13
14
|
import { totp } from './totp';
|
|
14
15
|
import { user } from './user';
|
|
@@ -26,6 +27,7 @@ type Api = {
|
|
|
26
27
|
list: typeof list;
|
|
27
28
|
orders: typeof orders;
|
|
28
29
|
persona: typeof persona;
|
|
30
|
+
storage: typeof storage;
|
|
29
31
|
tenants: typeof tenants;
|
|
30
32
|
totp: typeof totp;
|
|
31
33
|
user: typeof user;
|
package/dist/api/index.js
CHANGED
|
@@ -12,6 +12,7 @@ const kyc_1 = require("./kyc");
|
|
|
12
12
|
const list_1 = require("./list");
|
|
13
13
|
const orders_1 = require("./orders");
|
|
14
14
|
const persona_1 = require("./persona");
|
|
15
|
+
const storage_1 = require("./storage");
|
|
15
16
|
const tenants_1 = require("./tenants");
|
|
16
17
|
const totp_1 = require("./totp");
|
|
17
18
|
const user_1 = require("./user");
|
|
@@ -29,6 +30,7 @@ exports.squarefi_bff_api_client = {
|
|
|
29
30
|
list: list_1.list,
|
|
30
31
|
orders: orders_1.orders,
|
|
31
32
|
persona: persona_1.persona,
|
|
33
|
+
storage: storage_1.storage,
|
|
32
34
|
tenants: tenants_1.tenants,
|
|
33
35
|
totp: totp_1.totp,
|
|
34
36
|
user: user_1.user,
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { API } from './types/types';
|
|
2
|
+
export declare const storage: {
|
|
3
|
+
kyc: {
|
|
4
|
+
upload: (file: File) => Promise<API.Storage.KYC.Upload.Response>;
|
|
5
|
+
getFileUrl: ({ path }: API.Storage.KYC.GetFileUrl.Request) => Promise<API.Storage.KYC.GetFileUrl.Response>;
|
|
6
|
+
getFileById: ({ folderId, fileId, }: API.Storage.KYC.GetFileById.Request) => Promise<API.Storage.KYC.GetFileById.Response>;
|
|
7
|
+
};
|
|
8
|
+
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.storage = void 0;
|
|
4
|
+
const apiClientFactory_1 = require("../utils/apiClientFactory");
|
|
5
|
+
exports.storage = {
|
|
6
|
+
kyc: {
|
|
7
|
+
upload: (file) => {
|
|
8
|
+
const formData = new FormData();
|
|
9
|
+
formData.append('file', file);
|
|
10
|
+
return apiClientFactory_1.apiClientV2.postRequest('/storage/kyc', {
|
|
11
|
+
data: formData,
|
|
12
|
+
});
|
|
13
|
+
},
|
|
14
|
+
getFileUrl: ({ path }) => apiClientFactory_1.apiClientV2.getRequest('/storage/kyc', {
|
|
15
|
+
params: { path },
|
|
16
|
+
}),
|
|
17
|
+
getFileById: ({ folderId, fileId, }) => apiClientFactory_1.apiClientV2.getRequest(`/storage/kyc/${folderId}/${fileId}`),
|
|
18
|
+
},
|
|
19
|
+
};
|
|
@@ -2235,4 +2235,24 @@ export declare namespace API {
|
|
|
2235
2235
|
}
|
|
2236
2236
|
}
|
|
2237
2237
|
}
|
|
2238
|
+
namespace Storage {
|
|
2239
|
+
namespace KYC {
|
|
2240
|
+
namespace Upload {
|
|
2241
|
+
type Response = operations['StorageController_uploadKycFile']['responses']['201']['content']['application/json'];
|
|
2242
|
+
}
|
|
2243
|
+
namespace GetFileUrl {
|
|
2244
|
+
type Request = {
|
|
2245
|
+
path: string;
|
|
2246
|
+
};
|
|
2247
|
+
type Response = operations['StorageController_getFileUrl']['responses']['200']['content']['application/octet-stream'];
|
|
2248
|
+
}
|
|
2249
|
+
namespace GetFileById {
|
|
2250
|
+
interface Request {
|
|
2251
|
+
folderId: string;
|
|
2252
|
+
fileId: string;
|
|
2253
|
+
}
|
|
2254
|
+
type Response = operations['StorageController_getKycFile']['responses']['200']['content']['application/octet-stream'];
|
|
2255
|
+
}
|
|
2256
|
+
}
|
|
2257
|
+
}
|
|
2238
2258
|
}
|
|
@@ -27,9 +27,7 @@ export interface GetFileUrlOptions {
|
|
|
27
27
|
/**
|
|
28
28
|
* Названия бакетов по умолчанию
|
|
29
29
|
*/
|
|
30
|
-
export declare const
|
|
31
|
-
export declare const DOCUMENTS_BUCKET = "documents";
|
|
32
|
-
export declare const IMAGES_BUCKET = "images";
|
|
30
|
+
export declare const ORDER_DOCS_BUCKET = "order_docs";
|
|
33
31
|
/**
|
|
34
32
|
* Загружает файл в Supabase Storage
|
|
35
33
|
* Файл сохраняется по пути: {folder}/{fileName} или {fileName}
|
|
@@ -76,7 +74,7 @@ export declare const getSignedUrl: (options: GetFileUrlOptions) => Promise<strin
|
|
|
76
74
|
* @param bucket - название бакета
|
|
77
75
|
* @returns постоянный URL
|
|
78
76
|
*/
|
|
79
|
-
export declare const getPublicUrl: (path: string, bucket
|
|
77
|
+
export declare const getPublicUrl: (path: string, bucket: string) => string | null;
|
|
80
78
|
/**
|
|
81
79
|
* Удаляет файл из хранилища
|
|
82
80
|
*
|
|
@@ -85,7 +83,7 @@ export declare const getPublicUrl: (path: string, bucket?: string) => string | n
|
|
|
85
83
|
* @param authToken - JWT token для авторизации
|
|
86
84
|
* @returns true при успешном удалении
|
|
87
85
|
*/
|
|
88
|
-
export declare const deleteFile: (path: string, bucket
|
|
86
|
+
export declare const deleteFile: (path: string, bucket: string, authToken?: string) => Promise<boolean>;
|
|
89
87
|
/**
|
|
90
88
|
* Удаляет несколько файлов из хранилища
|
|
91
89
|
*
|
|
@@ -94,7 +92,7 @@ export declare const deleteFile: (path: string, bucket?: string, authToken?: str
|
|
|
94
92
|
* @param authToken - JWT token для авторизации
|
|
95
93
|
* @returns true при успешном удалении всех файлов
|
|
96
94
|
*/
|
|
97
|
-
export declare const deleteFiles: (paths: string[], bucket
|
|
95
|
+
export declare const deleteFiles: (paths: string[], bucket: string, authToken?: string) => Promise<boolean>;
|
|
98
96
|
/**
|
|
99
97
|
* Получает список файлов пользователя
|
|
100
98
|
*
|
|
@@ -103,7 +101,7 @@ export declare const deleteFiles: (paths: string[], bucket?: string, authToken?:
|
|
|
103
101
|
* @param authToken - JWT token для авторизации
|
|
104
102
|
* @returns список файлов или пустой массив при ошибке
|
|
105
103
|
*/
|
|
106
|
-
export declare const listUserFiles: (userId: string, bucket
|
|
104
|
+
export declare const listUserFiles: (userId: string, bucket: string, authToken?: string) => Promise<import("@supabase/storage-js").FileObject[]>;
|
|
107
105
|
/**
|
|
108
106
|
* Скачивает файл из хранилища
|
|
109
107
|
*
|
|
@@ -112,4 +110,4 @@ export declare const listUserFiles: (userId: string, bucket?: string, authToken?
|
|
|
112
110
|
* @param authToken - JWT token для авторизации
|
|
113
111
|
* @returns Blob файла или null при ошибке
|
|
114
112
|
*/
|
|
115
|
-
export declare const downloadFile: (path: string, bucket
|
|
113
|
+
export declare const downloadFile: (path: string, bucket: string, authToken?: string) => Promise<Blob | null>;
|
|
@@ -42,14 +42,12 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
42
42
|
});
|
|
43
43
|
};
|
|
44
44
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
45
|
-
exports.downloadFile = exports.listUserFiles = exports.deleteFiles = exports.deleteFile = exports.getPublicUrl = exports.getSignedUrl = exports.uploadFile = exports.
|
|
45
|
+
exports.downloadFile = exports.listUserFiles = exports.deleteFiles = exports.deleteFile = exports.getPublicUrl = exports.getSignedUrl = exports.uploadFile = exports.ORDER_DOCS_BUCKET = void 0;
|
|
46
46
|
const supabase_1 = require("./supabase");
|
|
47
47
|
/**
|
|
48
48
|
* Названия бакетов по умолчанию
|
|
49
49
|
*/
|
|
50
|
-
exports.
|
|
51
|
-
exports.DOCUMENTS_BUCKET = 'documents';
|
|
52
|
-
exports.IMAGES_BUCKET = 'images';
|
|
50
|
+
exports.ORDER_DOCS_BUCKET = 'order_docs';
|
|
53
51
|
/**
|
|
54
52
|
* Загружает файл в Supabase Storage
|
|
55
53
|
* Файл сохраняется по пути: {folder}/{fileName} или {fileName}
|
|
@@ -125,7 +123,7 @@ exports.uploadFile = uploadFile;
|
|
|
125
123
|
* @returns подписанный URL или null при ошибке
|
|
126
124
|
*/
|
|
127
125
|
const getSignedUrl = (options) => __awaiter(void 0, void 0, void 0, function* () {
|
|
128
|
-
const { path, bucket
|
|
126
|
+
const { path, bucket, expiresIn = 3600, authToken } = options;
|
|
129
127
|
if (!supabase_1.supabaseClient) {
|
|
130
128
|
console.error('Supabase client is not initialized');
|
|
131
129
|
return null;
|
|
@@ -183,7 +181,7 @@ exports.getSignedUrl = getSignedUrl;
|
|
|
183
181
|
* @param bucket - название бакета
|
|
184
182
|
* @returns постоянный URL
|
|
185
183
|
*/
|
|
186
|
-
const getPublicUrl = (path, bucket
|
|
184
|
+
const getPublicUrl = (path, bucket) => {
|
|
187
185
|
if (!supabase_1.supabaseClient) {
|
|
188
186
|
console.error('Supabase client is not initialized');
|
|
189
187
|
return null;
|
|
@@ -200,7 +198,7 @@ exports.getPublicUrl = getPublicUrl;
|
|
|
200
198
|
* @param authToken - JWT token для авторизации
|
|
201
199
|
* @returns true при успешном удалении
|
|
202
200
|
*/
|
|
203
|
-
const deleteFile = (
|
|
201
|
+
const deleteFile = (path, bucket, authToken) => __awaiter(void 0, void 0, void 0, function* () {
|
|
204
202
|
if (!supabase_1.supabaseClient) {
|
|
205
203
|
console.error('Supabase client is not initialized');
|
|
206
204
|
return false;
|
|
@@ -238,7 +236,7 @@ exports.deleteFile = deleteFile;
|
|
|
238
236
|
* @param authToken - JWT token для авторизации
|
|
239
237
|
* @returns true при успешном удалении всех файлов
|
|
240
238
|
*/
|
|
241
|
-
const deleteFiles = (
|
|
239
|
+
const deleteFiles = (paths, bucket, authToken) => __awaiter(void 0, void 0, void 0, function* () {
|
|
242
240
|
if (!supabase_1.supabaseClient) {
|
|
243
241
|
console.error('Supabase client is not initialized');
|
|
244
242
|
return false;
|
|
@@ -276,7 +274,7 @@ exports.deleteFiles = deleteFiles;
|
|
|
276
274
|
* @param authToken - JWT token для авторизации
|
|
277
275
|
* @returns список файлов или пустой массив при ошибке
|
|
278
276
|
*/
|
|
279
|
-
const listUserFiles = (
|
|
277
|
+
const listUserFiles = (userId, bucket, authToken) => __awaiter(void 0, void 0, void 0, function* () {
|
|
280
278
|
if (!supabase_1.supabaseClient) {
|
|
281
279
|
console.error('Supabase client is not initialized');
|
|
282
280
|
return [];
|
|
@@ -314,7 +312,7 @@ exports.listUserFiles = listUserFiles;
|
|
|
314
312
|
* @param authToken - JWT token для авторизации
|
|
315
313
|
* @returns Blob файла или null при ошибке
|
|
316
314
|
*/
|
|
317
|
-
const downloadFile = (
|
|
315
|
+
const downloadFile = (path, bucket, authToken) => __awaiter(void 0, void 0, void 0, function* () {
|
|
318
316
|
if (!supabase_1.supabaseClient) {
|
|
319
317
|
console.error('Supabase client is not initialized');
|
|
320
318
|
return null;
|
package/package.json
CHANGED
package/src/api/index.ts
CHANGED
|
@@ -9,6 +9,7 @@ import { kyc } from './kyc';
|
|
|
9
9
|
import { list } from './list';
|
|
10
10
|
import { orders } from './orders';
|
|
11
11
|
import { persona } from './persona';
|
|
12
|
+
import { storage } from './storage';
|
|
12
13
|
import { tenants } from './tenants';
|
|
13
14
|
import { totp } from './totp';
|
|
14
15
|
import { user } from './user';
|
|
@@ -27,6 +28,7 @@ type Api = {
|
|
|
27
28
|
list: typeof list;
|
|
28
29
|
orders: typeof orders;
|
|
29
30
|
persona: typeof persona;
|
|
31
|
+
storage: typeof storage;
|
|
30
32
|
tenants: typeof tenants;
|
|
31
33
|
totp: typeof totp;
|
|
32
34
|
user: typeof user;
|
|
@@ -46,6 +48,7 @@ export const squarefi_bff_api_client: Api = {
|
|
|
46
48
|
list,
|
|
47
49
|
orders,
|
|
48
50
|
persona,
|
|
51
|
+
storage,
|
|
49
52
|
tenants,
|
|
50
53
|
totp,
|
|
51
54
|
user,
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { apiClientV2 } from '../utils/apiClientFactory';
|
|
2
|
+
import { API } from './types/types';
|
|
3
|
+
|
|
4
|
+
export const storage = {
|
|
5
|
+
kyc: {
|
|
6
|
+
upload: (file: File): Promise<API.Storage.KYC.Upload.Response> => {
|
|
7
|
+
const formData = new FormData();
|
|
8
|
+
formData.append('file', file);
|
|
9
|
+
|
|
10
|
+
return apiClientV2.postRequest<API.Storage.KYC.Upload.Response>('/storage/kyc', {
|
|
11
|
+
data: formData,
|
|
12
|
+
});
|
|
13
|
+
},
|
|
14
|
+
getFileUrl: ({ path }: API.Storage.KYC.GetFileUrl.Request): Promise<API.Storage.KYC.GetFileUrl.Response> =>
|
|
15
|
+
apiClientV2.getRequest<API.Storage.KYC.GetFileUrl.Response>('/storage/kyc', {
|
|
16
|
+
params: { path },
|
|
17
|
+
}),
|
|
18
|
+
getFileById: ({
|
|
19
|
+
folderId,
|
|
20
|
+
fileId,
|
|
21
|
+
}: API.Storage.KYC.GetFileById.Request): Promise<API.Storage.KYC.GetFileById.Response> =>
|
|
22
|
+
apiClientV2.getRequest<API.Storage.KYC.GetFileById.Response>(`/storage/kyc/${folderId}/${fileId}`),
|
|
23
|
+
},
|
|
24
|
+
};
|
package/src/api/types/types.ts
CHANGED
|
@@ -2791,4 +2791,30 @@ export namespace API {
|
|
|
2791
2791
|
}
|
|
2792
2792
|
}
|
|
2793
2793
|
}
|
|
2794
|
+
|
|
2795
|
+
export namespace Storage {
|
|
2796
|
+
export namespace KYC {
|
|
2797
|
+
export namespace Upload {
|
|
2798
|
+
export type Response =
|
|
2799
|
+
operations['StorageController_uploadKycFile']['responses']['201']['content']['application/json'];
|
|
2800
|
+
}
|
|
2801
|
+
|
|
2802
|
+
export namespace GetFileUrl {
|
|
2803
|
+
export type Request = {
|
|
2804
|
+
path: string;
|
|
2805
|
+
};
|
|
2806
|
+
export type Response =
|
|
2807
|
+
operations['StorageController_getFileUrl']['responses']['200']['content']['application/octet-stream'];
|
|
2808
|
+
}
|
|
2809
|
+
|
|
2810
|
+
export namespace GetFileById {
|
|
2811
|
+
export interface Request {
|
|
2812
|
+
folderId: string;
|
|
2813
|
+
fileId: string;
|
|
2814
|
+
}
|
|
2815
|
+
export type Response =
|
|
2816
|
+
operations['StorageController_getKycFile']['responses']['200']['content']['application/octet-stream'];
|
|
2817
|
+
}
|
|
2818
|
+
}
|
|
2819
|
+
}
|
|
2794
2820
|
}
|
package/src/utils/fileStorage.ts
CHANGED
|
@@ -32,9 +32,7 @@ export interface GetFileUrlOptions {
|
|
|
32
32
|
/**
|
|
33
33
|
* Названия бакетов по умолчанию
|
|
34
34
|
*/
|
|
35
|
-
export const
|
|
36
|
-
export const DOCUMENTS_BUCKET = 'documents';
|
|
37
|
-
export const IMAGES_BUCKET = 'images';
|
|
35
|
+
export const ORDER_DOCS_BUCKET = 'order_docs';
|
|
38
36
|
|
|
39
37
|
/**
|
|
40
38
|
* Загружает файл в Supabase Storage
|
|
@@ -118,7 +116,7 @@ export const uploadFile = async (options: UploadFileOptions): Promise<UploadFile
|
|
|
118
116
|
* @returns подписанный URL или null при ошибке
|
|
119
117
|
*/
|
|
120
118
|
export const getSignedUrl = async (options: GetFileUrlOptions): Promise<string | null> => {
|
|
121
|
-
const { path, bucket
|
|
119
|
+
const { path, bucket, expiresIn = 3600, authToken } = options;
|
|
122
120
|
|
|
123
121
|
if (!supabaseClient) {
|
|
124
122
|
console.error('Supabase client is not initialized');
|
|
@@ -180,7 +178,7 @@ export const getSignedUrl = async (options: GetFileUrlOptions): Promise<string |
|
|
|
180
178
|
* @param bucket - название бакета
|
|
181
179
|
* @returns постоянный URL
|
|
182
180
|
*/
|
|
183
|
-
export const getPublicUrl = (path: string, bucket: string
|
|
181
|
+
export const getPublicUrl = (path: string, bucket: string): string | null => {
|
|
184
182
|
if (!supabaseClient) {
|
|
185
183
|
console.error('Supabase client is not initialized');
|
|
186
184
|
return null;
|
|
@@ -198,11 +196,7 @@ export const getPublicUrl = (path: string, bucket: string = DEFAULT_BUCKET): str
|
|
|
198
196
|
* @param authToken - JWT token для авторизации
|
|
199
197
|
* @returns true при успешном удалении
|
|
200
198
|
*/
|
|
201
|
-
export const deleteFile = async (
|
|
202
|
-
path: string,
|
|
203
|
-
bucket: string = DEFAULT_BUCKET,
|
|
204
|
-
authToken?: string,
|
|
205
|
-
): Promise<boolean> => {
|
|
199
|
+
export const deleteFile = async (path: string, bucket: string, authToken?: string): Promise<boolean> => {
|
|
206
200
|
if (!supabaseClient) {
|
|
207
201
|
console.error('Supabase client is not initialized');
|
|
208
202
|
return false;
|
|
@@ -243,11 +237,7 @@ export const deleteFile = async (
|
|
|
243
237
|
* @param authToken - JWT token для авторизации
|
|
244
238
|
* @returns true при успешном удалении всех файлов
|
|
245
239
|
*/
|
|
246
|
-
export const deleteFiles = async (
|
|
247
|
-
paths: string[],
|
|
248
|
-
bucket: string = DEFAULT_BUCKET,
|
|
249
|
-
authToken?: string,
|
|
250
|
-
): Promise<boolean> => {
|
|
240
|
+
export const deleteFiles = async (paths: string[], bucket: string, authToken?: string): Promise<boolean> => {
|
|
251
241
|
if (!supabaseClient) {
|
|
252
242
|
console.error('Supabase client is not initialized');
|
|
253
243
|
return false;
|
|
@@ -288,7 +278,7 @@ export const deleteFiles = async (
|
|
|
288
278
|
* @param authToken - JWT token для авторизации
|
|
289
279
|
* @returns список файлов или пустой массив при ошибке
|
|
290
280
|
*/
|
|
291
|
-
export const listUserFiles = async (userId: string, bucket: string
|
|
281
|
+
export const listUserFiles = async (userId: string, bucket: string, authToken?: string) => {
|
|
292
282
|
if (!supabaseClient) {
|
|
293
283
|
console.error('Supabase client is not initialized');
|
|
294
284
|
return [];
|
|
@@ -329,11 +319,7 @@ export const listUserFiles = async (userId: string, bucket: string = DEFAULT_BUC
|
|
|
329
319
|
* @param authToken - JWT token для авторизации
|
|
330
320
|
* @returns Blob файла или null при ошибке
|
|
331
321
|
*/
|
|
332
|
-
export const downloadFile = async (
|
|
333
|
-
path: string,
|
|
334
|
-
bucket: string = DEFAULT_BUCKET,
|
|
335
|
-
authToken?: string,
|
|
336
|
-
): Promise<Blob | null> => {
|
|
322
|
+
export const downloadFile = async (path: string, bucket: string, authToken?: string): Promise<Blob | null> => {
|
|
337
323
|
if (!supabaseClient) {
|
|
338
324
|
console.error('Supabase client is not initialized');
|
|
339
325
|
return null;
|
|
@@ -1,395 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Ready-to-use File Manager component
|
|
3
|
-
*
|
|
4
|
-
* Copy this file to your project and use it!
|
|
5
|
-
*
|
|
6
|
-
* Installation:
|
|
7
|
-
* npm install squarefi-bff-api-module
|
|
8
|
-
*
|
|
9
|
-
* Usage:
|
|
10
|
-
* import { FileManager } from './FileManager';
|
|
11
|
-
*
|
|
12
|
-
* <FileManager userId="user-123" />
|
|
13
|
-
*/
|
|
14
|
-
|
|
15
|
-
import React, { useState } from 'react';
|
|
16
|
-
import {
|
|
17
|
-
useFileUpload,
|
|
18
|
-
useUserFiles,
|
|
19
|
-
DEFAULT_BUCKET,
|
|
20
|
-
} from 'squarefi-bff-api-module';
|
|
21
|
-
|
|
22
|
-
interface FileManagerProps {
|
|
23
|
-
userId: string;
|
|
24
|
-
bucket?: string;
|
|
25
|
-
maxFileSize?: number; // in bytes
|
|
26
|
-
allowedTypes?: string[];
|
|
27
|
-
onFileUpload?: (path: string) => void;
|
|
28
|
-
onFileDelete?: (fileName: string) => void;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* Universal component for managing user files
|
|
33
|
-
*/
|
|
34
|
-
export const FileManager: React.FC<FileManagerProps> = ({
|
|
35
|
-
userId,
|
|
36
|
-
bucket = DEFAULT_BUCKET,
|
|
37
|
-
maxFileSize = 10 * 1024 * 1024, // 10MB by default
|
|
38
|
-
allowedTypes = ['*/*'], // all types by default
|
|
39
|
-
onFileUpload,
|
|
40
|
-
onFileDelete,
|
|
41
|
-
}) => {
|
|
42
|
-
const [selectedFile, setSelectedFile] = useState<File | null>(null);
|
|
43
|
-
const [validationError, setValidationError] = useState<string | null>(null);
|
|
44
|
-
|
|
45
|
-
// File upload hook
|
|
46
|
-
const { upload, uploading, progress, error: uploadError } = useFileUpload({
|
|
47
|
-
userId,
|
|
48
|
-
bucket,
|
|
49
|
-
onSuccess: (result) => {
|
|
50
|
-
setSelectedFile(null);
|
|
51
|
-
setValidationError(null);
|
|
52
|
-
if (result.path) {
|
|
53
|
-
onFileUpload?.(result.path);
|
|
54
|
-
}
|
|
55
|
-
reload(); // Refresh list after upload
|
|
56
|
-
},
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
// File list hook
|
|
60
|
-
const {
|
|
61
|
-
files,
|
|
62
|
-
loading,
|
|
63
|
-
error: listError,
|
|
64
|
-
reload,
|
|
65
|
-
deleteOne,
|
|
66
|
-
} = useUserFiles({
|
|
67
|
-
userId,
|
|
68
|
-
bucket,
|
|
69
|
-
autoLoad: true,
|
|
70
|
-
autoGenerateUrls: true,
|
|
71
|
-
urlExpiresIn: 3600,
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
// File validation
|
|
75
|
-
const validateFile = (file: File): string | null => {
|
|
76
|
-
// Size check
|
|
77
|
-
if (file.size > maxFileSize) {
|
|
78
|
-
return `File too large. Maximum ${(maxFileSize / 1024 / 1024).toFixed(0)}MB`;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
// Type check
|
|
82
|
-
if (!allowedTypes.includes('*/*')) {
|
|
83
|
-
const isAllowed = allowedTypes.some((type) => {
|
|
84
|
-
if (type.endsWith('/*')) {
|
|
85
|
-
// Example: image/*
|
|
86
|
-
const prefix = type.split('/')[0];
|
|
87
|
-
return file.type.startsWith(prefix + '/');
|
|
88
|
-
}
|
|
89
|
-
return file.type === type;
|
|
90
|
-
});
|
|
91
|
-
|
|
92
|
-
if (!isAllowed) {
|
|
93
|
-
return `Unsupported file type. Allowed: ${allowedTypes.join(', ')}`;
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
return null;
|
|
98
|
-
};
|
|
99
|
-
|
|
100
|
-
const handleFileSelect = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
101
|
-
const file = e.target.files?.[0];
|
|
102
|
-
if (!file) return;
|
|
103
|
-
|
|
104
|
-
const error = validateFile(file);
|
|
105
|
-
if (error) {
|
|
106
|
-
setValidationError(error);
|
|
107
|
-
setSelectedFile(null);
|
|
108
|
-
return;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
setValidationError(null);
|
|
112
|
-
setSelectedFile(file);
|
|
113
|
-
};
|
|
114
|
-
|
|
115
|
-
const handleUpload = async () => {
|
|
116
|
-
if (!selectedFile) return;
|
|
117
|
-
|
|
118
|
-
const timestamp = Date.now();
|
|
119
|
-
const uniqueFileName = `${timestamp}-${selectedFile.name}`;
|
|
120
|
-
await upload(selectedFile, uniqueFileName);
|
|
121
|
-
};
|
|
122
|
-
|
|
123
|
-
const handleDelete = async (fileName: string) => {
|
|
124
|
-
const confirmed = window.confirm(`Are you sure you want to delete "${fileName}"?`);
|
|
125
|
-
if (!confirmed) return;
|
|
126
|
-
|
|
127
|
-
const success = await deleteOne(fileName);
|
|
128
|
-
if (success) {
|
|
129
|
-
onFileDelete?.(fileName);
|
|
130
|
-
} else {
|
|
131
|
-
alert('Failed to delete file');
|
|
132
|
-
}
|
|
133
|
-
};
|
|
134
|
-
|
|
135
|
-
const formatFileSize = (bytes: number): string => {
|
|
136
|
-
if (bytes < 1024) return bytes + ' B';
|
|
137
|
-
if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + ' KB';
|
|
138
|
-
return (bytes / 1024 / 1024).toFixed(1) + ' MB';
|
|
139
|
-
};
|
|
140
|
-
|
|
141
|
-
const formatDate = (dateString: string): string => {
|
|
142
|
-
const date = new Date(dateString);
|
|
143
|
-
return date.toLocaleDateString('en-US', {
|
|
144
|
-
year: 'numeric',
|
|
145
|
-
month: 'short',
|
|
146
|
-
day: 'numeric',
|
|
147
|
-
hour: '2-digit',
|
|
148
|
-
minute: '2-digit',
|
|
149
|
-
});
|
|
150
|
-
};
|
|
151
|
-
|
|
152
|
-
return (
|
|
153
|
-
<div style={styles.container}>
|
|
154
|
-
<h2 style={styles.title}>File Management</h2>
|
|
155
|
-
|
|
156
|
-
{/* Upload section */}
|
|
157
|
-
<div style={styles.uploadSection}>
|
|
158
|
-
<h3 style={styles.sectionTitle}>📤 Upload File</h3>
|
|
159
|
-
|
|
160
|
-
<input
|
|
161
|
-
type="file"
|
|
162
|
-
onChange={handleFileSelect}
|
|
163
|
-
disabled={uploading}
|
|
164
|
-
style={styles.fileInput}
|
|
165
|
-
accept={allowedTypes.join(',')}
|
|
166
|
-
/>
|
|
167
|
-
|
|
168
|
-
{selectedFile && !validationError && (
|
|
169
|
-
<div style={styles.selectedFile}>
|
|
170
|
-
<p style={styles.fileName}>
|
|
171
|
-
📄 {selectedFile.name} ({formatFileSize(selectedFile.size)})
|
|
172
|
-
</p>
|
|
173
|
-
<button
|
|
174
|
-
onClick={handleUpload}
|
|
175
|
-
disabled={uploading}
|
|
176
|
-
style={{
|
|
177
|
-
...styles.button,
|
|
178
|
-
...styles.uploadButton,
|
|
179
|
-
...(uploading ? styles.buttonDisabled : {}),
|
|
180
|
-
}}
|
|
181
|
-
>
|
|
182
|
-
{uploading ? `⏳ Uploading... ${progress}%` : '✅ Upload'}
|
|
183
|
-
</button>
|
|
184
|
-
</div>
|
|
185
|
-
)}
|
|
186
|
-
|
|
187
|
-
{validationError && (
|
|
188
|
-
<p style={styles.errorText}>❌ {validationError}</p>
|
|
189
|
-
)}
|
|
190
|
-
|
|
191
|
-
{uploadError && (
|
|
192
|
-
<p style={styles.errorText}>❌ Upload error: {uploadError}</p>
|
|
193
|
-
)}
|
|
194
|
-
</div>
|
|
195
|
-
|
|
196
|
-
{/* File list section */}
|
|
197
|
-
<div style={styles.listSection}>
|
|
198
|
-
<div style={styles.listHeader}>
|
|
199
|
-
<h3 style={styles.sectionTitle}>
|
|
200
|
-
📁 Your Files ({files.length})
|
|
201
|
-
</h3>
|
|
202
|
-
<button
|
|
203
|
-
onClick={reload}
|
|
204
|
-
disabled={loading}
|
|
205
|
-
style={{
|
|
206
|
-
...styles.button,
|
|
207
|
-
...styles.refreshButton,
|
|
208
|
-
...(loading ? styles.buttonDisabled : {}),
|
|
209
|
-
}}
|
|
210
|
-
>
|
|
211
|
-
🔄 Refresh
|
|
212
|
-
</button>
|
|
213
|
-
</div>
|
|
214
|
-
|
|
215
|
-
{loading && <p style={styles.loadingText}>Loading file list...</p>}
|
|
216
|
-
|
|
217
|
-
{listError && (
|
|
218
|
-
<p style={styles.errorText}>❌ List loading error: {listError}</p>
|
|
219
|
-
)}
|
|
220
|
-
|
|
221
|
-
{!loading && files.length === 0 && (
|
|
222
|
-
<p style={styles.emptyText}>You don't have any uploaded files yet</p>
|
|
223
|
-
)}
|
|
224
|
-
|
|
225
|
-
{files.length > 0 && (
|
|
226
|
-
<div style={styles.filesList}>
|
|
227
|
-
{files.map((file) => (
|
|
228
|
-
<div key={file.id} style={styles.fileItem}>
|
|
229
|
-
<div style={styles.fileInfo}>
|
|
230
|
-
<a
|
|
231
|
-
href={file.signedUrl}
|
|
232
|
-
target="_blank"
|
|
233
|
-
rel="noopener noreferrer"
|
|
234
|
-
style={styles.fileLink}
|
|
235
|
-
>
|
|
236
|
-
📄 {file.name}
|
|
237
|
-
</a>
|
|
238
|
-
<p style={styles.fileDate}>
|
|
239
|
-
{formatDate(file.created_at)}
|
|
240
|
-
</p>
|
|
241
|
-
</div>
|
|
242
|
-
<button
|
|
243
|
-
onClick={() => handleDelete(file.name)}
|
|
244
|
-
style={{
|
|
245
|
-
...styles.button,
|
|
246
|
-
...styles.deleteButton,
|
|
247
|
-
}}
|
|
248
|
-
>
|
|
249
|
-
🗑️ Delete
|
|
250
|
-
</button>
|
|
251
|
-
</div>
|
|
252
|
-
))}
|
|
253
|
-
</div>
|
|
254
|
-
)}
|
|
255
|
-
</div>
|
|
256
|
-
</div>
|
|
257
|
-
);
|
|
258
|
-
};
|
|
259
|
-
|
|
260
|
-
// Стили компонента
|
|
261
|
-
const styles: { [key: string]: React.CSSProperties } = {
|
|
262
|
-
container: {
|
|
263
|
-
padding: '20px',
|
|
264
|
-
maxWidth: '900px',
|
|
265
|
-
margin: '0 auto',
|
|
266
|
-
fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
|
|
267
|
-
},
|
|
268
|
-
title: {
|
|
269
|
-
fontSize: '28px',
|
|
270
|
-
fontWeight: 'bold',
|
|
271
|
-
marginBottom: '30px',
|
|
272
|
-
color: '#333',
|
|
273
|
-
},
|
|
274
|
-
uploadSection: {
|
|
275
|
-
marginBottom: '40px',
|
|
276
|
-
padding: '25px',
|
|
277
|
-
border: '2px solid #e0e0e0',
|
|
278
|
-
borderRadius: '12px',
|
|
279
|
-
backgroundColor: '#fafafa',
|
|
280
|
-
},
|
|
281
|
-
listSection: {
|
|
282
|
-
padding: '25px',
|
|
283
|
-
border: '2px solid #e0e0e0',
|
|
284
|
-
borderRadius: '12px',
|
|
285
|
-
backgroundColor: '#ffffff',
|
|
286
|
-
},
|
|
287
|
-
sectionTitle: {
|
|
288
|
-
fontSize: '20px',
|
|
289
|
-
fontWeight: '600',
|
|
290
|
-
marginBottom: '15px',
|
|
291
|
-
color: '#555',
|
|
292
|
-
},
|
|
293
|
-
fileInput: {
|
|
294
|
-
padding: '10px',
|
|
295
|
-
fontSize: '16px',
|
|
296
|
-
border: '2px solid #ddd',
|
|
297
|
-
borderRadius: '8px',
|
|
298
|
-
width: '100%',
|
|
299
|
-
cursor: 'pointer',
|
|
300
|
-
},
|
|
301
|
-
selectedFile: {
|
|
302
|
-
marginTop: '15px',
|
|
303
|
-
padding: '15px',
|
|
304
|
-
backgroundColor: '#e8f5e9',
|
|
305
|
-
borderRadius: '8px',
|
|
306
|
-
},
|
|
307
|
-
fileName: {
|
|
308
|
-
margin: '0 0 10px 0',
|
|
309
|
-
fontSize: '16px',
|
|
310
|
-
color: '#333',
|
|
311
|
-
},
|
|
312
|
-
button: {
|
|
313
|
-
padding: '10px 20px',
|
|
314
|
-
fontSize: '16px',
|
|
315
|
-
border: 'none',
|
|
316
|
-
borderRadius: '8px',
|
|
317
|
-
cursor: 'pointer',
|
|
318
|
-
transition: 'all 0.2s ease',
|
|
319
|
-
fontWeight: '500',
|
|
320
|
-
},
|
|
321
|
-
uploadButton: {
|
|
322
|
-
backgroundColor: '#4caf50',
|
|
323
|
-
color: 'white',
|
|
324
|
-
},
|
|
325
|
-
refreshButton: {
|
|
326
|
-
backgroundColor: '#2196f3',
|
|
327
|
-
color: 'white',
|
|
328
|
-
fontSize: '14px',
|
|
329
|
-
padding: '8px 16px',
|
|
330
|
-
},
|
|
331
|
-
deleteButton: {
|
|
332
|
-
backgroundColor: '#f44336',
|
|
333
|
-
color: 'white',
|
|
334
|
-
fontSize: '14px',
|
|
335
|
-
padding: '6px 12px',
|
|
336
|
-
},
|
|
337
|
-
buttonDisabled: {
|
|
338
|
-
opacity: 0.6,
|
|
339
|
-
cursor: 'not-allowed',
|
|
340
|
-
},
|
|
341
|
-
listHeader: {
|
|
342
|
-
display: 'flex',
|
|
343
|
-
justifyContent: 'space-between',
|
|
344
|
-
alignItems: 'center',
|
|
345
|
-
marginBottom: '20px',
|
|
346
|
-
},
|
|
347
|
-
filesList: {
|
|
348
|
-
display: 'flex',
|
|
349
|
-
flexDirection: 'column',
|
|
350
|
-
gap: '10px',
|
|
351
|
-
},
|
|
352
|
-
fileItem: {
|
|
353
|
-
display: 'flex',
|
|
354
|
-
justifyContent: 'space-between',
|
|
355
|
-
alignItems: 'center',
|
|
356
|
-
padding: '15px',
|
|
357
|
-
border: '1px solid #e0e0e0',
|
|
358
|
-
borderRadius: '8px',
|
|
359
|
-
backgroundColor: '#fafafa',
|
|
360
|
-
transition: 'background-color 0.2s ease',
|
|
361
|
-
},
|
|
362
|
-
fileInfo: {
|
|
363
|
-
flex: 1,
|
|
364
|
-
},
|
|
365
|
-
fileLink: {
|
|
366
|
-
color: '#1976d2',
|
|
367
|
-
textDecoration: 'none',
|
|
368
|
-
fontSize: '16px',
|
|
369
|
-
fontWeight: '500',
|
|
370
|
-
},
|
|
371
|
-
fileDate: {
|
|
372
|
-
margin: '5px 0 0 0',
|
|
373
|
-
fontSize: '13px',
|
|
374
|
-
color: '#888',
|
|
375
|
-
},
|
|
376
|
-
errorText: {
|
|
377
|
-
color: '#f44336',
|
|
378
|
-
marginTop: '10px',
|
|
379
|
-
fontSize: '14px',
|
|
380
|
-
},
|
|
381
|
-
loadingText: {
|
|
382
|
-
textAlign: 'center',
|
|
383
|
-
color: '#888',
|
|
384
|
-
fontSize: '16px',
|
|
385
|
-
},
|
|
386
|
-
emptyText: {
|
|
387
|
-
textAlign: 'center',
|
|
388
|
-
color: '#aaa',
|
|
389
|
-
fontSize: '16px',
|
|
390
|
-
padding: '30px',
|
|
391
|
-
},
|
|
392
|
-
};
|
|
393
|
-
|
|
394
|
-
export default FileManager;
|
|
395
|
-
|