@moontra/moonui-pro 2.11.2 → 2.11.3
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/index.d.ts +1 -17
- package/dist/index.mjs +174 -42
- package/package.json +1 -1
- package/src/components/file-upload/index.tsx +221 -76
package/dist/index.d.ts
CHANGED
|
@@ -1776,23 +1776,7 @@ interface FileUploadProProps {
|
|
|
1776
1776
|
headers?: Record<string, string>;
|
|
1777
1777
|
serviceConfig?: {
|
|
1778
1778
|
type: 'aws-s3' | 'cloudinary' | 'firebase' | 'custom';
|
|
1779
|
-
|
|
1780
|
-
region: string;
|
|
1781
|
-
bucket: string;
|
|
1782
|
-
accessKeyId?: string;
|
|
1783
|
-
secretAccessKey?: string;
|
|
1784
|
-
sessionToken?: string;
|
|
1785
|
-
};
|
|
1786
|
-
cloudinaryConfig?: {
|
|
1787
|
-
cloudName: string;
|
|
1788
|
-
uploadPreset: string;
|
|
1789
|
-
apiKey?: string;
|
|
1790
|
-
apiSecret?: string;
|
|
1791
|
-
};
|
|
1792
|
-
firebaseConfig?: {
|
|
1793
|
-
storageBucket: string;
|
|
1794
|
-
folder?: string;
|
|
1795
|
-
};
|
|
1779
|
+
config?: any;
|
|
1796
1780
|
};
|
|
1797
1781
|
onUpload?: (files: FileUploadItem[]) => Promise<void>;
|
|
1798
1782
|
onProgress?: (fileId: string, progress: number) => void;
|
package/dist/index.mjs
CHANGED
|
@@ -59071,43 +59071,6 @@ var MoonUIFileUploadPro = t__default.forwardRef(
|
|
|
59071
59071
|
}
|
|
59072
59072
|
return metadata;
|
|
59073
59073
|
}, [duplicateCheck]);
|
|
59074
|
-
const uploadToService = useCallback(async (fileItem) => {
|
|
59075
|
-
if (!serviceConfig)
|
|
59076
|
-
return null;
|
|
59077
|
-
const { file } = fileItem;
|
|
59078
|
-
switch (serviceConfig.type) {
|
|
59079
|
-
case "aws-s3":
|
|
59080
|
-
if (uploadStrategy === "presigned" && endpoint) {
|
|
59081
|
-
const response = await fetch(endpoint, {
|
|
59082
|
-
method: "PUT",
|
|
59083
|
-
body: file,
|
|
59084
|
-
headers: {
|
|
59085
|
-
"Content-Type": file.type,
|
|
59086
|
-
...headers
|
|
59087
|
-
}
|
|
59088
|
-
});
|
|
59089
|
-
return response.ok ? { url: response.url } : null;
|
|
59090
|
-
}
|
|
59091
|
-
break;
|
|
59092
|
-
case "cloudinary":
|
|
59093
|
-
if (serviceConfig.cloudinaryConfig) {
|
|
59094
|
-
const { cloudName, uploadPreset } = serviceConfig.cloudinaryConfig;
|
|
59095
|
-
const formData = new FormData();
|
|
59096
|
-
formData.append("file", file);
|
|
59097
|
-
formData.append("upload_preset", uploadPreset);
|
|
59098
|
-
const response = await fetch(
|
|
59099
|
-
`https://api.cloudinary.com/v1_1/${cloudName}/auto/upload`,
|
|
59100
|
-
{
|
|
59101
|
-
method: "POST",
|
|
59102
|
-
body: formData
|
|
59103
|
-
}
|
|
59104
|
-
);
|
|
59105
|
-
return response.json();
|
|
59106
|
-
}
|
|
59107
|
-
break;
|
|
59108
|
-
}
|
|
59109
|
-
return null;
|
|
59110
|
-
}, [serviceConfig, uploadStrategy, endpoint, headers]);
|
|
59111
59074
|
const resizeImage = useCallback(async (file) => {
|
|
59112
59075
|
if (!imageResize || !file.type.startsWith("image/"))
|
|
59113
59076
|
return file;
|
|
@@ -59155,6 +59118,166 @@ var MoonUIFileUploadPro = t__default.forwardRef(
|
|
|
59155
59118
|
reader.readAsDataURL(file);
|
|
59156
59119
|
});
|
|
59157
59120
|
}, [imageResize]);
|
|
59121
|
+
const uploadToService = useCallback(async (fileItem) => {
|
|
59122
|
+
if (!serviceConfig)
|
|
59123
|
+
return null;
|
|
59124
|
+
const { file } = fileItem;
|
|
59125
|
+
let processedFile = file;
|
|
59126
|
+
if (imageResize && file.type.startsWith("image/")) {
|
|
59127
|
+
processedFile = await resizeImage(file);
|
|
59128
|
+
}
|
|
59129
|
+
try {
|
|
59130
|
+
switch (serviceConfig.type) {
|
|
59131
|
+
case "aws-s3": {
|
|
59132
|
+
const { config } = serviceConfig;
|
|
59133
|
+
if (!config)
|
|
59134
|
+
throw new Error("AWS S3 config is required");
|
|
59135
|
+
const { bucketUrl, bucketName, region, accessKeyId, policy, signature, algorithm, credential, date, fields } = config;
|
|
59136
|
+
if (!bucketUrl && !bucketName) {
|
|
59137
|
+
throw new Error("AWS S3 requires either bucketUrl or bucketName");
|
|
59138
|
+
}
|
|
59139
|
+
if (bucketUrl && !bucketName) {
|
|
59140
|
+
const formData2 = new FormData();
|
|
59141
|
+
formData2.append("file", processedFile || file);
|
|
59142
|
+
const response2 = await fetch(bucketUrl, {
|
|
59143
|
+
method: "POST",
|
|
59144
|
+
body: formData2,
|
|
59145
|
+
headers: {
|
|
59146
|
+
...headers,
|
|
59147
|
+
...config.headers || {}
|
|
59148
|
+
}
|
|
59149
|
+
});
|
|
59150
|
+
if (!response2.ok) {
|
|
59151
|
+
throw new Error(`Upload failed: ${response2.statusText}`);
|
|
59152
|
+
}
|
|
59153
|
+
return await response2.json();
|
|
59154
|
+
}
|
|
59155
|
+
const s3Endpoint = bucketUrl || `https://${bucketName}.s3.${region || "us-east-1"}.amazonaws.com/`;
|
|
59156
|
+
const key = fields?.key || `uploads/${Date.now()}_${file.name}`;
|
|
59157
|
+
const formData = new FormData();
|
|
59158
|
+
if (fields) {
|
|
59159
|
+
Object.entries(fields).forEach(([k3, v]) => {
|
|
59160
|
+
if (k3 !== "file") {
|
|
59161
|
+
formData.append(k3, v);
|
|
59162
|
+
}
|
|
59163
|
+
});
|
|
59164
|
+
} else {
|
|
59165
|
+
formData.append("key", key);
|
|
59166
|
+
if (accessKeyId)
|
|
59167
|
+
formData.append("AWSAccessKeyId", accessKeyId);
|
|
59168
|
+
formData.append("Content-Type", file.type);
|
|
59169
|
+
if (policy)
|
|
59170
|
+
formData.append("policy", policy);
|
|
59171
|
+
if (signature)
|
|
59172
|
+
formData.append("signature", signature);
|
|
59173
|
+
if (algorithm)
|
|
59174
|
+
formData.append("x-amz-algorithm", algorithm);
|
|
59175
|
+
if (credential)
|
|
59176
|
+
formData.append("x-amz-credential", credential);
|
|
59177
|
+
if (date)
|
|
59178
|
+
formData.append("x-amz-date", date);
|
|
59179
|
+
}
|
|
59180
|
+
formData.append("file", processedFile || file);
|
|
59181
|
+
const response = await fetch(s3Endpoint, {
|
|
59182
|
+
method: "POST",
|
|
59183
|
+
body: formData
|
|
59184
|
+
});
|
|
59185
|
+
if (!response.ok && response.status !== 204) {
|
|
59186
|
+
const errorText = await response.text();
|
|
59187
|
+
throw new Error(`S3 upload failed: ${response.status} - ${errorText}`);
|
|
59188
|
+
}
|
|
59189
|
+
return {
|
|
59190
|
+
url: `${s3Endpoint}${key}`,
|
|
59191
|
+
key,
|
|
59192
|
+
bucket: bucketName,
|
|
59193
|
+
etag: response.headers.get("ETag"),
|
|
59194
|
+
location: response.headers.get("Location") || `${s3Endpoint}${key}`
|
|
59195
|
+
};
|
|
59196
|
+
}
|
|
59197
|
+
case "cloudinary": {
|
|
59198
|
+
const { config } = serviceConfig;
|
|
59199
|
+
if (!config)
|
|
59200
|
+
throw new Error("Cloudinary config is required");
|
|
59201
|
+
const { cloudName, uploadPreset, apiKey } = config;
|
|
59202
|
+
const formData = new FormData();
|
|
59203
|
+
formData.append("file", processedFile || file);
|
|
59204
|
+
formData.append("upload_preset", uploadPreset);
|
|
59205
|
+
if (apiKey) {
|
|
59206
|
+
formData.append("api_key", apiKey);
|
|
59207
|
+
}
|
|
59208
|
+
if (imageResize) {
|
|
59209
|
+
formData.append("eager", `w_${imageResize.maxWidth},h_${imageResize.maxHeight},c_limit,q_${Math.round(imageResize.quality * 100)}`);
|
|
59210
|
+
}
|
|
59211
|
+
const response = await fetch(
|
|
59212
|
+
`https://api.cloudinary.com/v1_1/${cloudName}/auto/upload`,
|
|
59213
|
+
{
|
|
59214
|
+
method: "POST",
|
|
59215
|
+
body: formData
|
|
59216
|
+
}
|
|
59217
|
+
);
|
|
59218
|
+
if (!response.ok)
|
|
59219
|
+
throw new Error("Cloudinary upload failed");
|
|
59220
|
+
const result = await response.json();
|
|
59221
|
+
return {
|
|
59222
|
+
url: result.secure_url,
|
|
59223
|
+
publicId: result.public_id,
|
|
59224
|
+
format: result.format,
|
|
59225
|
+
width: result.width,
|
|
59226
|
+
height: result.height,
|
|
59227
|
+
bytes: result.bytes,
|
|
59228
|
+
cloudName
|
|
59229
|
+
};
|
|
59230
|
+
}
|
|
59231
|
+
case "firebase": {
|
|
59232
|
+
const { config } = serviceConfig;
|
|
59233
|
+
if (!config)
|
|
59234
|
+
throw new Error("Firebase config is required");
|
|
59235
|
+
const { storageBucket, folder = "uploads" } = config;
|
|
59236
|
+
const fileName = `${folder}/${Date.now()}-${file.name}`;
|
|
59237
|
+
const uploadUrl = `https://storage.googleapis.com/upload/storage/v1/b/${storageBucket}/o?uploadType=media&name=${encodeURIComponent(fileName)}`;
|
|
59238
|
+
const response = await fetch(uploadUrl, {
|
|
59239
|
+
method: "POST",
|
|
59240
|
+
body: processedFile || file,
|
|
59241
|
+
headers: {
|
|
59242
|
+
"Content-Type": file.type,
|
|
59243
|
+
...headers
|
|
59244
|
+
// Should include Authorization: Bearer [token]
|
|
59245
|
+
}
|
|
59246
|
+
});
|
|
59247
|
+
if (!response.ok)
|
|
59248
|
+
throw new Error("Firebase upload failed");
|
|
59249
|
+
const result = await response.json();
|
|
59250
|
+
return {
|
|
59251
|
+
url: `https://storage.googleapis.com/${storageBucket}/${fileName}`,
|
|
59252
|
+
name: result.name,
|
|
59253
|
+
bucket: result.bucket,
|
|
59254
|
+
generation: result.generation,
|
|
59255
|
+
contentType: result.contentType,
|
|
59256
|
+
size: result.size
|
|
59257
|
+
};
|
|
59258
|
+
}
|
|
59259
|
+
case "custom": {
|
|
59260
|
+
if (!endpoint)
|
|
59261
|
+
throw new Error("Custom endpoint is required");
|
|
59262
|
+
const formData = new FormData();
|
|
59263
|
+
formData.append("file", file);
|
|
59264
|
+
const response = await fetch(endpoint, {
|
|
59265
|
+
method: "POST",
|
|
59266
|
+
body: formData,
|
|
59267
|
+
headers
|
|
59268
|
+
});
|
|
59269
|
+
if (!response.ok)
|
|
59270
|
+
throw new Error("Custom upload failed");
|
|
59271
|
+
return response.json();
|
|
59272
|
+
}
|
|
59273
|
+
default:
|
|
59274
|
+
throw new Error(`Unsupported service type: ${serviceConfig.type}`);
|
|
59275
|
+
}
|
|
59276
|
+
} catch (error2) {
|
|
59277
|
+
console.error("Upload service error:", error2);
|
|
59278
|
+
throw error2;
|
|
59279
|
+
}
|
|
59280
|
+
}, [serviceConfig, endpoint, headers, imageResize, resizeImage]);
|
|
59158
59281
|
const uploadFileChunked = useCallback(async (fileItem) => {
|
|
59159
59282
|
const { file } = fileItem;
|
|
59160
59283
|
const chunks = [];
|
|
@@ -59176,14 +59299,23 @@ var MoonUIFileUploadPro = t__default.forwardRef(
|
|
|
59176
59299
|
let uploadedBytes = 0;
|
|
59177
59300
|
const startTime = Date.now();
|
|
59178
59301
|
try {
|
|
59302
|
+
let processedFile = file;
|
|
59303
|
+
if (imageResize && file.type.startsWith("image/")) {
|
|
59304
|
+
processedFile = await resizeImage(file);
|
|
59305
|
+
}
|
|
59306
|
+
if (serviceConfig) {
|
|
59307
|
+
const uploadResult = await uploadToService({ ...fileItem, file: processedFile });
|
|
59308
|
+
setFiles((prev) => prev.map(
|
|
59309
|
+
(f) => f.id === fileItem.id ? { ...f, status: "success", progress: 100, result: uploadResult } : f
|
|
59310
|
+
));
|
|
59311
|
+
onComplete?.(fileItem.id, uploadResult);
|
|
59312
|
+
return;
|
|
59313
|
+
}
|
|
59179
59314
|
for (const chunk of chunks) {
|
|
59180
59315
|
if (abortController.signal.aborted) {
|
|
59181
59316
|
throw new Error("Upload cancelled");
|
|
59182
59317
|
}
|
|
59183
|
-
|
|
59184
|
-
if (!serviceResult) {
|
|
59185
|
-
await new Promise((resolve) => setTimeout(resolve, 100 + Math.random() * 200));
|
|
59186
|
-
}
|
|
59318
|
+
await new Promise((resolve) => setTimeout(resolve, 100 + Math.random() * 200));
|
|
59187
59319
|
uploadedBytes += chunk.end - chunk.start;
|
|
59188
59320
|
const progress = Math.round(uploadedBytes / file.size * 100);
|
|
59189
59321
|
const elapsedTime = (Date.now() - startTime) / 1e3;
|
|
@@ -59215,7 +59347,7 @@ var MoonUIFileUploadPro = t__default.forwardRef(
|
|
|
59215
59347
|
} finally {
|
|
59216
59348
|
uploadQueue.current.delete(fileItem.id);
|
|
59217
59349
|
}
|
|
59218
|
-
}, [chunkSize, onProgress, onComplete, onError]);
|
|
59350
|
+
}, [chunkSize, onProgress, onComplete, onError, serviceConfig, uploadToService, resizeImage, imageResize]);
|
|
59219
59351
|
const processFiles = useCallback(async (fileList) => {
|
|
59220
59352
|
const fileArray = Array.from(fileList);
|
|
59221
59353
|
setError(null);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@moontra/moonui-pro",
|
|
3
|
-
"version": "2.11.
|
|
3
|
+
"version": "2.11.3",
|
|
4
4
|
"description": "Premium React components for MoonUI - Advanced UI library with 50+ pro components including performance, interactive, and gesture components",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.mjs",
|
|
@@ -81,23 +81,7 @@ export interface FileUploadProProps {
|
|
|
81
81
|
// Service integrations
|
|
82
82
|
serviceConfig?: {
|
|
83
83
|
type: 'aws-s3' | 'cloudinary' | 'firebase' | 'custom'
|
|
84
|
-
|
|
85
|
-
region: string
|
|
86
|
-
bucket: string
|
|
87
|
-
accessKeyId?: string
|
|
88
|
-
secretAccessKey?: string
|
|
89
|
-
sessionToken?: string
|
|
90
|
-
}
|
|
91
|
-
cloudinaryConfig?: {
|
|
92
|
-
cloudName: string
|
|
93
|
-
uploadPreset: string
|
|
94
|
-
apiKey?: string
|
|
95
|
-
apiSecret?: string
|
|
96
|
-
}
|
|
97
|
-
firebaseConfig?: {
|
|
98
|
-
storageBucket: string
|
|
99
|
-
folder?: string
|
|
100
|
-
}
|
|
84
|
+
config?: any
|
|
101
85
|
}
|
|
102
86
|
|
|
103
87
|
// Callbacks
|
|
@@ -614,56 +598,6 @@ export const MoonUIFileUploadPro = React.forwardRef<HTMLDivElement, FileUploadPr
|
|
|
614
598
|
return metadata
|
|
615
599
|
}, [duplicateCheck])
|
|
616
600
|
|
|
617
|
-
// Service specific upload handler
|
|
618
|
-
const uploadToService = useCallback(async (fileItem: FileUploadItem): Promise<any> => {
|
|
619
|
-
if (!serviceConfig) return null
|
|
620
|
-
|
|
621
|
-
const { file } = fileItem
|
|
622
|
-
|
|
623
|
-
switch (serviceConfig.type) {
|
|
624
|
-
case 'aws-s3':
|
|
625
|
-
// AWS S3 upload logic
|
|
626
|
-
if (uploadStrategy === 'presigned' && endpoint) {
|
|
627
|
-
const response = await fetch(endpoint, {
|
|
628
|
-
method: 'PUT',
|
|
629
|
-
body: file,
|
|
630
|
-
headers: {
|
|
631
|
-
'Content-Type': file.type,
|
|
632
|
-
...headers
|
|
633
|
-
}
|
|
634
|
-
})
|
|
635
|
-
return response.ok ? { url: response.url } : null
|
|
636
|
-
}
|
|
637
|
-
break
|
|
638
|
-
|
|
639
|
-
case 'cloudinary':
|
|
640
|
-
// Cloudinary upload logic
|
|
641
|
-
if (serviceConfig.cloudinaryConfig) {
|
|
642
|
-
const { cloudName, uploadPreset } = serviceConfig.cloudinaryConfig
|
|
643
|
-
const formData = new FormData()
|
|
644
|
-
formData.append('file', file)
|
|
645
|
-
formData.append('upload_preset', uploadPreset)
|
|
646
|
-
|
|
647
|
-
const response = await fetch(
|
|
648
|
-
`https://api.cloudinary.com/v1_1/${cloudName}/auto/upload`,
|
|
649
|
-
{
|
|
650
|
-
method: 'POST',
|
|
651
|
-
body: formData
|
|
652
|
-
}
|
|
653
|
-
)
|
|
654
|
-
return response.json()
|
|
655
|
-
}
|
|
656
|
-
break
|
|
657
|
-
|
|
658
|
-
case 'firebase':
|
|
659
|
-
// Firebase upload would be handled by the onUpload callback
|
|
660
|
-
// as it requires Firebase SDK integration
|
|
661
|
-
break
|
|
662
|
-
}
|
|
663
|
-
|
|
664
|
-
return null
|
|
665
|
-
}, [serviceConfig, uploadStrategy, endpoint, headers])
|
|
666
|
-
|
|
667
601
|
// Image resize handler
|
|
668
602
|
const resizeImage = useCallback(async (file: File): Promise<File> => {
|
|
669
603
|
if (!imageResize || !file.type.startsWith('image/')) return file
|
|
@@ -722,6 +656,202 @@ export const MoonUIFileUploadPro = React.forwardRef<HTMLDivElement, FileUploadPr
|
|
|
722
656
|
reader.readAsDataURL(file)
|
|
723
657
|
})
|
|
724
658
|
}, [imageResize])
|
|
659
|
+
|
|
660
|
+
// Service specific upload handler
|
|
661
|
+
const uploadToService = useCallback(async (fileItem: FileUploadItem): Promise<any> => {
|
|
662
|
+
if (!serviceConfig) return null
|
|
663
|
+
|
|
664
|
+
const { file } = fileItem
|
|
665
|
+
let processedFile = file
|
|
666
|
+
|
|
667
|
+
// Resize image if needed
|
|
668
|
+
if (imageResize && file.type.startsWith('image/')) {
|
|
669
|
+
processedFile = await resizeImage(file)
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
try {
|
|
673
|
+
switch (serviceConfig.type) {
|
|
674
|
+
case 'aws-s3': {
|
|
675
|
+
// AWS S3 Direct Upload
|
|
676
|
+
const { config } = serviceConfig as any
|
|
677
|
+
if (!config) throw new Error('AWS S3 config is required')
|
|
678
|
+
|
|
679
|
+
const { bucketUrl, bucketName, region, accessKeyId, policy, signature, algorithm, credential, date, fields } = config
|
|
680
|
+
|
|
681
|
+
if (!bucketUrl && !bucketName) {
|
|
682
|
+
throw new Error('AWS S3 requires either bucketUrl or bucketName')
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
// Option 1: Direct upload to custom S3-compatible endpoint
|
|
686
|
+
if (bucketUrl && !bucketName) {
|
|
687
|
+
const formData = new FormData()
|
|
688
|
+
formData.append('file', processedFile || file)
|
|
689
|
+
|
|
690
|
+
const response = await fetch(bucketUrl, {
|
|
691
|
+
method: 'POST',
|
|
692
|
+
body: formData,
|
|
693
|
+
headers: {
|
|
694
|
+
...headers,
|
|
695
|
+
...(config.headers || {})
|
|
696
|
+
}
|
|
697
|
+
})
|
|
698
|
+
|
|
699
|
+
if (!response.ok) {
|
|
700
|
+
throw new Error(`Upload failed: ${response.statusText}`)
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
return await response.json()
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
// Option 2: AWS S3 POST upload with policy-based authentication
|
|
707
|
+
// This requires server-side generation of policy and signature
|
|
708
|
+
const s3Endpoint = bucketUrl || `https://${bucketName}.s3.${region || 'us-east-1'}.amazonaws.com/`
|
|
709
|
+
const key = fields?.key || `uploads/${Date.now()}_${file.name}`
|
|
710
|
+
|
|
711
|
+
const formData = new FormData()
|
|
712
|
+
|
|
713
|
+
// AWS S3 POST policy fields must be added in specific order
|
|
714
|
+
if (fields) {
|
|
715
|
+
// Add all policy fields first
|
|
716
|
+
Object.entries(fields).forEach(([k, v]) => {
|
|
717
|
+
if (k !== 'file') {
|
|
718
|
+
formData.append(k, v as string)
|
|
719
|
+
}
|
|
720
|
+
})
|
|
721
|
+
} else {
|
|
722
|
+
// Basic fields for public bucket upload
|
|
723
|
+
formData.append('key', key)
|
|
724
|
+
if (accessKeyId) formData.append('AWSAccessKeyId', accessKeyId)
|
|
725
|
+
formData.append('Content-Type', file.type)
|
|
726
|
+
if (policy) formData.append('policy', policy)
|
|
727
|
+
if (signature) formData.append('signature', signature)
|
|
728
|
+
if (algorithm) formData.append('x-amz-algorithm', algorithm)
|
|
729
|
+
if (credential) formData.append('x-amz-credential', credential)
|
|
730
|
+
if (date) formData.append('x-amz-date', date)
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
// File must be the last field
|
|
734
|
+
formData.append('file', processedFile || file)
|
|
735
|
+
|
|
736
|
+
const response = await fetch(s3Endpoint, {
|
|
737
|
+
method: 'POST',
|
|
738
|
+
body: formData
|
|
739
|
+
})
|
|
740
|
+
|
|
741
|
+
// S3 returns 204 No Content on success for POST uploads
|
|
742
|
+
if (!response.ok && response.status !== 204) {
|
|
743
|
+
const errorText = await response.text()
|
|
744
|
+
throw new Error(`S3 upload failed: ${response.status} - ${errorText}`)
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
return {
|
|
748
|
+
url: `${s3Endpoint}${key}`,
|
|
749
|
+
key,
|
|
750
|
+
bucket: bucketName,
|
|
751
|
+
etag: response.headers.get('ETag'),
|
|
752
|
+
location: response.headers.get('Location') || `${s3Endpoint}${key}`
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
case 'cloudinary': {
|
|
757
|
+
// Cloudinary upload logic
|
|
758
|
+
const { config } = serviceConfig as any
|
|
759
|
+
if (!config) throw new Error('Cloudinary config is required')
|
|
760
|
+
|
|
761
|
+
const { cloudName, uploadPreset, apiKey } = config
|
|
762
|
+
const formData = new FormData()
|
|
763
|
+
formData.append('file', processedFile || file)
|
|
764
|
+
formData.append('upload_preset', uploadPreset)
|
|
765
|
+
|
|
766
|
+
if (apiKey) {
|
|
767
|
+
formData.append('api_key', apiKey)
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
// Add optional transformations
|
|
771
|
+
if (imageResize) {
|
|
772
|
+
formData.append('eager', `w_${imageResize.maxWidth},h_${imageResize.maxHeight},c_limit,q_${Math.round(imageResize.quality * 100)}`)
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
const response = await fetch(
|
|
776
|
+
`https://api.cloudinary.com/v1_1/${cloudName}/auto/upload`,
|
|
777
|
+
{
|
|
778
|
+
method: 'POST',
|
|
779
|
+
body: formData
|
|
780
|
+
}
|
|
781
|
+
)
|
|
782
|
+
|
|
783
|
+
if (!response.ok) throw new Error('Cloudinary upload failed')
|
|
784
|
+
const result = await response.json()
|
|
785
|
+
|
|
786
|
+
return {
|
|
787
|
+
url: result.secure_url,
|
|
788
|
+
publicId: result.public_id,
|
|
789
|
+
format: result.format,
|
|
790
|
+
width: result.width,
|
|
791
|
+
height: result.height,
|
|
792
|
+
bytes: result.bytes,
|
|
793
|
+
cloudName
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
case 'firebase': {
|
|
798
|
+
// Firebase Storage REST API upload (no SDK required)
|
|
799
|
+
const { config } = serviceConfig as any
|
|
800
|
+
if (!config) throw new Error('Firebase config is required')
|
|
801
|
+
|
|
802
|
+
const { storageBucket, folder = 'uploads' } = config
|
|
803
|
+
const fileName = `${folder}/${Date.now()}-${file.name}`
|
|
804
|
+
|
|
805
|
+
// Firebase Storage uses Google Cloud Storage REST API
|
|
806
|
+
const uploadUrl = `https://storage.googleapis.com/upload/storage/v1/b/${storageBucket}/o?uploadType=media&name=${encodeURIComponent(fileName)}`
|
|
807
|
+
|
|
808
|
+
const response = await fetch(uploadUrl, {
|
|
809
|
+
method: 'POST',
|
|
810
|
+
body: processedFile || file,
|
|
811
|
+
headers: {
|
|
812
|
+
'Content-Type': file.type,
|
|
813
|
+
...headers // Should include Authorization: Bearer [token]
|
|
814
|
+
}
|
|
815
|
+
})
|
|
816
|
+
|
|
817
|
+
if (!response.ok) throw new Error('Firebase upload failed')
|
|
818
|
+
const result = await response.json()
|
|
819
|
+
|
|
820
|
+
return {
|
|
821
|
+
url: `https://storage.googleapis.com/${storageBucket}/${fileName}`,
|
|
822
|
+
name: result.name,
|
|
823
|
+
bucket: result.bucket,
|
|
824
|
+
generation: result.generation,
|
|
825
|
+
contentType: result.contentType,
|
|
826
|
+
size: result.size
|
|
827
|
+
}
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
case 'custom': {
|
|
831
|
+
// Custom endpoint upload
|
|
832
|
+
if (!endpoint) throw new Error('Custom endpoint is required')
|
|
833
|
+
|
|
834
|
+
const formData = new FormData()
|
|
835
|
+
formData.append('file', file)
|
|
836
|
+
|
|
837
|
+
const response = await fetch(endpoint, {
|
|
838
|
+
method: 'POST',
|
|
839
|
+
body: formData,
|
|
840
|
+
headers: headers
|
|
841
|
+
})
|
|
842
|
+
|
|
843
|
+
if (!response.ok) throw new Error('Custom upload failed')
|
|
844
|
+
return response.json()
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
default:
|
|
848
|
+
throw new Error(`Unsupported service type: ${serviceConfig.type}`)
|
|
849
|
+
}
|
|
850
|
+
} catch (error) {
|
|
851
|
+
console.error('Upload service error:', error)
|
|
852
|
+
throw error
|
|
853
|
+
}
|
|
854
|
+
}, [serviceConfig, endpoint, headers, imageResize, resizeImage])
|
|
725
855
|
|
|
726
856
|
// Chunked upload simülasyonu
|
|
727
857
|
const uploadFileChunked = useCallback(async (fileItem: FileUploadItem) => {
|
|
@@ -754,19 +884,34 @@ export const MoonUIFileUploadPro = React.forwardRef<HTMLDivElement, FileUploadPr
|
|
|
754
884
|
const startTime = Date.now()
|
|
755
885
|
|
|
756
886
|
try {
|
|
757
|
-
//
|
|
887
|
+
// Image resize if needed
|
|
888
|
+
let processedFile = file
|
|
889
|
+
if (imageResize && file.type.startsWith('image/')) {
|
|
890
|
+
processedFile = await resizeImage(file)
|
|
891
|
+
}
|
|
892
|
+
|
|
893
|
+
// If service config exists, do the actual upload
|
|
894
|
+
if (serviceConfig) {
|
|
895
|
+
const uploadResult = await uploadToService({ ...fileItem, file: processedFile })
|
|
896
|
+
|
|
897
|
+
// Update progress to 100% immediately for service uploads
|
|
898
|
+
setFiles(prev => prev.map(f =>
|
|
899
|
+
f.id === fileItem.id
|
|
900
|
+
? { ...f, status: 'success', progress: 100, result: uploadResult }
|
|
901
|
+
: f
|
|
902
|
+
))
|
|
903
|
+
|
|
904
|
+
onComplete?.(fileItem.id, uploadResult)
|
|
905
|
+
return
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
// Regular chunk upload simulation for non-service uploads
|
|
758
909
|
for (const chunk of chunks) {
|
|
759
910
|
if (abortController.signal.aborted) {
|
|
760
911
|
throw new Error('Upload cancelled')
|
|
761
912
|
}
|
|
762
913
|
|
|
763
|
-
|
|
764
|
-
const serviceResult = await uploadToService(fileItem)
|
|
765
|
-
|
|
766
|
-
if (!serviceResult) {
|
|
767
|
-
// Fallback to chunk upload simulation
|
|
768
|
-
await new Promise(resolve => setTimeout(resolve, 100 + Math.random() * 200))
|
|
769
|
-
}
|
|
914
|
+
await new Promise(resolve => setTimeout(resolve, 100 + Math.random() * 200))
|
|
770
915
|
|
|
771
916
|
uploadedBytes += (chunk.end - chunk.start)
|
|
772
917
|
const progress = Math.round((uploadedBytes / file.size) * 100)
|
|
@@ -815,7 +960,7 @@ export const MoonUIFileUploadPro = React.forwardRef<HTMLDivElement, FileUploadPr
|
|
|
815
960
|
} finally {
|
|
816
961
|
uploadQueue.current.delete(fileItem.id)
|
|
817
962
|
}
|
|
818
|
-
}, [chunkSize, onProgress, onComplete, onError])
|
|
963
|
+
}, [chunkSize, onProgress, onComplete, onError, serviceConfig, uploadToService, resizeImage, imageResize])
|
|
819
964
|
|
|
820
965
|
// Dosya işleme
|
|
821
966
|
const processFiles = useCallback(async (fileList: FileList | File[]) => {
|