@moontra/moonui-pro 2.11.0 → 2.11.2

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 CHANGED
@@ -1774,6 +1774,26 @@ interface FileUploadProProps {
1774
1774
  uploadStrategy?: 'direct' | 'presigned' | 'multipart';
1775
1775
  endpoint?: string;
1776
1776
  headers?: Record<string, string>;
1777
+ serviceConfig?: {
1778
+ type: 'aws-s3' | 'cloudinary' | 'firebase' | 'custom';
1779
+ awsConfig?: {
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
+ };
1796
+ };
1777
1797
  onUpload?: (files: FileUploadItem[]) => Promise<void>;
1778
1798
  onProgress?: (fileId: string, progress: number) => void;
1779
1799
  onComplete?: (fileId: string, result: any) => void;
package/dist/index.mjs CHANGED
@@ -58771,23 +58771,54 @@ var generateFileHash = async (file) => {
58771
58771
  };
58772
58772
  var createImagePreview = (file) => {
58773
58773
  return new Promise((resolve) => {
58774
- const img = new Image();
58775
- const url = URL.createObjectURL(file);
58776
- img.onload = () => {
58777
- resolve({
58778
- type: "image",
58779
- url,
58780
- thumbnail: url,
58781
- dimensions: { width: img.width, height: img.height }
58782
- });
58774
+ const reader = new FileReader();
58775
+ reader.onload = (e) => {
58776
+ const result = e.target?.result;
58777
+ const img = new Image();
58778
+ img.onload = () => {
58779
+ const canvas = document.createElement("canvas");
58780
+ const ctx = canvas.getContext("2d");
58781
+ const maxThumbSize = 400;
58782
+ let thumbWidth = img.width;
58783
+ let thumbHeight = img.height;
58784
+ if (thumbWidth > maxThumbSize || thumbHeight > maxThumbSize) {
58785
+ const aspectRatio = img.width / img.height;
58786
+ if (aspectRatio > 1) {
58787
+ thumbWidth = maxThumbSize;
58788
+ thumbHeight = maxThumbSize / aspectRatio;
58789
+ } else {
58790
+ thumbHeight = maxThumbSize;
58791
+ thumbWidth = maxThumbSize * aspectRatio;
58792
+ }
58793
+ }
58794
+ canvas.width = thumbWidth;
58795
+ canvas.height = thumbHeight;
58796
+ ctx.drawImage(img, 0, 0, thumbWidth, thumbHeight);
58797
+ const thumbnail = canvas.toDataURL("image/jpeg", 0.9);
58798
+ resolve({
58799
+ type: "image",
58800
+ url: result,
58801
+ thumbnail,
58802
+ dimensions: { width: img.width, height: img.height }
58803
+ });
58804
+ };
58805
+ img.onerror = () => {
58806
+ resolve({
58807
+ type: "image",
58808
+ url: result,
58809
+ thumbnail: result
58810
+ });
58811
+ };
58812
+ img.src = result;
58783
58813
  };
58784
- img.onerror = () => {
58814
+ reader.onerror = () => {
58815
+ const url = URL.createObjectURL(file);
58785
58816
  resolve({
58786
58817
  type: "image",
58787
58818
  url
58788
58819
  });
58789
58820
  };
58790
- img.src = url;
58821
+ reader.readAsDataURL(file);
58791
58822
  });
58792
58823
  };
58793
58824
  var createVideoPreview = (file) => {
@@ -58839,16 +58870,16 @@ var BulkActions = ({
58839
58870
  children: [
58840
58871
  /* @__PURE__ */ jsxs("span", { className: "text-sm font-medium", children: [
58841
58872
  selectedIds.length,
58842
- " dosya se\xE7ildi"
58873
+ " files selected"
58843
58874
  ] }),
58844
58875
  /* @__PURE__ */ jsxs("div", { className: "flex gap-1 ml-auto", children: [
58845
58876
  /* @__PURE__ */ jsxs(MoonUIButtonPro, { variant: "ghost", size: "sm", onClick: onBulkDownload, children: [
58846
58877
  /* @__PURE__ */ jsx(Download, { className: "h-4 w-4 mr-1" }),
58847
- "\u0130ndir"
58878
+ "Download"
58848
58879
  ] }),
58849
58880
  /* @__PURE__ */ jsxs(MoonUIButtonPro, { variant: "ghost", size: "sm", onClick: onBulkRemove, children: [
58850
58881
  /* @__PURE__ */ jsx(Trash2, { className: "h-4 w-4 mr-1" }),
58851
- "Sil"
58882
+ "Delete"
58852
58883
  ] }),
58853
58884
  /* @__PURE__ */ jsx(MoonUIButtonPro, { variant: "ghost", size: "sm", onClick: onClearSelection, children: /* @__PURE__ */ jsx(X, { className: "h-4 w-4" }) })
58854
58885
  ] })
@@ -58870,37 +58901,45 @@ var FilePreviewModal = ({
58870
58901
  /* @__PURE__ */ jsx(MoonUIBadgePro, { variant: "secondary", children: formatFileSize(file.file.size) })
58871
58902
  ] }) }),
58872
58903
  /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-center p-4 bg-muted/20 rounded-lg", children: [
58873
- file.preview.type === "image" && /* @__PURE__ */ jsx(
58874
- "img",
58875
- {
58876
- src: file.preview.url,
58877
- alt: file.file.name,
58878
- className: "max-w-full max-h-[60vh] object-contain rounded"
58879
- }
58880
- ),
58881
- file.preview.type === "video" && /* @__PURE__ */ jsx(
58904
+ file.preview.type === "image" && /* @__PURE__ */ jsxs("div", { className: "relative", children: [
58905
+ /* @__PURE__ */ jsx(
58906
+ "img",
58907
+ {
58908
+ src: file.preview.url,
58909
+ alt: file.file.name,
58910
+ className: "max-w-full max-h-[60vh] object-contain rounded shadow-lg",
58911
+ loading: "eager"
58912
+ }
58913
+ ),
58914
+ /* @__PURE__ */ jsx("div", { className: "absolute inset-0 flex items-center justify-center bg-background/80 rounded opacity-0 pointer-events-none transition-opacity duration-300", children: /* @__PURE__ */ jsx(Loader2, { className: "h-8 w-8 animate-spin text-muted-foreground" }) })
58915
+ ] }),
58916
+ file.preview.type === "video" && /* @__PURE__ */ jsx("div", { className: "relative w-full max-w-4xl", children: /* @__PURE__ */ jsx(
58882
58917
  "video",
58883
58918
  {
58884
58919
  src: file.preview.url,
58885
58920
  controls: true,
58886
- className: "max-w-full max-h-[60vh] rounded"
58921
+ className: "w-full max-h-[60vh] rounded shadow-lg",
58922
+ poster: file.preview.thumbnail
58887
58923
  }
58888
- ),
58889
- file.preview.type === "audio" && /* @__PURE__ */ jsx("div", { className: "w-full max-w-md", children: /* @__PURE__ */ jsx("audio", { src: file.preview.url, controls: true, className: "w-full" }) }),
58924
+ ) }),
58925
+ file.preview.type === "audio" && /* @__PURE__ */ jsx("div", { className: "w-full max-w-md space-y-4", children: /* @__PURE__ */ jsxs("div", { className: "p-8 bg-gradient-to-br from-primary/10 to-primary/5 rounded-lg", children: [
58926
+ /* @__PURE__ */ jsx(Music, { className: "h-16 w-16 mx-auto text-primary mb-4" }),
58927
+ /* @__PURE__ */ jsx("audio", { src: file.preview.url, controls: true, className: "w-full" })
58928
+ ] }) }),
58890
58929
  !["image", "video", "audio"].includes(file.preview.type) && /* @__PURE__ */ jsxs("div", { className: "text-center py-8", children: [
58891
58930
  getFileIcon(file.file.type, "lg"),
58892
- /* @__PURE__ */ jsx("p", { className: "mt-2 text-muted-foreground", children: "\xD6nizleme mevcut de\u011Fil" })
58931
+ /* @__PURE__ */ jsx("p", { className: "mt-2 text-muted-foreground", children: "Preview not available" })
58893
58932
  ] })
58894
58933
  ] }),
58895
58934
  file.preview.dimensions && /* @__PURE__ */ jsxs("div", { className: "flex gap-4 text-sm text-muted-foreground", children: [
58896
58935
  /* @__PURE__ */ jsxs("span", { children: [
58897
- "Boyutlar: ",
58936
+ "Dimensions: ",
58898
58937
  file.preview.dimensions.width,
58899
58938
  " \xD7 ",
58900
58939
  file.preview.dimensions.height
58901
58940
  ] }),
58902
58941
  file.preview.duration && /* @__PURE__ */ jsxs("span", { children: [
58903
- "S\xFCre: ",
58942
+ "Duration: ",
58904
58943
  formatTime(file.preview.duration)
58905
58944
  ] })
58906
58945
  ] })
@@ -58937,6 +58976,10 @@ var MoonUIFileUploadPro = t__default.forwardRef(
58937
58976
  onPreview,
58938
58977
  onBulkSelect,
58939
58978
  customValidation,
58979
+ serviceConfig,
58980
+ imageResize,
58981
+ endpoint,
58982
+ headers,
58940
58983
  ...props
58941
58984
  }, ref) => {
58942
58985
  const { hasProAccess, isLoading } = useSubscription();
@@ -58952,21 +58995,21 @@ var MoonUIFileUploadPro = t__default.forwardRef(
58952
58995
  return /* @__PURE__ */ jsx(MoonUICardPro, { className: cn("w-full", className), children: /* @__PURE__ */ jsx(MoonUICardContentPro, { className: "py-12 text-center", children: /* @__PURE__ */ jsxs("div", { className: "max-w-md mx-auto space-y-4", children: [
58953
58996
  /* @__PURE__ */ jsx("div", { className: "rounded-full bg-purple-100 dark:bg-purple-900/30 p-3 w-fit mx-auto", children: /* @__PURE__ */ jsx(Lock, { className: "h-6 w-6 text-purple-600 dark:text-purple-400" }) }),
58954
58997
  /* @__PURE__ */ jsxs("div", { children: [
58955
- /* @__PURE__ */ jsx("h3", { className: "font-semibold text-lg mb-2", children: "Pro \xD6zellik" }),
58956
- /* @__PURE__ */ jsx("p", { className: "text-muted-foreground text-sm mb-4", children: "Geli\u015Fmi\u015F Dosya Y\xFCkleme sadece MoonUI Pro abonelerine \xF6zeldir." }),
58998
+ /* @__PURE__ */ jsx("h3", { className: "font-semibold text-lg mb-2", children: "Pro Feature" }),
58999
+ /* @__PURE__ */ jsx("p", { className: "text-muted-foreground text-sm mb-4", children: "Advanced File Upload is exclusive to MoonUI Pro subscribers." }),
58957
59000
  /* @__PURE__ */ jsx("div", { className: "flex gap-3 justify-center", children: /* @__PURE__ */ jsx("a", { href: "/pricing", children: /* @__PURE__ */ jsxs(MoonUIButtonPro, { size: "sm", children: [
58958
59001
  /* @__PURE__ */ jsx(Sparkles, { className: "mr-2 h-4 w-4" }),
58959
- "Pro'ya Y\xFCkseltin"
59002
+ "Upgrade to Pro"
58960
59003
  ] }) }) })
58961
59004
  ] })
58962
59005
  ] }) }) });
58963
59006
  }
58964
59007
  const validateFile = useCallback(async (file) => {
58965
59008
  if (file.size > maxSize) {
58966
- return `Dosya boyutu ${formatFileSize(maxSize)} limitini a\u015F\u0131yor`;
59009
+ return `File size exceeds ${formatFileSize(maxSize)} limit`;
58967
59010
  }
58968
59011
  if (allowedMimeTypes.length > 0 && !allowedMimeTypes.includes(file.type)) {
58969
- return `Dosya t\xFCr\xFC ${file.type} desteklenmiyor`;
59012
+ return `File type ${file.type} is not supported`;
58970
59013
  }
58971
59014
  if (accept !== "*") {
58972
59015
  const acceptTypes = accept.split(",").map((t2) => t2.trim());
@@ -58977,7 +59020,7 @@ var MoonUIFileUploadPro = t__default.forwardRef(
58977
59020
  return file.type === acceptType;
58978
59021
  });
58979
59022
  if (!isAccepted) {
58980
- return `Dosya t\xFCr\xFC kabul edilmiyor`;
59023
+ return `File type not accepted`;
58981
59024
  }
58982
59025
  }
58983
59026
  if (customValidation) {
@@ -59012,7 +59055,7 @@ var MoonUIFileUploadPro = t__default.forwardRef(
59012
59055
  };
59013
59056
  }
59014
59057
  } catch (error2) {
59015
- console.warn("\xD6nizleme olu\u015Fturulamad\u0131:", error2);
59058
+ console.warn("Failed to create preview:", error2);
59016
59059
  }
59017
59060
  return void 0;
59018
59061
  }, [showPreview, previewTypes]);
@@ -59028,6 +59071,90 @@ var MoonUIFileUploadPro = t__default.forwardRef(
59028
59071
  }
59029
59072
  return metadata;
59030
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
+ const resizeImage = useCallback(async (file) => {
59112
+ if (!imageResize || !file.type.startsWith("image/"))
59113
+ return file;
59114
+ const { maxWidth, maxHeight, quality } = imageResize;
59115
+ return new Promise((resolve) => {
59116
+ const img = new Image();
59117
+ const reader = new FileReader();
59118
+ reader.onload = (e) => {
59119
+ img.src = e.target?.result;
59120
+ img.onload = () => {
59121
+ const canvas = document.createElement("canvas");
59122
+ const ctx = canvas.getContext("2d");
59123
+ let width = img.width;
59124
+ let height = img.height;
59125
+ if (width > maxWidth || height > maxHeight) {
59126
+ const aspectRatio = width / height;
59127
+ if (width > height) {
59128
+ width = maxWidth;
59129
+ height = maxWidth / aspectRatio;
59130
+ } else {
59131
+ height = maxHeight;
59132
+ width = maxHeight * aspectRatio;
59133
+ }
59134
+ }
59135
+ canvas.width = width;
59136
+ canvas.height = height;
59137
+ ctx.drawImage(img, 0, 0, width, height);
59138
+ canvas.toBlob(
59139
+ (blob) => {
59140
+ if (blob) {
59141
+ const resizedFile = new window.File([blob], file.name, {
59142
+ type: file.type,
59143
+ lastModified: Date.now()
59144
+ });
59145
+ resolve(resizedFile);
59146
+ } else {
59147
+ resolve(file);
59148
+ }
59149
+ },
59150
+ file.type,
59151
+ quality
59152
+ );
59153
+ };
59154
+ };
59155
+ reader.readAsDataURL(file);
59156
+ });
59157
+ }, [imageResize]);
59031
59158
  const uploadFileChunked = useCallback(async (fileItem) => {
59032
59159
  const { file } = fileItem;
59033
59160
  const chunks = [];
@@ -59053,7 +59180,10 @@ var MoonUIFileUploadPro = t__default.forwardRef(
59053
59180
  if (abortController.signal.aborted) {
59054
59181
  throw new Error("Upload cancelled");
59055
59182
  }
59056
- await new Promise((resolve) => setTimeout(resolve, 100 + Math.random() * 200));
59183
+ const serviceResult = await uploadToService(fileItem);
59184
+ if (!serviceResult) {
59185
+ await new Promise((resolve) => setTimeout(resolve, 100 + Math.random() * 200));
59186
+ }
59057
59187
  uploadedBytes += chunk.end - chunk.start;
59058
59188
  const progress = Math.round(uploadedBytes / file.size * 100);
59059
59189
  const elapsedTime = (Date.now() - startTime) / 1e3;
@@ -59090,14 +59220,14 @@ var MoonUIFileUploadPro = t__default.forwardRef(
59090
59220
  const fileArray = Array.from(fileList);
59091
59221
  setError(null);
59092
59222
  if (files.length + fileArray.length > maxFiles) {
59093
- setError(`Maksimum ${maxFiles} dosya y\xFCkleyebilirsiniz`);
59223
+ setError(`Maximum ${maxFiles} files allowed`);
59094
59224
  return;
59095
59225
  }
59096
59226
  if (maxTotalSize) {
59097
59227
  const currentSize = files.reduce((sum, f) => sum + f.file.size, 0);
59098
59228
  const newSize = fileArray.reduce((sum, f) => sum + f.size, 0);
59099
59229
  if (currentSize + newSize > maxTotalSize) {
59100
- setError(`Toplam dosya boyutu ${formatFileSize(maxTotalSize)} limitini a\u015F\u0131yor`);
59230
+ setError(`Total file size exceeds ${formatFileSize(maxTotalSize)} limit`);
59101
59231
  return;
59102
59232
  }
59103
59233
  }
@@ -59111,7 +59241,7 @@ var MoonUIFileUploadPro = t__default.forwardRef(
59111
59241
  }
59112
59242
  const isDuplicate = await checkDuplicate(file);
59113
59243
  if (isDuplicate) {
59114
- errors.push(`${file.name}: Dosya zaten mevcut`);
59244
+ errors.push(`${file.name}: File already exists`);
59115
59245
  continue;
59116
59246
  }
59117
59247
  validFiles.push(file);
@@ -59124,14 +59254,15 @@ var MoonUIFileUploadPro = t__default.forwardRef(
59124
59254
  const newFileItems = [];
59125
59255
  for (const file of validFiles) {
59126
59256
  const id = `${Date.now()}-${Math.random().toString(36).substring(2, 11)}`;
59127
- const preview = await createPreview(file);
59128
- const metadata = await createMetadata(file);
59257
+ const processedFile = await resizeImage(file);
59258
+ const preview = await createPreview(processedFile);
59259
+ const metadata = await createMetadata(processedFile);
59129
59260
  newFileItems.push({
59130
59261
  id,
59131
- file,
59262
+ file: processedFile,
59132
59263
  status: "pending",
59133
59264
  progress: 0,
59134
- totalBytes: file.size,
59265
+ totalBytes: processedFile.size,
59135
59266
  preview,
59136
59267
  metadata
59137
59268
  });
@@ -59157,7 +59288,8 @@ var MoonUIFileUploadPro = t__default.forwardRef(
59157
59288
  createPreview,
59158
59289
  createMetadata,
59159
59290
  onUpload,
59160
- uploadFileChunked
59291
+ uploadFileChunked,
59292
+ resizeImage
59161
59293
  ]);
59162
59294
  const handleDrop = useCallback((e) => {
59163
59295
  e.preventDefault();
@@ -59298,28 +59430,28 @@ var MoonUIFileUploadPro = t__default.forwardRef(
59298
59430
  }
59299
59431
  ),
59300
59432
  /* @__PURE__ */ jsxs("div", { children: [
59301
- /* @__PURE__ */ jsx("h3", { className: "text-lg font-semibold", children: isDragOver ? "Dosyalar\u0131 buraya b\u0131rak\u0131n" : "Dosya Y\xFCkleyin" }),
59302
- /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground mt-1", children: "Dosyalar\u0131 s\xFCr\xFCkleyip b\u0131rak\u0131n veya t\u0131klayarak se\xE7in" }),
59433
+ /* @__PURE__ */ jsx("h3", { className: "text-lg font-semibold", children: isDragOver ? "Drop files here" : "Upload Files" }),
59434
+ /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground mt-1", children: "Drag and drop files or click to select" }),
59303
59435
  /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-center gap-4 mt-3 text-xs text-muted-foreground", children: [
59304
59436
  /* @__PURE__ */ jsxs("span", { children: [
59305
- "Maks ",
59437
+ "Max ",
59306
59438
  maxFiles,
59307
- " dosya"
59439
+ " files"
59308
59440
  ] }),
59309
59441
  /* @__PURE__ */ jsx("span", { children: "\u2022" }),
59310
59442
  /* @__PURE__ */ jsxs("span", { children: [
59311
59443
  formatFileSize(maxSize),
59312
- " her dosya"
59444
+ " per file"
59313
59445
  ] }),
59314
59446
  resumable && /* @__PURE__ */ jsxs(Fragment, { children: [
59315
59447
  /* @__PURE__ */ jsx("span", { children: "\u2022" }),
59316
- /* @__PURE__ */ jsx("span", { children: "Devam ettirilebilir" })
59448
+ /* @__PURE__ */ jsx("span", { children: "Resumable" })
59317
59449
  ] })
59318
59450
  ] })
59319
59451
  ] }),
59320
59452
  /* @__PURE__ */ jsxs(MoonUIButtonPro, { variant: "outline", disabled, type: "button", children: [
59321
59453
  /* @__PURE__ */ jsx(Upload, { className: "mr-2 h-4 w-4" }),
59322
- "Dosya Se\xE7"
59454
+ "Select Files"
59323
59455
  ] })
59324
59456
  ] })
59325
59457
  ]
@@ -59358,7 +59490,7 @@ var MoonUIFileUploadPro = t__default.forwardRef(
59358
59490
  children: [
59359
59491
  /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
59360
59492
  /* @__PURE__ */ jsxs("h4", { className: "text-sm font-medium", children: [
59361
- "Y\xFCklenen Dosyalar (",
59493
+ "Uploaded Files (",
59362
59494
  files.length,
59363
59495
  ")"
59364
59496
  ] }),
@@ -59368,7 +59500,7 @@ var MoonUIFileUploadPro = t__default.forwardRef(
59368
59500
  variant: "ghost",
59369
59501
  size: "sm",
59370
59502
  onClick: selectedIds.length === files.length ? handleClearSelection : handleSelectAll,
59371
- children: selectedIds.length === files.length ? "Se\xE7imi Temizle" : "T\xFCm\xFCn\xFC Se\xE7"
59503
+ children: selectedIds.length === files.length ? "Clear Selection" : "Select All"
59372
59504
  }
59373
59505
  )
59374
59506
  ] }),
@@ -59522,7 +59654,7 @@ var FileUploadItem = ({
59522
59654
  file.status === "success" && /* @__PURE__ */ jsx(CheckCircle2, { className: "h-3 w-3 mr-1" }),
59523
59655
  file.status === "error" && /* @__PURE__ */ jsx(AlertCircle, { className: "h-3 w-3 mr-1" }),
59524
59656
  file.status === "paused" && /* @__PURE__ */ jsx(Pause, { className: "h-3 w-3 mr-1" }),
59525
- file.status === "pending" ? "Bekliyor" : file.status === "uploading" ? "Y\xFCkleniyor" : file.status === "paused" ? "Duraklat\u0131ld\u0131" : file.status === "success" ? "Tamamland\u0131" : file.status === "error" ? "Hata" : "\u0130ptal"
59657
+ file.status === "pending" ? "Pending" : file.status === "uploading" ? "Uploading" : file.status === "paused" ? "Paused" : file.status === "success" ? "Completed" : file.status === "error" ? "Error" : "Cancelled"
59526
59658
  ]
59527
59659
  }
59528
59660
  )
@@ -59539,7 +59671,7 @@ var FileUploadItem = ({
59539
59671
  ] }),
59540
59672
  file.estimatedTime && file.estimatedTime > 0 && /* @__PURE__ */ jsxs("span", { children: [
59541
59673
  formatTime(file.estimatedTime),
59542
- " kald\u0131"
59674
+ " remaining"
59543
59675
  ] })
59544
59676
  ] }),
59545
59677
  /* @__PURE__ */ jsx(MoonUIProgressPro, { value: file.progress, className: "h-1" })
@@ -59583,19 +59715,19 @@ var FileUploadItem = ({
59583
59715
  /* @__PURE__ */ jsxs(MoonUIDropdownMenuContentPro, { align: "end", children: [
59584
59716
  canPreview && /* @__PURE__ */ jsxs(MoonUIDropdownMenuItemPro, { onClick: onPreview, children: [
59585
59717
  /* @__PURE__ */ jsx(Eye, { className: "mr-2 h-4 w-4" }),
59586
- "\xD6nizle"
59718
+ "Preview"
59587
59719
  ] }),
59588
59720
  /* @__PURE__ */ jsxs(MoonUIDropdownMenuItemPro, { children: [
59589
59721
  /* @__PURE__ */ jsx(Copy, { className: "mr-2 h-4 w-4" }),
59590
- "Linki Kopyala"
59722
+ "Copy Link"
59591
59723
  ] }),
59592
59724
  /* @__PURE__ */ jsxs(MoonUIDropdownMenuItemPro, { children: [
59593
59725
  /* @__PURE__ */ jsx(Share, { className: "mr-2 h-4 w-4" }),
59594
- "Payla\u015F"
59726
+ "Share"
59595
59727
  ] }),
59596
59728
  /* @__PURE__ */ jsxs(MoonUIDropdownMenuItemPro, { onClick: onRemove, className: "text-destructive", children: [
59597
59729
  /* @__PURE__ */ jsx(Trash2, { className: "mr-2 h-4 w-4" }),
59598
- "Sil"
59730
+ "Delete"
59599
59731
  ] })
59600
59732
  ] })
59601
59733
  ] })
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@moontra/moonui-pro",
3
- "version": "2.11.0",
3
+ "version": "2.11.2",
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",
@@ -78,6 +78,28 @@ export interface FileUploadProProps {
78
78
  endpoint?: string
79
79
  headers?: Record<string, string>
80
80
 
81
+ // Service integrations
82
+ serviceConfig?: {
83
+ type: 'aws-s3' | 'cloudinary' | 'firebase' | 'custom'
84
+ awsConfig?: {
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
+ }
101
+ }
102
+
81
103
  // Callbacks
82
104
  onUpload?: (files: FileUploadItem[]) => Promise<void>
83
105
  onProgress?: (fileId: string, progress: number) => void
@@ -200,26 +222,67 @@ const generateFileHash = async (file: File): Promise<string> => {
200
222
 
201
223
  const createImagePreview = (file: File): Promise<FilePreview> => {
202
224
  return new Promise((resolve) => {
203
- const img = new Image()
204
- const url = URL.createObjectURL(file)
225
+ const reader = new FileReader()
205
226
 
206
- img.onload = () => {
207
- resolve({
208
- type: 'image',
209
- url,
210
- thumbnail: url,
211
- dimensions: { width: img.width, height: img.height }
212
- })
227
+ reader.onload = (e) => {
228
+ const result = e.target?.result as string
229
+ const img = new Image()
230
+
231
+ img.onload = () => {
232
+ // Create high-quality thumbnail
233
+ const canvas = document.createElement('canvas')
234
+ const ctx = canvas.getContext('2d')!
235
+
236
+ // Calculate thumbnail dimensions (max 400px)
237
+ const maxThumbSize = 400
238
+ let thumbWidth = img.width
239
+ let thumbHeight = img.height
240
+
241
+ if (thumbWidth > maxThumbSize || thumbHeight > maxThumbSize) {
242
+ const aspectRatio = img.width / img.height
243
+ if (aspectRatio > 1) {
244
+ thumbWidth = maxThumbSize
245
+ thumbHeight = maxThumbSize / aspectRatio
246
+ } else {
247
+ thumbHeight = maxThumbSize
248
+ thumbWidth = maxThumbSize * aspectRatio
249
+ }
250
+ }
251
+
252
+ canvas.width = thumbWidth
253
+ canvas.height = thumbHeight
254
+ ctx.drawImage(img, 0, 0, thumbWidth, thumbHeight)
255
+
256
+ const thumbnail = canvas.toDataURL('image/jpeg', 0.9)
257
+
258
+ resolve({
259
+ type: 'image',
260
+ url: result,
261
+ thumbnail,
262
+ dimensions: { width: img.width, height: img.height }
263
+ })
264
+ }
265
+
266
+ img.onerror = () => {
267
+ resolve({
268
+ type: 'image',
269
+ url: result,
270
+ thumbnail: result
271
+ })
272
+ }
273
+
274
+ img.src = result
213
275
  }
214
276
 
215
- img.onerror = () => {
277
+ reader.onerror = () => {
278
+ const url = URL.createObjectURL(file)
216
279
  resolve({
217
280
  type: 'image',
218
281
  url
219
282
  })
220
283
  }
221
284
 
222
- img.src = url
285
+ reader.readAsDataURL(file)
223
286
  })
224
287
  }
225
288
 
@@ -284,15 +347,15 @@ const BulkActions = ({
284
347
  exit={{ opacity: 0, y: -10 }}
285
348
  className="flex items-center gap-2 p-3 bg-primary/5 rounded-lg border"
286
349
  >
287
- <span className="text-sm font-medium">{selectedIds.length} dosya seçildi</span>
350
+ <span className="text-sm font-medium">{selectedIds.length} files selected</span>
288
351
  <div className="flex gap-1 ml-auto">
289
352
  <Button variant="ghost" size="sm" onClick={onBulkDownload}>
290
353
  <Download className="h-4 w-4 mr-1" />
291
- İndir
354
+ Download
292
355
  </Button>
293
356
  <Button variant="ghost" size="sm" onClick={onBulkRemove}>
294
357
  <Trash2 className="h-4 w-4 mr-1" />
295
- Sil
358
+ Delete
296
359
  </Button>
297
360
  <Button variant="ghost" size="sm" onClick={onClearSelection}>
298
361
  <X className="h-4 w-4" />
@@ -329,40 +392,53 @@ const FilePreviewModal = ({
329
392
 
330
393
  <div className="flex items-center justify-center p-4 bg-muted/20 rounded-lg">
331
394
  {file.preview.type === 'image' && (
332
- <img
333
- src={file.preview.url}
334
- alt={file.file.name}
335
- className="max-w-full max-h-[60vh] object-contain rounded"
336
- />
395
+ <div className="relative">
396
+ <img
397
+ src={file.preview.url}
398
+ alt={file.file.name}
399
+ className="max-w-full max-h-[60vh] object-contain rounded shadow-lg"
400
+ loading="eager"
401
+ />
402
+ {/* Image loading indicator */}
403
+ <div className="absolute inset-0 flex items-center justify-center bg-background/80 rounded opacity-0 pointer-events-none transition-opacity duration-300">
404
+ <Loader2 className="h-8 w-8 animate-spin text-muted-foreground" />
405
+ </div>
406
+ </div>
337
407
  )}
338
408
 
339
409
  {file.preview.type === 'video' && (
340
- <video
341
- src={file.preview.url}
342
- controls
343
- className="max-w-full max-h-[60vh] rounded"
344
- />
410
+ <div className="relative w-full max-w-4xl">
411
+ <video
412
+ src={file.preview.url}
413
+ controls
414
+ className="w-full max-h-[60vh] rounded shadow-lg"
415
+ poster={file.preview.thumbnail}
416
+ />
417
+ </div>
345
418
  )}
346
419
 
347
420
  {file.preview.type === 'audio' && (
348
- <div className="w-full max-w-md">
349
- <audio src={file.preview.url} controls className="w-full" />
421
+ <div className="w-full max-w-md space-y-4">
422
+ <div className="p-8 bg-gradient-to-br from-primary/10 to-primary/5 rounded-lg">
423
+ <Music className="h-16 w-16 mx-auto text-primary mb-4" />
424
+ <audio src={file.preview.url} controls className="w-full" />
425
+ </div>
350
426
  </div>
351
427
  )}
352
428
 
353
429
  {!['image', 'video', 'audio'].includes(file.preview.type) && (
354
430
  <div className="text-center py-8">
355
431
  {getFileIcon(file.file.type, 'lg')}
356
- <p className="mt-2 text-muted-foreground">Önizleme mevcut değil</p>
432
+ <p className="mt-2 text-muted-foreground">Preview not available</p>
357
433
  </div>
358
434
  )}
359
435
  </div>
360
436
 
361
437
  {file.preview.dimensions && (
362
438
  <div className="flex gap-4 text-sm text-muted-foreground">
363
- <span>Boyutlar: {file.preview.dimensions.width} × {file.preview.dimensions.height}</span>
439
+ <span>Dimensions: {file.preview.dimensions.width} × {file.preview.dimensions.height}</span>
364
440
  {file.preview.duration && (
365
- <span>Süre: {formatTime(file.preview.duration)}</span>
441
+ <span>Duration: {formatTime(file.preview.duration)}</span>
366
442
  )}
367
443
  </div>
368
444
  )}
@@ -401,6 +477,10 @@ export const MoonUIFileUploadPro = React.forwardRef<HTMLDivElement, FileUploadPr
401
477
  onPreview,
402
478
  onBulkSelect,
403
479
  customValidation,
480
+ serviceConfig,
481
+ imageResize,
482
+ endpoint,
483
+ headers,
404
484
  ...props
405
485
  }, ref) => {
406
486
 
@@ -428,15 +508,15 @@ export const MoonUIFileUploadPro = React.forwardRef<HTMLDivElement, FileUploadPr
428
508
  <Lock className="h-6 w-6 text-purple-600 dark:text-purple-400" />
429
509
  </div>
430
510
  <div>
431
- <h3 className="font-semibold text-lg mb-2">Pro Özellik</h3>
511
+ <h3 className="font-semibold text-lg mb-2">Pro Feature</h3>
432
512
  <p className="text-muted-foreground text-sm mb-4">
433
- Gelişmiş Dosya Yükleme sadece MoonUI Pro abonelerine özeldir.
513
+ Advanced File Upload is exclusive to MoonUI Pro subscribers.
434
514
  </p>
435
515
  <div className="flex gap-3 justify-center">
436
516
  <a href="/pricing">
437
517
  <Button size="sm">
438
518
  <Sparkles className="mr-2 h-4 w-4" />
439
- Pro'ya Yükseltin
519
+ Upgrade to Pro
440
520
  </Button>
441
521
  </a>
442
522
  </div>
@@ -451,12 +531,12 @@ export const MoonUIFileUploadPro = React.forwardRef<HTMLDivElement, FileUploadPr
451
531
  const validateFile = useCallback(async (file: File): Promise<string | null> => {
452
532
  // Boyut kontrolü
453
533
  if (file.size > maxSize) {
454
- return `Dosya boyutu ${formatFileSize(maxSize)} limitini aşıyor`
534
+ return `File size exceeds ${formatFileSize(maxSize)} limit`
455
535
  }
456
536
 
457
537
  // MIME type kontrolü
458
538
  if (allowedMimeTypes.length > 0 && !allowedMimeTypes.includes(file.type)) {
459
- return `Dosya türü ${file.type} desteklenmiyor`
539
+ return `File type ${file.type} is not supported`
460
540
  }
461
541
 
462
542
  // Accept kontrolü
@@ -470,7 +550,7 @@ export const MoonUIFileUploadPro = React.forwardRef<HTMLDivElement, FileUploadPr
470
550
  })
471
551
 
472
552
  if (!isAccepted) {
473
- return `Dosya türü kabul edilmiyor`
553
+ return `File type not accepted`
474
554
  }
475
555
  }
476
556
 
@@ -512,7 +592,7 @@ export const MoonUIFileUploadPro = React.forwardRef<HTMLDivElement, FileUploadPr
512
592
  }
513
593
  }
514
594
  } catch (error) {
515
- console.warn('Önizleme oluşturulamadı:', error)
595
+ console.warn('Failed to create preview:', error)
516
596
  }
517
597
 
518
598
  return undefined
@@ -534,6 +614,115 @@ export const MoonUIFileUploadPro = React.forwardRef<HTMLDivElement, FileUploadPr
534
614
  return metadata
535
615
  }, [duplicateCheck])
536
616
 
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
+ // Image resize handler
668
+ const resizeImage = useCallback(async (file: File): Promise<File> => {
669
+ if (!imageResize || !file.type.startsWith('image/')) return file
670
+
671
+ const { maxWidth, maxHeight, quality } = imageResize
672
+
673
+ return new Promise((resolve) => {
674
+ const img = new Image()
675
+ const reader = new FileReader()
676
+
677
+ reader.onload = (e) => {
678
+ img.src = e.target?.result as string
679
+
680
+ img.onload = () => {
681
+ const canvas = document.createElement('canvas')
682
+ const ctx = canvas.getContext('2d')!
683
+
684
+ let width = img.width
685
+ let height = img.height
686
+
687
+ // Calculate new dimensions
688
+ if (width > maxWidth || height > maxHeight) {
689
+ const aspectRatio = width / height
690
+ if (width > height) {
691
+ width = maxWidth
692
+ height = maxWidth / aspectRatio
693
+ } else {
694
+ height = maxHeight
695
+ width = maxHeight * aspectRatio
696
+ }
697
+ }
698
+
699
+ canvas.width = width
700
+ canvas.height = height
701
+ ctx.drawImage(img, 0, 0, width, height)
702
+
703
+ canvas.toBlob(
704
+ (blob) => {
705
+ if (blob) {
706
+ // Create a resized file
707
+ const resizedFile = new window.File([blob], file.name, {
708
+ type: file.type,
709
+ lastModified: Date.now()
710
+ })
711
+ resolve(resizedFile)
712
+ } else {
713
+ resolve(file)
714
+ }
715
+ },
716
+ file.type,
717
+ quality
718
+ )
719
+ }
720
+ }
721
+
722
+ reader.readAsDataURL(file)
723
+ })
724
+ }, [imageResize])
725
+
537
726
  // Chunked upload simülasyonu
538
727
  const uploadFileChunked = useCallback(async (fileItem: FileUploadItem) => {
539
728
  const { file } = fileItem
@@ -571,8 +760,13 @@ export const MoonUIFileUploadPro = React.forwardRef<HTMLDivElement, FileUploadPr
571
760
  throw new Error('Upload cancelled')
572
761
  }
573
762
 
574
- // Chunk upload simülasyonu
575
- await new Promise(resolve => setTimeout(resolve, 100 + Math.random() * 200))
763
+ // Try service-specific upload first
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
+ }
576
770
 
577
771
  uploadedBytes += (chunk.end - chunk.start)
578
772
  const progress = Math.round((uploadedBytes / file.size) * 100)
@@ -630,7 +824,7 @@ export const MoonUIFileUploadPro = React.forwardRef<HTMLDivElement, FileUploadPr
630
824
 
631
825
  // Toplam dosya sayısı kontrolü
632
826
  if (files.length + fileArray.length > maxFiles) {
633
- setError(`Maksimum ${maxFiles} dosya yükleyebilirsiniz`)
827
+ setError(`Maximum ${maxFiles} files allowed`)
634
828
  return
635
829
  }
636
830
 
@@ -640,7 +834,7 @@ export const MoonUIFileUploadPro = React.forwardRef<HTMLDivElement, FileUploadPr
640
834
  const newSize = fileArray.reduce((sum, f) => sum + f.size, 0)
641
835
 
642
836
  if (currentSize + newSize > maxTotalSize) {
643
- setError(`Toplam dosya boyutu ${formatFileSize(maxTotalSize)} limitini aşıyor`)
837
+ setError(`Total file size exceeds ${formatFileSize(maxTotalSize)} limit`)
644
838
  return
645
839
  }
646
840
  }
@@ -658,7 +852,7 @@ export const MoonUIFileUploadPro = React.forwardRef<HTMLDivElement, FileUploadPr
658
852
 
659
853
  const isDuplicate = await checkDuplicate(file)
660
854
  if (isDuplicate) {
661
- errors.push(`${file.name}: Dosya zaten mevcut`)
855
+ errors.push(`${file.name}: File already exists`)
662
856
  continue
663
857
  }
664
858
 
@@ -676,15 +870,19 @@ export const MoonUIFileUploadPro = React.forwardRef<HTMLDivElement, FileUploadPr
676
870
 
677
871
  for (const file of validFiles) {
678
872
  const id = `${Date.now()}-${Math.random().toString(36).substring(2, 11)}`
679
- const preview = await createPreview(file)
680
- const metadata = await createMetadata(file)
873
+
874
+ // Resize image if needed
875
+ const processedFile = await resizeImage(file)
876
+
877
+ const preview = await createPreview(processedFile)
878
+ const metadata = await createMetadata(processedFile)
681
879
 
682
880
  newFileItems.push({
683
881
  id,
684
- file,
882
+ file: processedFile,
685
883
  status: 'pending',
686
884
  progress: 0,
687
- totalBytes: file.size,
885
+ totalBytes: processedFile.size,
688
886
  preview,
689
887
  metadata
690
888
  })
@@ -714,7 +912,8 @@ export const MoonUIFileUploadPro = React.forwardRef<HTMLDivElement, FileUploadPr
714
912
  createPreview,
715
913
  createMetadata,
716
914
  onUpload,
717
- uploadFileChunked
915
+ uploadFileChunked,
916
+ resizeImage
718
917
  ])
719
918
 
720
919
  // Drag & Drop handlers
@@ -886,19 +1085,19 @@ export const MoonUIFileUploadPro = React.forwardRef<HTMLDivElement, FileUploadPr
886
1085
 
887
1086
  <div>
888
1087
  <h3 className="text-lg font-semibold">
889
- {isDragOver ? 'Dosyaları buraya bırakın' : 'Dosya Yükleyin'}
1088
+ {isDragOver ? 'Drop files here' : 'Upload Files'}
890
1089
  </h3>
891
1090
  <p className="text-sm text-muted-foreground mt-1">
892
- Dosyaları sürükleyip bırakın veya tıklayarak seçin
1091
+ Drag and drop files or click to select
893
1092
  </p>
894
1093
  <div className="flex items-center justify-center gap-4 mt-3 text-xs text-muted-foreground">
895
- <span>Maks {maxFiles} dosya</span>
1094
+ <span>Max {maxFiles} files</span>
896
1095
  <span>•</span>
897
- <span>{formatFileSize(maxSize)} her dosya</span>
1096
+ <span>{formatFileSize(maxSize)} per file</span>
898
1097
  {resumable && (
899
1098
  <>
900
1099
  <span>•</span>
901
- <span>Devam ettirilebilir</span>
1100
+ <span>Resumable</span>
902
1101
  </>
903
1102
  )}
904
1103
  </div>
@@ -906,7 +1105,7 @@ export const MoonUIFileUploadPro = React.forwardRef<HTMLDivElement, FileUploadPr
906
1105
 
907
1106
  <Button variant="outline" disabled={disabled} type="button">
908
1107
  <Upload className="mr-2 h-4 w-4" />
909
- Dosya Seç
1108
+ Select Files
910
1109
  </Button>
911
1110
  </div>
912
1111
  </motion.div>
@@ -946,7 +1145,7 @@ export const MoonUIFileUploadPro = React.forwardRef<HTMLDivElement, FileUploadPr
946
1145
  {/* List Header */}
947
1146
  <div className="flex items-center justify-between">
948
1147
  <h4 className="text-sm font-medium">
949
- Yüklenen Dosyalar ({files.length})
1148
+ Uploaded Files ({files.length})
950
1149
  </h4>
951
1150
  {allowBulkOperations && files.length > 1 && (
952
1151
  <Button
@@ -954,7 +1153,7 @@ export const MoonUIFileUploadPro = React.forwardRef<HTMLDivElement, FileUploadPr
954
1153
  size="sm"
955
1154
  onClick={selectedIds.length === files.length ? handleClearSelection : handleSelectAll}
956
1155
  >
957
- {selectedIds.length === files.length ? 'Seçimi Temizle' : 'Tümünü Seç'}
1156
+ {selectedIds.length === files.length ? 'Clear Selection' : 'Select All'}
958
1157
  </Button>
959
1158
  )}
960
1159
  </div>
@@ -1152,11 +1351,11 @@ const FileUploadItem = ({
1152
1351
  {file.status === 'success' && <CheckCircle2 className="h-3 w-3 mr-1" />}
1153
1352
  {file.status === 'error' && <AlertCircle className="h-3 w-3 mr-1" />}
1154
1353
  {file.status === 'paused' && <Pause className="h-3 w-3 mr-1" />}
1155
- {file.status === 'pending' ? 'Bekliyor' :
1156
- file.status === 'uploading' ? 'Yükleniyor' :
1157
- file.status === 'paused' ? 'Duraklatıldı' :
1158
- file.status === 'success' ? 'Tamamlandı' :
1159
- file.status === 'error' ? 'Hata' : 'İptal'}
1354
+ {file.status === 'pending' ? 'Pending' :
1355
+ file.status === 'uploading' ? 'Uploading' :
1356
+ file.status === 'paused' ? 'Paused' :
1357
+ file.status === 'success' ? 'Completed' :
1358
+ file.status === 'error' ? 'Error' : 'Cancelled'}
1160
1359
  </Badge>
1161
1360
  </div>
1162
1361
 
@@ -1169,7 +1368,7 @@ const FileUploadItem = ({
1169
1368
  <span>{formatFileSize(file.speed)}/s</span>
1170
1369
  )}
1171
1370
  {file.estimatedTime && file.estimatedTime > 0 && (
1172
- <span>{formatTime(file.estimatedTime)} kaldı</span>
1371
+ <span>{formatTime(file.estimatedTime)} remaining</span>
1173
1372
  )}
1174
1373
  </div>
1175
1374
  <Progress value={file.progress} className="h-1" />
@@ -1232,20 +1431,20 @@ const FileUploadItem = ({
1232
1431
  {canPreview && (
1233
1432
  <DropdownMenuItem onClick={onPreview}>
1234
1433
  <Eye className="mr-2 h-4 w-4" />
1235
- Önizle
1434
+ Preview
1236
1435
  </DropdownMenuItem>
1237
1436
  )}
1238
1437
  <DropdownMenuItem>
1239
1438
  <Copy className="mr-2 h-4 w-4" />
1240
- Linki Kopyala
1439
+ Copy Link
1241
1440
  </DropdownMenuItem>
1242
1441
  <DropdownMenuItem>
1243
1442
  <Share className="mr-2 h-4 w-4" />
1244
- Paylaş
1443
+ Share
1245
1444
  </DropdownMenuItem>
1246
1445
  <DropdownMenuItem onClick={onRemove} className="text-destructive">
1247
1446
  <Trash2 className="mr-2 h-4 w-4" />
1248
- Sil
1447
+ Delete
1249
1448
  </DropdownMenuItem>
1250
1449
  </DropdownMenuContent>
1251
1450
  </DropdownMenu>