mirror of
https://github.com/kyantech/Palmr.git
synced 2025-10-23 06:11:58 +00:00
Feat: QR Code implementation (#159)
This commit is contained in:
@@ -327,7 +327,12 @@
|
||||
"copyButton": "نسخ الرابط",
|
||||
"success": "تم إنشاء الرابط بنجاح",
|
||||
"error": "فشل في إنشاء الرابط",
|
||||
"copied": "تم نسخ الرابط إلى الحافظة"
|
||||
"copied": "تم نسخ الرابط إلى الحافظة",
|
||||
"readyDescription": "رابط المشاركة الخاص بك جاهز. يمكنك مسح رمز QR مباشرة، أو تنزيله للاستخدام لاحقًا، أو نسخ الرابط أدناه.",
|
||||
"tabs": {
|
||||
"link": "الرابط",
|
||||
"qrcode": "رمز QR"
|
||||
}
|
||||
},
|
||||
"home": {
|
||||
"description": "البديل مفتوح المصدر لـ WeTransfer. شارك ملفاتك بأمان، دون تتبع أو قيود.",
|
||||
@@ -355,6 +360,12 @@
|
||||
"stats": "{iconCount} أيقونة من {libraryCount} مكتبة",
|
||||
"categoryBadge": "{category} ({count} أيقونات)"
|
||||
},
|
||||
"imageEdit": {
|
||||
"title": "تعديل الصورة",
|
||||
"rotate": "تدوير",
|
||||
"zoom": "تكبير/تصغير",
|
||||
"cropInstructions": "اسحب لإعادة تحديد الموضع، غير حجم الزوايا لضبط منطقة القص"
|
||||
},
|
||||
"login": {
|
||||
"welcome": "مرحبا بك",
|
||||
"signInToContinue": "قم بتسجيل الدخول للمتابعة",
|
||||
@@ -613,11 +624,12 @@
|
||||
"createLink": "إنشاء رابط",
|
||||
"delete": "حذف",
|
||||
"copyLinkTitle": "نسخ الرابط",
|
||||
"createLinkCTA": "إنشاء رابط استلام"
|
||||
"createLinkCTA": "إنشاء رابط استلام",
|
||||
"viewQrCode": "عرض رمز QR"
|
||||
},
|
||||
"status": {
|
||||
"active": "نشط",
|
||||
"inactive": "غير نشط",
|
||||
"inactive": "غير نشط",
|
||||
"expired": "منتهي الصلاحية",
|
||||
"protected": "محمي",
|
||||
"public": "عام"
|
||||
@@ -629,7 +641,8 @@
|
||||
"viewDetails": "عرض التفاصيل",
|
||||
"edit": "تحرير",
|
||||
"delete": "حذف",
|
||||
"viewFiles": "الملفات المستلمة"
|
||||
"viewFiles": "الملفات المستلمة",
|
||||
"viewQrCode": "عرض رمز QR"
|
||||
},
|
||||
"empty": {
|
||||
"title": "لم يتم إنشاء روابط استلام",
|
||||
@@ -1232,7 +1245,10 @@
|
||||
"invalidDate": "تاريخ غير صحيح",
|
||||
"loadError": "فشل في تحميل تفاصيل المشاركة",
|
||||
"editSecurity": "تحرير الأمان",
|
||||
"editExpiration": "تحرير انتهاء الصلاحية"
|
||||
"editExpiration": "تحرير انتهاء الصلاحية",
|
||||
"clickToEnlargeQrCode": "انقر لتكبير رمز QR",
|
||||
"downloadQrCode": "تحميل رمز QR",
|
||||
"qrCode": "رمز QR"
|
||||
},
|
||||
"shareExpiration": {
|
||||
"neverExpires": "لا تنتهي صلاحيته أبداً",
|
||||
@@ -1419,7 +1435,8 @@
|
||||
"copyLink": "نسخ الرابط",
|
||||
"notifyRecipients": "إشعار المستقبلين",
|
||||
"delete": "حذف",
|
||||
"downloadShareFiles": "قم بتنزيل جميع الملفات"
|
||||
"downloadShareFiles": "قم بتنزيل جميع الملفات",
|
||||
"viewQrCode": "عرض رمز QR"
|
||||
},
|
||||
"bulkActions": {
|
||||
"delete": "حذف",
|
||||
@@ -1722,10 +1739,9 @@
|
||||
"nameRequired": "الاسم مطلوب",
|
||||
"required": "هذا الحقل مطلوب"
|
||||
},
|
||||
"imageEdit": {
|
||||
"title": "تعديل الصورة",
|
||||
"rotate": "تدوير",
|
||||
"zoom": "تكبير/تصغير",
|
||||
"cropInstructions": "اسحب لإعادة تحديد الموضع، غير حجم الزوايا لضبط منطقة القص"
|
||||
"qrCodeModal": {
|
||||
"title": "مشاركة رمز QR",
|
||||
"description": "امسح رمز QR هذا للوصول إلى الرابط.",
|
||||
"download": "تحميل رمز QR"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -327,7 +327,12 @@
|
||||
"copyButton": "Link kopieren",
|
||||
"success": "Link erfolgreich generiert",
|
||||
"error": "Fehler beim Generieren des Links",
|
||||
"copied": "Link in die Zwischenablage kopiert"
|
||||
"copied": "Link in die Zwischenablage kopiert",
|
||||
"readyDescription": "Ihr Freigabe-Link ist bereit. Sie können den QR-Code direkt scannen, ihn für die spätere Verwendung herunterladen oder den Link unten kopieren.",
|
||||
"tabs": {
|
||||
"link": "Link",
|
||||
"qrcode": "QR-Code"
|
||||
}
|
||||
},
|
||||
"home": {
|
||||
"description": "Die Open-Source-Alternative zu WeTransfer. Teilen Sie Dateien sicher, ohne Tracking oder Einschränkungen.",
|
||||
@@ -355,6 +360,12 @@
|
||||
"stats": "{iconCount} Symbole aus {libraryCount} Bibliotheken",
|
||||
"categoryBadge": "{category} ({count} Symbole)"
|
||||
},
|
||||
"imageEdit": {
|
||||
"title": "Bild bearbeiten",
|
||||
"rotate": "Drehen",
|
||||
"zoom": "Zoom",
|
||||
"cropInstructions": "Ziehen Sie, um die Position zu ändern, ändern Sie die Größe der Ecken, um die Zuschneidefläche anzupassen"
|
||||
},
|
||||
"login": {
|
||||
"welcome": "Willkommen zu",
|
||||
"signInToContinue": "Melden Sie sich an, um fortzufahren",
|
||||
@@ -613,10 +624,11 @@
|
||||
"createLink": "Link erstellen",
|
||||
"delete": "Löschen",
|
||||
"copyLinkTitle": "Link kopieren",
|
||||
"createLinkCTA": "Empfangslink erstellen"
|
||||
"createLinkCTA": "Empfangslink erstellen",
|
||||
"viewQrCode": "QR-Code anzeigen"
|
||||
},
|
||||
"status": {
|
||||
"active": "Aktiv",
|
||||
"active": "Aktiv",
|
||||
"inactive": "Inaktiv",
|
||||
"expired": "Abgelaufen",
|
||||
"protected": "Geschützt",
|
||||
@@ -624,12 +636,13 @@
|
||||
},
|
||||
"actions": {
|
||||
"copyLink": "Link kopieren",
|
||||
"editAlias": "Alias bearbeiten",
|
||||
"editAlias": "Alias bearbeiten",
|
||||
"createAlias": "Alias erstellen",
|
||||
"viewDetails": "Details anzeigen",
|
||||
"edit": "Bearbeiten",
|
||||
"delete": "Löschen",
|
||||
"viewFiles": "Empfangene Dateien"
|
||||
"viewFiles": "Empfangene Dateien",
|
||||
"viewQrCode": "QR-Code anzeigen"
|
||||
},
|
||||
"empty": {
|
||||
"title": "Keine Empfangslinks erstellt",
|
||||
@@ -1230,7 +1243,10 @@
|
||||
"invalidDate": "Ungültiges Datum",
|
||||
"loadError": "Fehler beim Laden der Freigabe-Details",
|
||||
"editSecurity": "Sicherheit bearbeiten",
|
||||
"editExpiration": "Ablauf bearbeiten"
|
||||
"editExpiration": "Ablauf bearbeiten",
|
||||
"clickToEnlargeQrCode": "Klicken Sie zum Vergrößern des QR-Codes",
|
||||
"downloadQrCode": "QR-Code herunterladen",
|
||||
"qrCode": "QR-Code"
|
||||
},
|
||||
"shareExpiration": {
|
||||
"neverExpires": "Läuft nie ab",
|
||||
@@ -1417,7 +1433,8 @@
|
||||
"copyLink": "Link Kopieren",
|
||||
"notifyRecipients": "Empfänger Benachrichtigen",
|
||||
"delete": "Löschen",
|
||||
"downloadShareFiles": "Laden Sie alle Dateien herunter"
|
||||
"downloadShareFiles": "Laden Sie alle Dateien herunter",
|
||||
"viewQrCode": "QR-Code anzeigen"
|
||||
},
|
||||
"bulkActions": {
|
||||
"delete": "Löschen",
|
||||
@@ -1720,10 +1737,9 @@
|
||||
"nameRequired": "Name ist erforderlich",
|
||||
"required": "Dieses Feld ist erforderlich"
|
||||
},
|
||||
"imageEdit": {
|
||||
"title": "Bild bearbeiten",
|
||||
"rotate": "Drehen",
|
||||
"zoom": "Zoom",
|
||||
"cropInstructions": "Ziehen Sie, um die Position zu ändern, ändern Sie die Größe der Ecken, um die Zuschneidefläche anzupassen"
|
||||
"qrCodeModal": {
|
||||
"title": "QR-Code teilen",
|
||||
"description": "Scannen Sie diesen QR-Code, um auf den Link zuzugreifen.",
|
||||
"download": "QR-Code herunterladen"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -318,16 +318,21 @@
|
||||
"generateShareLink": {
|
||||
"generateTitle": "Generate Share Link",
|
||||
"updateTitle": "Update Share Link",
|
||||
"generateDescription": "Generate a link to share your files",
|
||||
"updateDescription": "Update the alias for this share link",
|
||||
"aliasPlaceholder": "Enter alias",
|
||||
"linkReady": "Your share link is ready:",
|
||||
"generateDescription": "Generate a custom link for this share. You can customize the URL to make it more memorable.",
|
||||
"updateDescription": "Update the custom link for this share. You can customize the URL to make it more memorable.",
|
||||
"aliasPlaceholder": "Custom ID for link",
|
||||
"linkReady": "Your share link is ready. You can copy it now.",
|
||||
"readyDescription": "Your share link is ready. You can scan the QR code directly, download it for later use, or copy the link below.",
|
||||
"generateButton": "Generate Link",
|
||||
"updateButton": "Update Link",
|
||||
"copyButton": "Copy Link",
|
||||
"success": "Link generated successfully",
|
||||
"error": "Failed to generate link",
|
||||
"copied": "Link copied to clipboard"
|
||||
"copied": "Link copied to clipboard",
|
||||
"tabs": {
|
||||
"link": "Link",
|
||||
"qrcode": "QR Code"
|
||||
}
|
||||
},
|
||||
"home": {
|
||||
"description": "The open-source alternative to WeTransfer. Share files securely, without tracking or limitations.",
|
||||
@@ -355,6 +360,12 @@
|
||||
"stats": "{iconCount} icons from {libraryCount} libraries",
|
||||
"categoryBadge": "{category} ({count} icons)"
|
||||
},
|
||||
"imageEdit": {
|
||||
"title": "Edit Image",
|
||||
"rotate": "Rotate",
|
||||
"zoom": "Zoom",
|
||||
"cropInstructions": "Drag to reposition, resize corners to adjust crop area"
|
||||
},
|
||||
"login": {
|
||||
"welcome": "Welcome to",
|
||||
"signInToContinue": "Sign in to continue",
|
||||
@@ -400,12 +411,6 @@
|
||||
"navigation": {
|
||||
"dashboard": "Dashboard"
|
||||
},
|
||||
"imageEdit": {
|
||||
"title": "Edit Image",
|
||||
"rotate": "Rotate",
|
||||
"zoom": "Zoom",
|
||||
"cropInstructions": "Drag to reposition, resize corners to adjust crop area"
|
||||
},
|
||||
"profile": {
|
||||
"password": {
|
||||
"title": "Change Password",
|
||||
@@ -448,6 +453,11 @@
|
||||
},
|
||||
"pageTitle": "Profile"
|
||||
},
|
||||
"qrCodeModal": {
|
||||
"title": "Share QR Code",
|
||||
"description": "Scan this QR code to access the link.",
|
||||
"download": "Download QR Code"
|
||||
},
|
||||
"quickAccess": {
|
||||
"files": {
|
||||
"title": "My Files",
|
||||
@@ -613,6 +623,7 @@
|
||||
"expired": "Expired",
|
||||
"expires": "Expires",
|
||||
"viewDetails": "View details",
|
||||
"viewQrCode": "View QR Code",
|
||||
"copyLink": "Copy Link",
|
||||
"openInNewTab": "Open in New Tab",
|
||||
"editLink": "Edit Link",
|
||||
@@ -635,7 +646,8 @@
|
||||
"viewDetails": "View Details",
|
||||
"edit": "Edit",
|
||||
"delete": "Delete",
|
||||
"viewFiles": "Received Files"
|
||||
"viewFiles": "Received Files",
|
||||
"viewQrCode": "View QR Code"
|
||||
},
|
||||
"empty": {
|
||||
"title": "No receive links created",
|
||||
@@ -1206,34 +1218,36 @@
|
||||
},
|
||||
"shareDetails": {
|
||||
"title": "Share Details",
|
||||
"subtitle": "Detailed information about this share",
|
||||
"subtitle": "View and manage details for this share",
|
||||
"basicInfo": "Basic Information",
|
||||
"name": "Name",
|
||||
"description": "Description",
|
||||
"noDescription": "No description provided",
|
||||
"untitled": "Untitled",
|
||||
"shareLink": "Share Link",
|
||||
"editLink": "Edit Link",
|
||||
"generateLink": "Generate Link",
|
||||
"noLink": "No link generated yet",
|
||||
"copyLink": "Copy link",
|
||||
"openLink": "Open in new tab",
|
||||
"linkCopied": "Link copied to clipboard",
|
||||
"views": "Views",
|
||||
"dates": "Dates",
|
||||
"security": "Security",
|
||||
"files": "Files",
|
||||
"recipients": "Recipients",
|
||||
"views": "Views",
|
||||
"created": "Created",
|
||||
"expires": "Expires",
|
||||
"never": "Never",
|
||||
"security": "Security",
|
||||
"editSecurity": "Edit Security",
|
||||
"editExpiration": "Edit Expiration",
|
||||
"untitled": "Untitled Share",
|
||||
"noDescription": "No description",
|
||||
"notAvailable": "N/A",
|
||||
"invalidDate": "Invalid date",
|
||||
"passwordProtected": "Password Protected",
|
||||
"publicAccess": "Public Access",
|
||||
"maxViews": "Max Views:",
|
||||
"files": "Files",
|
||||
"recipients": "Recipients",
|
||||
"notAvailable": "N/A",
|
||||
"invalidDate": "Invalid date",
|
||||
"noLink": "No link generated",
|
||||
"generateLink": "Generate Link",
|
||||
"editLink": "Edit Link",
|
||||
"copyLink": "Copy Link",
|
||||
"openLink": "Open Link",
|
||||
"editSecurity": "Edit Security",
|
||||
"editExpiration": "Edit Expiration",
|
||||
"qrCode": "QR Code",
|
||||
"downloadQrCode": "Download QR Code",
|
||||
"clickToEnlargeQrCode": "Click to enlarge QR Code",
|
||||
"loadError": "Failed to load share details"
|
||||
},
|
||||
"shareExpiration": {
|
||||
@@ -1419,6 +1433,7 @@
|
||||
"generateLink": "Generate Link",
|
||||
"editLink": "Edit Link",
|
||||
"copyLink": "Copy Link",
|
||||
"viewQrCode": "View QR Code",
|
||||
"notifyRecipients": "Notify Recipients",
|
||||
"downloadShareFiles": "Download All Files",
|
||||
"delete": "Delete"
|
||||
|
@@ -327,7 +327,12 @@
|
||||
"copyButton": "Copiar enlace",
|
||||
"success": "Enlace generado exitosamente",
|
||||
"error": "Error al generar enlace",
|
||||
"copied": "Enlace copiado al portapapeles"
|
||||
"copied": "Enlace copiado al portapapeles",
|
||||
"readyDescription": "Tu enlace de compartir está listo. Puedes escanear el código QR directamente, descargarlo para usarlo más tarde, o copiar el enlace a continuación.",
|
||||
"tabs": {
|
||||
"link": "Enlace",
|
||||
"qrcode": "Código QR"
|
||||
}
|
||||
},
|
||||
"home": {
|
||||
"description": "La alternativa de código abierto a WeTransfer. Comparte archivos de forma segura, sin rastreo ni limitaciones.",
|
||||
@@ -355,6 +360,12 @@
|
||||
"stats": "{iconCount} iconos de {libraryCount} bibliotecas",
|
||||
"categoryBadge": "{category} ({count} iconos)"
|
||||
},
|
||||
"imageEdit": {
|
||||
"title": "Editar Imagen",
|
||||
"rotate": "Rotar",
|
||||
"zoom": "Zoom",
|
||||
"cropInstructions": "Arrastra para reubicar, ajusta las esquinas para ajustar el área de recorte"
|
||||
},
|
||||
"login": {
|
||||
"welcome": "Bienvenido a",
|
||||
"signInToContinue": "Inicia sesión para continuar",
|
||||
@@ -613,7 +624,8 @@
|
||||
"createLink": "Crear Enlace",
|
||||
"delete": "Eliminar",
|
||||
"copyLinkTitle": "Copiar enlace",
|
||||
"createLinkCTA": "Crear Enlace de Recepción"
|
||||
"createLinkCTA": "Crear Enlace de Recepción",
|
||||
"viewQrCode": "Ver Código QR"
|
||||
},
|
||||
"status": {
|
||||
"active": "Activo",
|
||||
@@ -629,7 +641,8 @@
|
||||
"viewDetails": "Ver Detalles",
|
||||
"edit": "Editar",
|
||||
"delete": "Eliminar",
|
||||
"viewFiles": "Archivos Recibidos"
|
||||
"viewFiles": "Archivos Recibidos",
|
||||
"viewQrCode": "Ver Código QR"
|
||||
},
|
||||
"empty": {
|
||||
"title": "Ningún enlace de recepción creado",
|
||||
@@ -1230,7 +1243,10 @@
|
||||
"invalidDate": "Fecha inválida",
|
||||
"loadError": "Error al cargar detalles del compartir",
|
||||
"editSecurity": "Editar Seguridad",
|
||||
"editExpiration": "Editar Expiración"
|
||||
"editExpiration": "Editar Expiración",
|
||||
"clickToEnlargeQrCode": "Haz clic para ampliar el Código QR",
|
||||
"downloadQrCode": "Descargar Código QR",
|
||||
"qrCode": "Código QR"
|
||||
},
|
||||
"shareExpiration": {
|
||||
"neverExpires": "Nunca Expira",
|
||||
@@ -1417,7 +1433,8 @@
|
||||
"copyLink": "Copiar Enlace",
|
||||
"notifyRecipients": "Notificar Destinatarios",
|
||||
"delete": "Eliminar",
|
||||
"downloadShareFiles": "Descargar todos los archivos"
|
||||
"downloadShareFiles": "Descargar todos los archivos",
|
||||
"viewQrCode": "Ver Código QR"
|
||||
},
|
||||
"bulkActions": {
|
||||
"delete": "Eliminar",
|
||||
@@ -1720,10 +1737,9 @@
|
||||
"nameRequired": "El nombre es obligatorio",
|
||||
"required": "Este campo es obligatorio"
|
||||
},
|
||||
"imageEdit": {
|
||||
"title": "Editar Imagen",
|
||||
"rotate": "Rotar",
|
||||
"zoom": "Zoom",
|
||||
"cropInstructions": "Arrastra para reubicar, ajusta las esquinas para ajustar el área de recorte"
|
||||
"qrCodeModal": {
|
||||
"title": "Compartir Código QR",
|
||||
"description": "Escanea este código QR para acceder al enlace.",
|
||||
"download": "Descargar Código QR"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -327,7 +327,12 @@
|
||||
"copyButton": "Copier le lien",
|
||||
"success": "Lien généré avec succès",
|
||||
"error": "Échec de la génération du lien",
|
||||
"copied": "Lien copié dans le presse-papiers"
|
||||
"copied": "Lien copié dans le presse-papiers",
|
||||
"readyDescription": "Votre lien de partage est prêt. Vous pouvez scanner le QR code directement, le télécharger pour une utilisation ultérieure, ou copier le lien ci-dessous.",
|
||||
"tabs": {
|
||||
"link": "Lien",
|
||||
"qrcode": "QR Code"
|
||||
}
|
||||
},
|
||||
"home": {
|
||||
"description": "L'alternative open-source à WeTransfer. Partagez des fichiers en toute sécurité, sans suivi ni limitations.",
|
||||
@@ -355,6 +360,12 @@
|
||||
"stats": "{iconCount} icônes de {libraryCount} bibliothèques",
|
||||
"categoryBadge": "{category} ({count} icônes)"
|
||||
},
|
||||
"imageEdit": {
|
||||
"title": "Modifier l'Image",
|
||||
"rotate": "Tourner",
|
||||
"zoom": "Zoom",
|
||||
"cropInstructions": "Glisser pour répositionner, redimensionner les coins pour ajuster la zone de découpe"
|
||||
},
|
||||
"login": {
|
||||
"welcome": "Bienvenue à",
|
||||
"signInToContinue": "Connectez-vous pour continuer",
|
||||
@@ -613,7 +624,8 @@
|
||||
"createLink": "Créer un Lien",
|
||||
"delete": "Supprimer",
|
||||
"copyLinkTitle": "Copier le lien",
|
||||
"createLinkCTA": "Créer un Lien de Réception"
|
||||
"createLinkCTA": "Créer un Lien de Réception",
|
||||
"viewQrCode": "Voir le QR Code"
|
||||
},
|
||||
"status": {
|
||||
"active": "Actif",
|
||||
@@ -629,7 +641,8 @@
|
||||
"viewDetails": "Voir les Détails",
|
||||
"edit": "Modifier",
|
||||
"delete": "Supprimer",
|
||||
"viewFiles": "Fichiers Reçus"
|
||||
"viewFiles": "Fichiers Reçus",
|
||||
"viewQrCode": "Voir le QR Code"
|
||||
},
|
||||
"empty": {
|
||||
"title": "Aucun lien de réception créé",
|
||||
@@ -1230,7 +1243,10 @@
|
||||
"invalidDate": "Date invalide",
|
||||
"loadError": "Échec du chargement des détails du partage",
|
||||
"editSecurity": "Modifier la Sécurité",
|
||||
"editExpiration": "Modifier l'Expiration"
|
||||
"editExpiration": "Modifier l'Expiration",
|
||||
"clickToEnlargeQrCode": "Cliquez pour agrandir le Code QR",
|
||||
"downloadQrCode": "Télécharger le Code QR",
|
||||
"qrCode": "Code QR"
|
||||
},
|
||||
"shareExpiration": {
|
||||
"neverExpires": "N'expire Jamais",
|
||||
@@ -1417,7 +1433,8 @@
|
||||
"copyLink": "Copier le Lien",
|
||||
"notifyRecipients": "Notifier les Destinataires",
|
||||
"delete": "Supprimer",
|
||||
"downloadShareFiles": "Télécharger tous les fichiers"
|
||||
"downloadShareFiles": "Télécharger tous les fichiers",
|
||||
"viewQrCode": "Voir le QR Code"
|
||||
},
|
||||
"bulkActions": {
|
||||
"delete": "Supprimer",
|
||||
@@ -1720,10 +1737,9 @@
|
||||
"nameRequired": "Nome é obrigatório",
|
||||
"required": "Este campo é obrigatório"
|
||||
},
|
||||
"imageEdit": {
|
||||
"title": "Modifier l'Image",
|
||||
"rotate": "Tourner",
|
||||
"zoom": "Zoom",
|
||||
"cropInstructions": "Glisser pour répositionner, redimensionner les coins pour ajuster la zone de découpe"
|
||||
"qrCodeModal": {
|
||||
"title": "Code QR de Partage",
|
||||
"description": "Scannez ce code QR pour accéder au lien.",
|
||||
"download": "Télécharger le Code QR"
|
||||
}
|
||||
}
|
@@ -327,7 +327,12 @@
|
||||
"copyButton": "लिंक कॉपी करें",
|
||||
"success": "लिंक सफलतापूर्वक उत्पन्न हुआ",
|
||||
"error": "लिंक उत्पन्न करने में विफल",
|
||||
"copied": "लिंक क्लिपबोर्ड में कॉपी किया गया"
|
||||
"copied": "लिंक क्लिपबोर्ड में कॉपी किया गया",
|
||||
"readyDescription": "आपका साझाकरण लिंक तैयार है। आप डायरेक्ट रूप से QR कोड स्कैन कर सकते हैं, इसे बाद में उपयोग के लिए डाउनलोड कर सकते हैं, या नीचे लिंक कॉपी कर सकते हैं।",
|
||||
"tabs": {
|
||||
"link": "लिंक",
|
||||
"qrcode": "QR कोड"
|
||||
}
|
||||
},
|
||||
"home": {
|
||||
"description": "WeTransfer का ओपन-सोर्स विकल्प। फाइलें सुरक्षित रूप से साझा करें, बिना ट्रैकिंग या सीमाओं के।",
|
||||
@@ -355,6 +360,12 @@
|
||||
"stats": "{libraryCount} लाइब्रेरी से {iconCount} आइकन",
|
||||
"categoryBadge": "{category} ({count} आइकन)"
|
||||
},
|
||||
"imageEdit": {
|
||||
"title": "छवि संपादित करें",
|
||||
"rotate": "घुमाएं",
|
||||
"zoom": "ज़ूम",
|
||||
"cropInstructions": "छवि को पुनः स्थानांतरित करने के लिए खींचें, कोणों को समायोजित करने के लिए आकार बदलें"
|
||||
},
|
||||
"login": {
|
||||
"welcome": "स्वागत है में",
|
||||
"signInToContinue": "जारी रखने के लिए साइन इन करें",
|
||||
@@ -613,7 +624,8 @@
|
||||
"createLink": "लिंक बनाएं",
|
||||
"delete": "हटाएं",
|
||||
"copyLinkTitle": "लिंक कॉपी करें",
|
||||
"createLinkCTA": "प्राप्ति लिंक बनाएं"
|
||||
"createLinkCTA": "प्राप्ति लिंक बनाएं",
|
||||
"viewQrCode": "QR कोड देखें"
|
||||
},
|
||||
"status": {
|
||||
"active": "सक्रिय",
|
||||
@@ -629,7 +641,8 @@
|
||||
"viewDetails": "विवरण देखें",
|
||||
"edit": "संपादित करें",
|
||||
"delete": "हटाएं",
|
||||
"viewFiles": "प्राप्त फ़ाइलें"
|
||||
"viewFiles": "प्राप्त फ़ाइलें",
|
||||
"viewQrCode": "QR कोड देखें"
|
||||
},
|
||||
"empty": {
|
||||
"title": "कोई प्राप्ति लिंक नहीं बनाया गया",
|
||||
@@ -1230,7 +1243,10 @@
|
||||
"invalidDate": "अमान्य तिथि",
|
||||
"loadError": "साझाकरण विवरण लोड करने में विफल",
|
||||
"editSecurity": "सुरक्षा संपादित करें",
|
||||
"editExpiration": "समाप्ति संपादित करें"
|
||||
"editExpiration": "समाप्ति संपादित करें",
|
||||
"clickToEnlargeQrCode": "QR कोड को बड़ा करने के लिए क्लिक करें",
|
||||
"downloadQrCode": "QR कोड डाउनलोड करें",
|
||||
"qrCode": "QR कोड"
|
||||
},
|
||||
"shareExpiration": {
|
||||
"neverExpires": "कभी समाप्त नहीं होता",
|
||||
@@ -1417,7 +1433,8 @@
|
||||
"copyLink": "लिंक कॉपी करें",
|
||||
"notifyRecipients": "प्राप्तकर्ताओं को सूचित करें",
|
||||
"delete": "हटाएं",
|
||||
"downloadShareFiles": "सभी फ़ाइलें डाउनलोड करें"
|
||||
"downloadShareFiles": "सभी फ़ाइलें डाउनलोड करें",
|
||||
"viewQrCode": "QR कोड देखें"
|
||||
},
|
||||
"bulkActions": {
|
||||
"delete": "हटाएं",
|
||||
@@ -1720,10 +1737,9 @@
|
||||
"nameRequired": "नाम आवश्यक है",
|
||||
"required": "यह फ़ील्ड आवश्यक है"
|
||||
},
|
||||
"imageEdit": {
|
||||
"title": "छवि संपादित करें",
|
||||
"rotate": "घुमाएं",
|
||||
"zoom": "ज़ूम",
|
||||
"cropInstructions": "छवि को पुनः स्थानांतरित करने के लिए खींचें, कोणों को समायोजित करने के लिए आकार बदलें"
|
||||
"qrCodeModal": {
|
||||
"title": "QR कोड साझा करें",
|
||||
"description": "इस QR कोड को स्कैन करके लिंक तक पहुंच सकते हैं।",
|
||||
"download": "QR कोड डाउनलोड करें"
|
||||
}
|
||||
}
|
@@ -327,7 +327,12 @@
|
||||
"copyButton": "Copia link",
|
||||
"success": "Link generato con successo",
|
||||
"error": "Errore nella generazione del link",
|
||||
"copied": "Link copiato negli appunti"
|
||||
"copied": "Link copiato negli appunti",
|
||||
"readyDescription": "Il tuo link di condivisione è pronto. Puoi scansionare il codice QR direttamente, scaricarlo per un uso successivo, o copiare il link qui sotto.",
|
||||
"tabs": {
|
||||
"link": "Link",
|
||||
"qrcode": "QR Code"
|
||||
}
|
||||
},
|
||||
"home": {
|
||||
"description": "L'alternativa open-source a WeTransfer. Condividi file in sicurezza, senza tracciamento o limitazioni.",
|
||||
@@ -355,6 +360,12 @@
|
||||
"stats": "{iconCount} icone da {libraryCount} librerie",
|
||||
"categoryBadge": "{category} ({count} icone)"
|
||||
},
|
||||
"imageEdit": {
|
||||
"title": "Modifica Immagine",
|
||||
"rotate": "Ruota",
|
||||
"zoom": "Zoom",
|
||||
"cropInstructions": "Trascina per riposizionare, ridimensiona gli angoli per adattare l'area di ritaglio"
|
||||
},
|
||||
"login": {
|
||||
"welcome": "Benvenuto in",
|
||||
"signInToContinue": "Accedi per continuare",
|
||||
@@ -613,7 +624,8 @@
|
||||
"createLink": "Crea Link",
|
||||
"delete": "Elimina",
|
||||
"copyLinkTitle": "Copia link",
|
||||
"createLinkCTA": "Crea Link di Ricezione"
|
||||
"createLinkCTA": "Crea Link di Ricezione",
|
||||
"viewQrCode": "Visualizza QR Code"
|
||||
},
|
||||
"status": {
|
||||
"active": "Attivo",
|
||||
@@ -629,7 +641,8 @@
|
||||
"viewDetails": "Vedi Dettagli",
|
||||
"edit": "Modifica",
|
||||
"delete": "Elimina",
|
||||
"viewFiles": "File Ricevuti"
|
||||
"viewFiles": "File Ricevuti",
|
||||
"viewQrCode": "Visualizza QR Code"
|
||||
},
|
||||
"empty": {
|
||||
"title": "Nessun link di ricezione creato",
|
||||
@@ -1230,7 +1243,10 @@
|
||||
"invalidDate": "Data non valida",
|
||||
"loadError": "Errore nel caricamento dei dettagli della condivisione",
|
||||
"editSecurity": "Modifica Sicurezza",
|
||||
"editExpiration": "Modifica Scadenza"
|
||||
"editExpiration": "Modifica Scadenza",
|
||||
"clickToEnlargeQrCode": "Clicca per ingrandire il QR Code",
|
||||
"downloadQrCode": "Scarica QR Code",
|
||||
"qrCode": "QR Code"
|
||||
},
|
||||
"shareExpiration": {
|
||||
"neverExpires": "Non Scade Mai",
|
||||
@@ -1417,7 +1433,8 @@
|
||||
"copyLink": "Copia Link",
|
||||
"notifyRecipients": "Notifica Destinatari",
|
||||
"delete": "Elimina",
|
||||
"downloadShareFiles": "Scarica tutti i file"
|
||||
"downloadShareFiles": "Scarica tutti i file",
|
||||
"viewQrCode": "Visualizza QR Code"
|
||||
},
|
||||
"bulkActions": {
|
||||
"delete": "Elimina",
|
||||
@@ -1720,10 +1737,9 @@
|
||||
"nameRequired": "Il nome è obbligatorio",
|
||||
"required": "Questo campo è obbligatorio"
|
||||
},
|
||||
"imageEdit": {
|
||||
"title": "Modifica Immagine",
|
||||
"rotate": "Ruota",
|
||||
"zoom": "Zoom",
|
||||
"cropInstructions": "Trascina per riposizionare, ridimensiona gli angoli per adattare l'area di ritaglio"
|
||||
"qrCodeModal": {
|
||||
"title": "Condividi QR Code",
|
||||
"description": "Scansiona questo codice QR per accedere al link.",
|
||||
"download": "Scarica QR Code"
|
||||
}
|
||||
}
|
@@ -327,7 +327,12 @@
|
||||
"copyButton": "リンクをコピー",
|
||||
"success": "リンクが正常に生成されました",
|
||||
"error": "リンクの生成に失敗しました",
|
||||
"copied": "リンクがクリップボードにコピーされました"
|
||||
"copied": "リンクがクリップボードにコピーされました",
|
||||
"readyDescription": "共有リンクが準備できました。QRコードを直接スキャンして、後で使用するためにダウンロードするか、リンクをコピーしてください。",
|
||||
"tabs": {
|
||||
"link": "リンク",
|
||||
"qrcode": "QRコード"
|
||||
}
|
||||
},
|
||||
"home": {
|
||||
"description": "WeTransferのオープンソース代替です。トラッキングや制限なしに安全にファイルを共有します。",
|
||||
@@ -355,6 +360,12 @@
|
||||
"stats": "{libraryCount}ライブラリから{iconCount}個のアイコン",
|
||||
"categoryBadge": "{category}({count}個のアイコン)"
|
||||
},
|
||||
"imageEdit": {
|
||||
"title": "画像を編集",
|
||||
"rotate": "回転",
|
||||
"zoom": "ズーム",
|
||||
"cropInstructions": "位置を変更するにはドラッグし、カット領域を調整するには角をリサイズしてください"
|
||||
},
|
||||
"login": {
|
||||
"welcome": "ようこそへ",
|
||||
"signInToContinue": "続行するにはサインインしてください",
|
||||
@@ -613,7 +624,8 @@
|
||||
"createLink": "リンクを作成",
|
||||
"delete": "削除",
|
||||
"copyLinkTitle": "リンクをコピー",
|
||||
"createLinkCTA": "受信リンクを作成"
|
||||
"createLinkCTA": "受信リンクを作成",
|
||||
"viewQrCode": "QRコードを表示"
|
||||
},
|
||||
"status": {
|
||||
"active": "有効",
|
||||
@@ -629,7 +641,8 @@
|
||||
"viewDetails": "詳細を表示",
|
||||
"edit": "編集",
|
||||
"delete": "削除",
|
||||
"viewFiles": "受信済みファイル"
|
||||
"viewFiles": "受信済みファイル",
|
||||
"viewQrCode": "QRコードを表示"
|
||||
},
|
||||
"empty": {
|
||||
"title": "受信リンクが作成されていません",
|
||||
@@ -1230,7 +1243,10 @@
|
||||
"invalidDate": "無効な日付",
|
||||
"loadError": "共有詳細の読み込みに失敗しました",
|
||||
"editSecurity": "セキュリティを編集",
|
||||
"editExpiration": "期限を編集"
|
||||
"editExpiration": "期限を編集",
|
||||
"clickToEnlargeQrCode": "QRコードを拡大",
|
||||
"downloadQrCode": "QRコードをダウンロード",
|
||||
"qrCode": "QRコード"
|
||||
},
|
||||
"shareExpiration": {
|
||||
"neverExpires": "期限なし",
|
||||
@@ -1417,7 +1433,8 @@
|
||||
"copyLink": "リンクコピー",
|
||||
"notifyRecipients": "受信者に通知",
|
||||
"delete": "削除",
|
||||
"downloadShareFiles": "すべてのファイルをダウンロードします"
|
||||
"downloadShareFiles": "すべてのファイルをダウンロードします",
|
||||
"viewQrCode": "QRコードを表示"
|
||||
},
|
||||
"bulkActions": {
|
||||
"delete": "削除",
|
||||
@@ -1720,10 +1737,9 @@
|
||||
"nameRequired": "名前は必須です",
|
||||
"required": "このフィールドは必須です"
|
||||
},
|
||||
"imageEdit": {
|
||||
"title": "画像を編集",
|
||||
"rotate": "回転",
|
||||
"zoom": "ズーム",
|
||||
"cropInstructions": "位置を変更するにはドラッグし、カット領域を調整するには角をリサイズしてください"
|
||||
"qrCodeModal": {
|
||||
"title": "QRコードを共有",
|
||||
"description": "このQRコードをスキャンしてリンクにアクセスしてください。",
|
||||
"download": "QRコードをダウンロード"
|
||||
}
|
||||
}
|
@@ -327,7 +327,12 @@
|
||||
"copyButton": "링크 복사",
|
||||
"success": "링크가 성공적으로 생성되었습니다",
|
||||
"error": "링크 생성에 실패했습니다",
|
||||
"copied": "링크가 클립보드에 복사되었습니다"
|
||||
"copied": "링크가 클립보드에 복사되었습니다",
|
||||
"readyDescription": "공유 링크가 준비되었습니다. QR 코드를 직접 스캔하여 링크에 접근하거나, 나중에 사용하기 위해 다운로드하거나, 아래 링크를 복사할 수 있습니다.",
|
||||
"tabs": {
|
||||
"link": "링크",
|
||||
"qrcode": "QR 코드"
|
||||
}
|
||||
},
|
||||
"home": {
|
||||
"description": "WeTransfer의 오픈소스 대안입니다. 추적이나 제한 없이 파일을 안전하게 공유하세요.",
|
||||
@@ -355,6 +360,12 @@
|
||||
"stats": "{libraryCount}개의 라이브러리에서 {iconCount}개의 아이콘",
|
||||
"categoryBadge": "{category} ({count}개의 아이콘)"
|
||||
},
|
||||
"imageEdit": {
|
||||
"title": "이미지 편집",
|
||||
"rotate": "회전",
|
||||
"zoom": "확대/축소",
|
||||
"cropInstructions": "위치를 변경하려면 드래그하고, 자르기 영역을 조정하려면 모서리를 확대/축소하세요"
|
||||
},
|
||||
"login": {
|
||||
"welcome": "에 오신 것을 환영합니다",
|
||||
"signInToContinue": "계속하려면 로그인하세요",
|
||||
@@ -613,7 +624,8 @@
|
||||
"createLink": "링크 생성",
|
||||
"delete": "삭제",
|
||||
"copyLinkTitle": "링크 복사",
|
||||
"createLinkCTA": "수신 링크 생성"
|
||||
"createLinkCTA": "수신 링크 생성",
|
||||
"viewQrCode": "QR 코드 보기"
|
||||
},
|
||||
"status": {
|
||||
"active": "활성",
|
||||
@@ -629,7 +641,8 @@
|
||||
"viewDetails": "상세 보기",
|
||||
"edit": "편집",
|
||||
"delete": "삭제",
|
||||
"viewFiles": "받은 파일"
|
||||
"viewFiles": "받은 파일",
|
||||
"viewQrCode": "QR 코드 보기"
|
||||
},
|
||||
"empty": {
|
||||
"title": "생성된 수신 링크 없음",
|
||||
@@ -1230,7 +1243,10 @@
|
||||
"invalidDate": "잘못된 날짜",
|
||||
"loadError": "공유 세부 정보 로드에 실패했습니다",
|
||||
"editSecurity": "보안 편집",
|
||||
"editExpiration": "만료 편집"
|
||||
"editExpiration": "만료 편집",
|
||||
"clickToEnlargeQrCode": "QR 코드 확대",
|
||||
"downloadQrCode": "QR 코드 다운로드",
|
||||
"qrCode": "QR 코드"
|
||||
},
|
||||
"shareExpiration": {
|
||||
"neverExpires": "만료되지 않음",
|
||||
@@ -1417,7 +1433,8 @@
|
||||
"copyLink": "링크 복사",
|
||||
"notifyRecipients": "받는 사람에게 알림",
|
||||
"delete": "삭제",
|
||||
"downloadShareFiles": "모든 파일을 다운로드하십시오"
|
||||
"downloadShareFiles": "모든 파일을 다운로드하십시오",
|
||||
"viewQrCode": "QR 코드 보기"
|
||||
},
|
||||
"bulkActions": {
|
||||
"delete": "삭제",
|
||||
@@ -1720,10 +1737,9 @@
|
||||
"nameRequired": "이름은 필수입니다",
|
||||
"required": "이 필드는 필수입니다"
|
||||
},
|
||||
"imageEdit": {
|
||||
"title": "이미지 편집",
|
||||
"rotate": "회전",
|
||||
"zoom": "확대/축소",
|
||||
"cropInstructions": "위치를 변경하려면 드래그하고, 자르기 영역을 조정하려면 모서리를 확대/축소하세요"
|
||||
"qrCodeModal": {
|
||||
"title": "QR 코드 공유",
|
||||
"description": "이 QR 코드를 스캔하여 링크에 접근할 수 있습니다.",
|
||||
"download": "QR 코드 다운로드"
|
||||
}
|
||||
}
|
@@ -327,7 +327,12 @@
|
||||
"copyButton": "Link kopiëren",
|
||||
"success": "Link succesvol gegenereerd",
|
||||
"error": "Fout bij het genereren van link",
|
||||
"copied": "Link gekopieerd naar klembord"
|
||||
"copied": "Link gekopieerd naar klembord",
|
||||
"readyDescription": "Uw deel-link is klaar. U kunt de QR-code direct scannen, downloaden voor later gebruik, of de link hieronder kopiëren.",
|
||||
"tabs": {
|
||||
"link": "Link",
|
||||
"qrcode": "QR Code"
|
||||
}
|
||||
},
|
||||
"home": {
|
||||
"description": "Het open-source alternatief voor WeTransfer. Deel bestanden veilig, zonder tracking of beperkingen.",
|
||||
@@ -355,6 +360,12 @@
|
||||
"stats": "{iconCount} pictogrammen van {libraryCount} bibliotheken",
|
||||
"categoryBadge": "{category} ({count} pictogrammen)"
|
||||
},
|
||||
"imageEdit": {
|
||||
"title": "Afbeelding bewerken",
|
||||
"rotate": "Draai",
|
||||
"zoom": "Vergroot",
|
||||
"cropInstructions": "Sleep om te herpositioneren, verander de grootte van de hoeken om de uitsnijdgebied aan te passen"
|
||||
},
|
||||
"login": {
|
||||
"welcome": "Welkom bij",
|
||||
"signInToContinue": "Log in om door te gaan",
|
||||
@@ -613,7 +624,8 @@
|
||||
"createLink": "Link Aanmaken",
|
||||
"delete": "Verwijderen",
|
||||
"copyLinkTitle": "Link kopiëren",
|
||||
"createLinkCTA": "Ontvangstlink Aanmaken"
|
||||
"createLinkCTA": "Ontvangstlink Aanmaken",
|
||||
"viewQrCode": "QR Code Bekijken"
|
||||
},
|
||||
"status": {
|
||||
"active": "Actief",
|
||||
@@ -629,7 +641,8 @@
|
||||
"viewDetails": "Details Bekijken",
|
||||
"edit": "Bewerken",
|
||||
"delete": "Verwijderen",
|
||||
"viewFiles": "Ontvangen Bestanden"
|
||||
"viewFiles": "Ontvangen Bestanden",
|
||||
"viewQrCode": "QR Code Bekijken"
|
||||
},
|
||||
"empty": {
|
||||
"title": "Geen ontvangstlinks aangemaakt",
|
||||
@@ -1230,7 +1243,10 @@
|
||||
"invalidDate": "Ongeldige datum",
|
||||
"loadError": "Fout bij laden van delen details",
|
||||
"editSecurity": "Beveiliging Bewerken",
|
||||
"editExpiration": "Vervaldatum Bewerken"
|
||||
"editExpiration": "Vervaldatum Bewerken",
|
||||
"clickToEnlargeQrCode": "Klik om QR Code te vergroten",
|
||||
"downloadQrCode": "QR Code Downloaden",
|
||||
"qrCode": "QR Code"
|
||||
},
|
||||
"shareExpiration": {
|
||||
"neverExpires": "Verloopt Nooit",
|
||||
@@ -1417,7 +1433,8 @@
|
||||
"copyLink": "Link Kopiëren",
|
||||
"notifyRecipients": "Ontvangers Informeren",
|
||||
"delete": "Verwijderen",
|
||||
"downloadShareFiles": "Download alle bestanden"
|
||||
"downloadShareFiles": "Download alle bestanden",
|
||||
"viewQrCode": "QR Code Bekijken"
|
||||
},
|
||||
"bulkActions": {
|
||||
"delete": "Verwijderen",
|
||||
@@ -1720,10 +1737,9 @@
|
||||
"nameRequired": "Naam is verplicht",
|
||||
"required": "Dit veld is verplicht"
|
||||
},
|
||||
"imageEdit": {
|
||||
"title": "Afbeelding bewerken",
|
||||
"rotate": "Draai",
|
||||
"zoom": "Vergroot",
|
||||
"cropInstructions": "Sleep om te herpositioneren, verander de grootte van de hoeken om de uitsnijdgebied aan te passen"
|
||||
"qrCodeModal": {
|
||||
"title": "QR Code Delen",
|
||||
"description": "Scan deze QR-code om toegang te krijgen tot de link.",
|
||||
"download": "QR Code Downloaden"
|
||||
}
|
||||
}
|
@@ -327,7 +327,12 @@
|
||||
"copyButton": "Skopiuj link",
|
||||
"success": "Link wygenerowany pomyślnie",
|
||||
"error": "Nie udało się wygenerować linku",
|
||||
"copied": "Link skopiowany do schowka"
|
||||
"copied": "Link skopiowany do schowka",
|
||||
"readyDescription": "Twój link do udostępniania jest gotowy. Możesz skanować kod QR bezpośrednio, pobrać go do późniejszego użycia lub skopiować link poniżej.",
|
||||
"tabs": {
|
||||
"link": "Link",
|
||||
"qrcode": "QR Code"
|
||||
}
|
||||
},
|
||||
"home": {
|
||||
"description": "Otwartoźródłowa alternatywa dla WeTransfer. Udostępniaj pliki bezpiecznie, bez śledzenia i ograniczeń.",
|
||||
@@ -355,6 +360,12 @@
|
||||
"stats": "{iconCount} ikon z {libraryCount} bibliotek",
|
||||
"categoryBadge": "{category} ({count} ikon)"
|
||||
},
|
||||
"imageEdit": {
|
||||
"title": "Edytuj obraz",
|
||||
"rotate": "Obróć",
|
||||
"zoom": "Powiększ",
|
||||
"cropInstructions": "Przeciągnij, aby przesunąć, zmień rozmiar rogów, aby dostosować obszar przycięcia"
|
||||
},
|
||||
"login": {
|
||||
"welcome": "Witaj w",
|
||||
"signInToContinue": "Zaloguj się, aby kontynuować",
|
||||
@@ -613,7 +624,8 @@
|
||||
"createLink": "Utwórz link",
|
||||
"delete": "Usuń",
|
||||
"copyLinkTitle": "Skopiuj link",
|
||||
"createLinkCTA": "Utwórz link do odbierania"
|
||||
"createLinkCTA": "Utwórz link do odbierania",
|
||||
"viewQrCode": "Wyświetl kod QR"
|
||||
},
|
||||
"status": {
|
||||
"active": "Aktywny",
|
||||
@@ -629,7 +641,8 @@
|
||||
"viewDetails": "Wyświetl szczegóły",
|
||||
"edit": "Edytuj",
|
||||
"delete": "Usuń",
|
||||
"viewFiles": "Odebrane pliki"
|
||||
"viewFiles": "Odebrane pliki",
|
||||
"viewQrCode": "Wyświetl kod QR"
|
||||
},
|
||||
"empty": {
|
||||
"title": "Brak utworzonych linków do odbierania",
|
||||
@@ -1230,7 +1243,10 @@
|
||||
"recipients": "Odbiorcy",
|
||||
"notAvailable": "N/A",
|
||||
"invalidDate": "Nieprawidłowa data",
|
||||
"loadError": "Nie udało się załadować szczegółów udostępnienia"
|
||||
"loadError": "Nie udało się załadować szczegółów udostępnienia",
|
||||
"clickToEnlargeQrCode": "Kliknij, aby powiększyć kod QR",
|
||||
"downloadQrCode": "Pobierz kod QR",
|
||||
"qrCode": "Kod QR"
|
||||
},
|
||||
"shareExpiration": {
|
||||
"title": "Ustawienia wygaśnięcia udostępnienia",
|
||||
@@ -1417,7 +1433,8 @@
|
||||
"copyLink": "Skopiuj link",
|
||||
"notifyRecipients": "Powiadom odbiorców",
|
||||
"delete": "Usuń",
|
||||
"downloadShareFiles": "Pobierz wszystkie pliki"
|
||||
"downloadShareFiles": "Pobierz wszystkie pliki",
|
||||
"viewQrCode": "Wyświetl kod QR"
|
||||
},
|
||||
"bulkActions": {
|
||||
"delete": "Usuń",
|
||||
@@ -1720,10 +1737,9 @@
|
||||
"nameRequired": "Nazwa jest wymagana",
|
||||
"required": "To pole jest wymagane"
|
||||
},
|
||||
"imageEdit": {
|
||||
"title": "Edytuj obraz",
|
||||
"rotate": "Obróć",
|
||||
"zoom": "Powiększ",
|
||||
"cropInstructions": "Przeciągnij, aby przesunąć, zmień rozmiar rogów, aby dostosować obszar przycięcia"
|
||||
"qrCodeModal": {
|
||||
"title": "Udostępnij kod QR",
|
||||
"description": "Skanuj ten kod QR, aby uzyskać dostęp do linku.",
|
||||
"download": "Pobierz kod QR"
|
||||
}
|
||||
}
|
@@ -327,7 +327,12 @@
|
||||
"copyButton": "Copiar link",
|
||||
"success": "Link gerado com sucesso",
|
||||
"error": "Erro ao gerar link",
|
||||
"copied": "Link copiado para a área de transferência"
|
||||
"copied": "Link copiado para a área de transferência",
|
||||
"readyDescription": "Seu link de compartilhamento está pronto. Você pode escanear o código QR diretamente, baixá-lo para uso posterior ou copiar o link abaixo.",
|
||||
"tabs": {
|
||||
"link": "Link",
|
||||
"qrcode": "QR Code"
|
||||
}
|
||||
},
|
||||
"home": {
|
||||
"description": "A alternativa open-source ao WeTransfer. Compartilhe arquivos com segurança, sem rastreamento ou limitações.",
|
||||
@@ -355,6 +360,12 @@
|
||||
"stats": "{iconCount} ícones de {libraryCount} bibliotecas",
|
||||
"categoryBadge": "{category} ({count} ícones)"
|
||||
},
|
||||
"imageEdit": {
|
||||
"title": "Editar imagem",
|
||||
"rotate": "Girar",
|
||||
"zoom": "Ampliar",
|
||||
"cropInstructions": "Arraste para reposicionar, redimensione os cantos para ajustar a área de recorte"
|
||||
},
|
||||
"login": {
|
||||
"welcome": "Bem-vindo ao",
|
||||
"signInToContinue": "Faça login para continuar",
|
||||
@@ -613,7 +624,8 @@
|
||||
"createLink": "Criar Link",
|
||||
"delete": "Excluir",
|
||||
"copyLinkTitle": "Copiar link",
|
||||
"createLinkCTA": "Criar Link de Recebimento"
|
||||
"createLinkCTA": "Criar Link de Recebimento",
|
||||
"viewQrCode": "Visualizar QR Code"
|
||||
},
|
||||
"status": {
|
||||
"active": "Ativo",
|
||||
@@ -629,7 +641,8 @@
|
||||
"viewDetails": "Ver Detalhes",
|
||||
"edit": "Editar",
|
||||
"delete": "Excluir",
|
||||
"viewFiles": "Arquivos Recebidos"
|
||||
"viewFiles": "Arquivos Recebidos",
|
||||
"viewQrCode": "[TO_TRANSLATE] View QR Code"
|
||||
},
|
||||
"empty": {
|
||||
"title": "Nenhum link de recebimento criado",
|
||||
@@ -1230,7 +1243,10 @@
|
||||
"invalidDate": "Data inválida",
|
||||
"loadError": "Falha ao carregar detalhes do compartilhamento",
|
||||
"editSecurity": "Editar Segurança",
|
||||
"editExpiration": "Editar Expiração"
|
||||
"editExpiration": "Editar Expiração",
|
||||
"clickToEnlargeQrCode": "Clique para ampliar o QR Code",
|
||||
"downloadQrCode": "Baixar QR Code",
|
||||
"qrCode": "QR Code"
|
||||
},
|
||||
"shareExpiration": {
|
||||
"neverExpires": "Nunca Expira",
|
||||
@@ -1425,7 +1441,8 @@
|
||||
"copyLink": "Copiar Link",
|
||||
"notifyRecipients": "Notificar Destinatários",
|
||||
"delete": "Excluir",
|
||||
"downloadShareFiles": "Baixar todos os arquivos"
|
||||
"downloadShareFiles": "Baixar todos os arquivos",
|
||||
"viewQrCode": "Visualizar QR Code"
|
||||
}
|
||||
},
|
||||
"storageUsage": {
|
||||
@@ -1720,10 +1737,9 @@
|
||||
"usernameLength": "O nome de usuário deve ter pelo menos 3 caracteres",
|
||||
"usernameSpaces": "O nome de usuário não pode conter espaços"
|
||||
},
|
||||
"imageEdit": {
|
||||
"title": "Editar imagem",
|
||||
"rotate": "Girar",
|
||||
"zoom": "Ampliar",
|
||||
"cropInstructions": "Arraste para reposicionar, redimensione os cantos para ajustar a área de recorte"
|
||||
"qrCodeModal": {
|
||||
"title": "Compartilhar QR Code",
|
||||
"description": "Escaneie este código QR para acessar o link.",
|
||||
"download": "Baixar QR Code"
|
||||
}
|
||||
}
|
@@ -327,7 +327,12 @@
|
||||
"copyButton": "Копировать ссылку",
|
||||
"success": "Ссылка успешно создана",
|
||||
"error": "Ошибка при создании ссылки",
|
||||
"copied": "Ссылка скопирована в буфер обмена"
|
||||
"copied": "Ссылка скопирована в буфер обмена",
|
||||
"readyDescription": "Ваша ссылка для обмена готова. Вы можете сканировать QR-код напрямую, скачать его для последующего использования или скопировать ссылку ниже.",
|
||||
"tabs": {
|
||||
"link": "Ссылка",
|
||||
"qrcode": "QR-код"
|
||||
}
|
||||
},
|
||||
"home": {
|
||||
"description": "Открытая альтернатива WeTransfer. Делитесь файлами безопасно, без отслеживания и ограничений.",
|
||||
@@ -355,6 +360,12 @@
|
||||
"stats": "{iconCount} иконок из {libraryCount} библиотек",
|
||||
"categoryBadge": "{category} ({count} иконок)"
|
||||
},
|
||||
"imageEdit": {
|
||||
"title": "Редактировать изображение",
|
||||
"rotate": "Повернуть",
|
||||
"zoom": "Увеличить",
|
||||
"cropInstructions": "Перетащите, чтобы переместить, измените размер углов, чтобы отрегулировать область обрезки"
|
||||
},
|
||||
"login": {
|
||||
"welcome": "Добро пожаловать в",
|
||||
"signInToContinue": "Войдите, чтобы продолжить",
|
||||
@@ -613,7 +624,8 @@
|
||||
"createLink": "Создать ссылку",
|
||||
"delete": "Удалить",
|
||||
"copyLinkTitle": "Копировать ссылку",
|
||||
"createLinkCTA": "Создать ссылку для получения"
|
||||
"createLinkCTA": "Создать ссылку для получения",
|
||||
"viewQrCode": "Просмотр QR-кода"
|
||||
},
|
||||
"status": {
|
||||
"active": "Активно",
|
||||
@@ -629,7 +641,8 @@
|
||||
"viewDetails": "Просмотр деталей",
|
||||
"edit": "Редактировать",
|
||||
"delete": "Удалить",
|
||||
"viewFiles": "Полученные файлы"
|
||||
"viewFiles": "Полученные файлы",
|
||||
"viewQrCode": "Просмотр QR-кода"
|
||||
},
|
||||
"empty": {
|
||||
"title": "Нет созданных ссылок для получения",
|
||||
@@ -1230,7 +1243,10 @@
|
||||
"invalidDate": "Неверная дата",
|
||||
"loadError": "Ошибка загрузки деталей общего доступа",
|
||||
"editSecurity": "Изменить безопасность",
|
||||
"editExpiration": "Изменить срок действия"
|
||||
"editExpiration": "Изменить срок действия",
|
||||
"clickToEnlargeQrCode": "Нажмите, чтобы увеличить QR-код",
|
||||
"downloadQrCode": "Скачать QR-код",
|
||||
"qrCode": "QR-код"
|
||||
},
|
||||
"shareExpiration": {
|
||||
"neverExpires": "Никогда не истекает",
|
||||
@@ -1417,7 +1433,8 @@
|
||||
"copyLink": "Скопировать Ссылку",
|
||||
"notifyRecipients": "Уведомить Получателей",
|
||||
"delete": "Удалить",
|
||||
"downloadShareFiles": "Загрузите все файлы"
|
||||
"downloadShareFiles": "Загрузите все файлы",
|
||||
"viewQrCode": "Просмотр QR-кода"
|
||||
},
|
||||
"bulkActions": {
|
||||
"delete": "Удалить",
|
||||
@@ -1720,10 +1737,9 @@
|
||||
"nameRequired": "Требуется имя",
|
||||
"required": "Это поле обязательно"
|
||||
},
|
||||
"imageEdit": {
|
||||
"title": "Редактировать изображение",
|
||||
"rotate": "Повернуть",
|
||||
"zoom": "Увеличить",
|
||||
"cropInstructions": "Перетащите, чтобы переместить, измените размер углов, чтобы отрегулировать область обрезки"
|
||||
"qrCodeModal": {
|
||||
"title": "Поделиться QR-кодом",
|
||||
"description": "Отсканируйте этот QR-код, чтобы получить доступ к ссылке.",
|
||||
"download": "Скачать QR-код"
|
||||
}
|
||||
}
|
@@ -327,7 +327,12 @@
|
||||
"copyButton": "Bağlantıyı Kopyala",
|
||||
"success": "Bağlantı başarıyla oluşturuldu",
|
||||
"error": "Bağlantı oluşturulamadı",
|
||||
"copied": "Bağlantı panoya kopyalandı"
|
||||
"copied": "Bağlantı panoya kopyalandı",
|
||||
"readyDescription": "Paylaşım bağlantınız hazır. QR kodu doğrudan tarayabilir, daha sonra kullanmak için indirebilir veya aşağıdaki bağlantıyı kopyalayabilirsiniz.",
|
||||
"tabs": {
|
||||
"link": "Bağlantı",
|
||||
"qrcode": "QR Kodu"
|
||||
}
|
||||
},
|
||||
"home": {
|
||||
"description": "WeTransfer'e açık kaynaklı alternatif. Takip veya kısıtlama olmadan dosyalarınızı güvenle paylaşın.",
|
||||
@@ -355,6 +360,12 @@
|
||||
"stats": "{libraryCount} kütüphaneden {iconCount} simge",
|
||||
"categoryBadge": "{category} ({count} simge)"
|
||||
},
|
||||
"imageEdit": {
|
||||
"title": "Resmi Düzenle",
|
||||
"rotate": "Döndür",
|
||||
"zoom": "Yakınlaştır",
|
||||
"cropInstructions": "Yerleştirmek için sürükleyin, kırpma alanını ayarlamak için köşeleri yeniden boyutlandırın"
|
||||
},
|
||||
"login": {
|
||||
"welcome": "Hoş geldiniz'e",
|
||||
"signInToContinue": "Devam etmek için oturum açın",
|
||||
@@ -613,7 +624,8 @@
|
||||
"createLink": "Bağlantı Oluştur",
|
||||
"delete": "Sil",
|
||||
"copyLinkTitle": "Bağlantıyı kopyala",
|
||||
"createLinkCTA": "Alma Bağlantısı Oluştur"
|
||||
"createLinkCTA": "Alma Bağlantısı Oluştur",
|
||||
"viewQrCode": "QR Kodu Görüntüle"
|
||||
},
|
||||
"status": {
|
||||
"active": "Aktif",
|
||||
@@ -629,7 +641,8 @@
|
||||
"viewDetails": "Detayları Görüntüle",
|
||||
"edit": "Düzenle",
|
||||
"delete": "Sil",
|
||||
"viewFiles": "Alınan Dosyalar"
|
||||
"viewFiles": "Alınan Dosyalar",
|
||||
"viewQrCode": "QR Kodu Görüntüle"
|
||||
},
|
||||
"empty": {
|
||||
"title": "Alma bağlantısı oluşturulmadı",
|
||||
@@ -1230,7 +1243,10 @@
|
||||
"invalidDate": "Geçersiz tarih",
|
||||
"loadError": "Paylaşım detaylarını yükleme başarısız",
|
||||
"editSecurity": "Güvenlik Düzenle",
|
||||
"editExpiration": "Son Kullanma Düzenle"
|
||||
"editExpiration": "Son Kullanma Düzenle",
|
||||
"clickToEnlargeQrCode": "QR Kodu Büyüt",
|
||||
"downloadQrCode": "QR Kodu İndir",
|
||||
"qrCode": "QR Kodu"
|
||||
},
|
||||
"shareExpiration": {
|
||||
"neverExpires": "Asla Sona Ermez",
|
||||
@@ -1417,7 +1433,8 @@
|
||||
"copyLink": "Bağlantıyı Kopyala",
|
||||
"notifyRecipients": "Alıcıları Bilgilendir",
|
||||
"delete": "Sil",
|
||||
"downloadShareFiles": "Tüm dosyaları indirin"
|
||||
"downloadShareFiles": "Tüm dosyaları indirin",
|
||||
"viewQrCode": "QR Kodu Görüntüle"
|
||||
},
|
||||
"bulkActions": {
|
||||
"delete": "Sil",
|
||||
@@ -1720,10 +1737,9 @@
|
||||
"nameRequired": "İsim gereklidir",
|
||||
"required": "Bu alan zorunludur"
|
||||
},
|
||||
"imageEdit": {
|
||||
"title": "Resmi Düzenle",
|
||||
"rotate": "Döndür",
|
||||
"zoom": "Yakınlaştır",
|
||||
"cropInstructions": "Yerleştirmek için sürükleyin, kırpma alanını ayarlamak için köşeleri yeniden boyutlandırın"
|
||||
"qrCodeModal": {
|
||||
"title": "QR Kodu Paylaş",
|
||||
"description": "Bu QR kodu tarayarak bağlantıya erişebilirsiniz.",
|
||||
"download": "QR Kodu İndir"
|
||||
}
|
||||
}
|
@@ -327,7 +327,12 @@
|
||||
"copyButton": "复制链接",
|
||||
"success": "链接生成成功",
|
||||
"error": "链接生成失败",
|
||||
"copied": "链接已复制到剪贴板"
|
||||
"copied": "链接已复制到剪贴板",
|
||||
"readyDescription": "您的分享链接已准备就绪:",
|
||||
"tabs": {
|
||||
"link": "链接",
|
||||
"qrcode": "QR Code"
|
||||
}
|
||||
},
|
||||
"home": {
|
||||
"description": "WeTransfer的开源替代方案。安全分享文件,无需跟踪或限制。",
|
||||
@@ -355,6 +360,12 @@
|
||||
"stats": "来自 {libraryCount} 个库的 {iconCount} 个图标",
|
||||
"categoryBadge": "{category}({count} 个图标)"
|
||||
},
|
||||
"imageEdit": {
|
||||
"title": "编辑图片",
|
||||
"rotate": "旋转",
|
||||
"zoom": "缩放",
|
||||
"cropInstructions": "拖动以重新定位,调整角落大小以调整裁剪区域"
|
||||
},
|
||||
"login": {
|
||||
"welcome": "欢迎您",
|
||||
"signInToContinue": "请登录以继续",
|
||||
@@ -613,7 +624,8 @@
|
||||
"createLink": "创建链接",
|
||||
"delete": "删除",
|
||||
"copyLinkTitle": "复制链接",
|
||||
"createLinkCTA": "创建接收链接"
|
||||
"createLinkCTA": "创建接收链接",
|
||||
"viewQrCode": "查看QR Code"
|
||||
},
|
||||
"status": {
|
||||
"active": "活动",
|
||||
@@ -629,7 +641,8 @@
|
||||
"viewDetails": "查看详情",
|
||||
"edit": "编辑",
|
||||
"delete": "删除",
|
||||
"viewFiles": "已接收文件"
|
||||
"viewFiles": "已接收文件",
|
||||
"viewQrCode": "查看QR Code"
|
||||
},
|
||||
"empty": {
|
||||
"title": "未创建接收链接",
|
||||
@@ -1230,7 +1243,10 @@
|
||||
"description": "描述",
|
||||
"linkCopied": "链接已复制到剪贴板",
|
||||
"editSecurity": "编辑安全",
|
||||
"editExpiration": "编辑过期"
|
||||
"editExpiration": "编辑过期",
|
||||
"clickToEnlargeQrCode": "点击放大QR Code",
|
||||
"downloadQrCode": "下载QR Code",
|
||||
"qrCode": "QR Code"
|
||||
},
|
||||
"shareExpiration": {
|
||||
"neverExpires": "永不过期",
|
||||
@@ -1417,7 +1433,8 @@
|
||||
"copyLink": "复制链接",
|
||||
"notifyRecipients": "通知收件人",
|
||||
"delete": "删除",
|
||||
"downloadShareFiles": "下载所有文件"
|
||||
"downloadShareFiles": "下载所有文件",
|
||||
"viewQrCode": "查看QR Code"
|
||||
},
|
||||
"bulkActions": {
|
||||
"delete": "删除",
|
||||
@@ -1720,10 +1737,9 @@
|
||||
"nameRequired": "名称为必填项",
|
||||
"required": "此字段为必填项"
|
||||
},
|
||||
"imageEdit": {
|
||||
"title": "编辑图片",
|
||||
"rotate": "旋转",
|
||||
"zoom": "缩放",
|
||||
"cropInstructions": "拖动以重新定位,调整角落大小以调整裁剪区域"
|
||||
"qrCodeModal": {
|
||||
"title": "分享QR Code",
|
||||
"description": "扫描此QR Code以访问链接。",
|
||||
"download": "下载QR Code"
|
||||
}
|
||||
}
|
@@ -70,6 +70,7 @@
|
||||
"react-hook-form": "^7.59.0",
|
||||
"react-icons": "^5.5.0",
|
||||
"react-image-crop": "^11.0.10",
|
||||
"react-qr-code": "^2.0.18",
|
||||
"react-qr-reader": "3.0.0-beta-1",
|
||||
"sonner": "^2.0.5",
|
||||
"tailwind-merge": "^3.3.1",
|
||||
@@ -98,4 +99,4 @@
|
||||
"tailwindcss": "4.1.11",
|
||||
"typescript": "5.8.3"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
19
apps/web/pnpm-lock.yaml
generated
19
apps/web/pnpm-lock.yaml
generated
@@ -128,6 +128,9 @@ importers:
|
||||
react-image-crop:
|
||||
specifier: ^11.0.10
|
||||
version: 11.0.10(react@19.1.0)
|
||||
react-qr-code:
|
||||
specifier: ^2.0.18
|
||||
version: 2.0.18(react@19.1.0)
|
||||
react-qr-reader:
|
||||
specifier: 3.0.0-beta-1
|
||||
version: 3.0.0-beta-1(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
|
||||
@@ -2544,6 +2547,9 @@ packages:
|
||||
resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
qr.js@0.0.0:
|
||||
resolution: {integrity: sha512-c4iYnWb+k2E+vYpRimHqSu575b1/wKl4XFeJGpFmrJQz5I88v9aY2czh7s0w36srfCM1sXgC/xpoJz5dJfq+OQ==}
|
||||
|
||||
qrcode@1.5.4:
|
||||
resolution: {integrity: sha512-1ca71Zgiu6ORjHqFBDpnSMTR2ReToX4l1Au1VFLyVeBTFavzQnv5JxMFr3ukHVKpSrSA2MCk0lNJSykjUfz7Zg==}
|
||||
engines: {node: '>=10.13.0'}
|
||||
@@ -2591,6 +2597,11 @@ packages:
|
||||
react-is@16.13.1:
|
||||
resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==}
|
||||
|
||||
react-qr-code@2.0.18:
|
||||
resolution: {integrity: sha512-v1Jqz7urLMhkO6jkgJuBYhnqvXagzceg3qJUWayuCK/c6LTIonpWbwxR1f1APGd4xrW/QcQEovNrAojbUz65Tg==}
|
||||
peerDependencies:
|
||||
react: '*'
|
||||
|
||||
react-qr-reader@3.0.0-beta-1:
|
||||
resolution: {integrity: sha512-5HeFH9x/BlziRYQYGK2AeWS9WiKYZtGGMs9DXy3bcySTX3C9UJL9EwcPnWw8vlf7JP4FcrAlr1SnZ5nsWLQGyw==}
|
||||
peerDependencies:
|
||||
@@ -5451,6 +5462,8 @@ snapshots:
|
||||
|
||||
punycode@2.3.1: {}
|
||||
|
||||
qr.js@0.0.0: {}
|
||||
|
||||
qrcode@1.5.4:
|
||||
dependencies:
|
||||
dijkstrajs: 1.0.3
|
||||
@@ -5491,6 +5504,12 @@ snapshots:
|
||||
|
||||
react-is@16.13.1: {}
|
||||
|
||||
react-qr-code@2.0.18(react@19.1.0):
|
||||
dependencies:
|
||||
prop-types: 15.8.1
|
||||
qr.js: 0.0.0
|
||||
react: 19.1.0
|
||||
|
||||
react-qr-reader@3.0.0-beta-1(react-dom@19.1.0(react@19.1.0))(react@19.1.0):
|
||||
dependencies:
|
||||
'@zxing/browser': 0.0.7(@zxing/library@0.18.6)
|
||||
|
@@ -11,6 +11,7 @@ import {
|
||||
IconLink,
|
||||
IconLock,
|
||||
IconLockOpen,
|
||||
IconQrcode,
|
||||
IconToggleLeft,
|
||||
IconToggleRight,
|
||||
IconTrash,
|
||||
@@ -38,6 +39,7 @@ interface ReverseShareCardProps {
|
||||
onGenerateLink: (reverseShare: ReverseShare) => void;
|
||||
onViewDetails: (reverseShare: ReverseShare) => void;
|
||||
onViewFiles: (reverseShare: ReverseShare) => void;
|
||||
onViewQrCode?: (reverseShare: ReverseShare) => void;
|
||||
onUpdateReverseShare?: (id: string, data: any) => Promise<any>;
|
||||
onToggleActive?: (id: string, isActive: boolean) => Promise<any>;
|
||||
onUpdatePassword?: (id: string, data: { hasPassword: boolean; password?: string }) => Promise<any>;
|
||||
@@ -51,6 +53,7 @@ export function ReverseShareCard({
|
||||
onGenerateLink,
|
||||
onViewDetails,
|
||||
onViewFiles,
|
||||
onViewQrCode,
|
||||
onUpdateReverseShare,
|
||||
onToggleActive,
|
||||
onUpdatePassword,
|
||||
@@ -230,6 +233,18 @@ export function ReverseShareCard({
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-1">
|
||||
{hasAlias && onViewQrCode && (
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="h-6 w-6 p-0 hover:bg-background/80 rounded-sm"
|
||||
onClick={() => onViewQrCode(reverseShare)}
|
||||
title={t("reverseShares.card.viewQrCode")}
|
||||
>
|
||||
<IconQrcode className="h-3 w-3" />
|
||||
</Button>
|
||||
)}
|
||||
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
@@ -239,7 +254,6 @@ export function ReverseShareCard({
|
||||
>
|
||||
<IconEye className="h-3 w-3" />
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
@@ -257,6 +271,11 @@ export function ReverseShareCard({
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end">
|
||||
<DropdownMenuItem onClick={() => onViewDetails(reverseShare)}>
|
||||
<IconEye className="h-4 w-4" />
|
||||
{t("reverseShares.card.viewDetails")}
|
||||
</DropdownMenuItem>
|
||||
|
||||
<DropdownMenuItem onClick={() => onCopyLink(reverseShare)}>
|
||||
<IconCopy className="h-4 w-4" />
|
||||
{t("reverseShares.card.copyLink")}
|
||||
@@ -286,6 +305,13 @@ export function ReverseShareCard({
|
||||
{t("reverseShares.actions.viewFiles")}
|
||||
</DropdownMenuItem>
|
||||
|
||||
{hasAlias && onViewQrCode && (
|
||||
<DropdownMenuItem onClick={() => onViewQrCode(reverseShare)}>
|
||||
<IconQrcode className="h-4 w-4" />
|
||||
{t("reverseShares.actions.viewQrCode")}
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
|
||||
<DropdownMenuItem className="text-destructive" onClick={() => onDelete(reverseShare)}>
|
||||
<IconTrash className="h-4 w-4" />
|
||||
{t("reverseShares.card.delete")}
|
||||
|
@@ -3,6 +3,7 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import {
|
||||
IconCopy,
|
||||
IconDownload,
|
||||
IconEdit,
|
||||
IconLink,
|
||||
IconLock,
|
||||
@@ -11,6 +12,7 @@ import {
|
||||
IconToggleRight,
|
||||
} from "@tabler/icons-react";
|
||||
import { useTranslations } from "next-intl";
|
||||
import QRCode from "react-qr-code";
|
||||
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Button } from "@/components/ui/button";
|
||||
@@ -42,6 +44,7 @@ interface ReverseShareDetailsModalProps {
|
||||
onCopyLink?: (reverseShare: ReverseShare) => void;
|
||||
onToggleActive?: (id: string, isActive: boolean) => Promise<void>;
|
||||
onUpdatePassword?: (id: string, data: { hasPassword: boolean; password?: string }) => Promise<void>;
|
||||
onViewQrCode?: (reverseShare: ReverseShare) => void;
|
||||
refreshTrigger?: number;
|
||||
onSuccess?: () => void;
|
||||
}
|
||||
@@ -55,10 +58,12 @@ export function ReverseShareDetailsModal({
|
||||
onCopyLink,
|
||||
onToggleActive,
|
||||
onUpdatePassword,
|
||||
onViewQrCode,
|
||||
onSuccess,
|
||||
}: ReverseShareDetailsModalProps) {
|
||||
const t = useTranslations();
|
||||
const [pendingChanges, setPendingChanges] = useState<Record<string, any>>({});
|
||||
const [isDownloading, setIsDownloading] = useState(false);
|
||||
|
||||
const {
|
||||
showAliasModal,
|
||||
@@ -140,46 +145,119 @@ export function ReverseShareDetailsModal({
|
||||
isActive={reverseShare.isActive}
|
||||
/>
|
||||
|
||||
{/* Informações Básicas */}
|
||||
<div className="space-y-3">
|
||||
<h3 className="text-base font-medium text-foreground border-b pb-2">
|
||||
{t("reverseShares.modals.details.basicInfo")}
|
||||
</h3>
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
{/* Informações Básicas */}
|
||||
<div className="space-y-3">
|
||||
<h3 className="text-base font-medium text-foreground border-b pb-2">
|
||||
{t("reverseShares.modals.details.basicInfo")}
|
||||
</h3>
|
||||
|
||||
<EditableField
|
||||
label={t("reverseShares.form.name.label")}
|
||||
value={getDisplayValue(reverseShare, "name", pendingChanges)}
|
||||
onSave={(value) => handleUpdateField("name", value)}
|
||||
placeholder={t("reverseShares.card.untitled")}
|
||||
disabled={!onUpdateReverseShare}
|
||||
/>
|
||||
<EditableField
|
||||
label={t("reverseShares.form.name.label")}
|
||||
value={getDisplayValue(reverseShare, "name", pendingChanges)}
|
||||
onSave={(value) => handleUpdateField("name", value)}
|
||||
placeholder={t("reverseShares.card.untitled")}
|
||||
disabled={!onUpdateReverseShare}
|
||||
/>
|
||||
|
||||
<EditableField
|
||||
label={t("reverseShares.labels.description")}
|
||||
value={getDisplayValue(reverseShare, "description", pendingChanges)}
|
||||
onSave={(value) => handleUpdateField("description", value)}
|
||||
placeholder={t("reverseShares.card.noDescription")}
|
||||
disabled={!onUpdateReverseShare}
|
||||
/>
|
||||
<EditableField
|
||||
label={t("reverseShares.labels.description")}
|
||||
value={getDisplayValue(reverseShare, "description", pendingChanges)}
|
||||
onSave={(value) => handleUpdateField("description", value)}
|
||||
placeholder={t("reverseShares.card.noDescription")}
|
||||
disabled={!onUpdateReverseShare}
|
||||
/>
|
||||
|
||||
<EditableField
|
||||
label={t("reverseShares.labels.pageLayout")}
|
||||
value={getDisplayValue(reverseShare, "pageLayout", pendingChanges)}
|
||||
onSave={(value) => handleUpdateField("pageLayout", value)}
|
||||
type="select"
|
||||
options={[
|
||||
{ value: "DEFAULT", label: t("reverseShares.labels.layoutOptions.default") },
|
||||
{ value: "WETRANSFER", label: t("reverseShares.labels.layoutOptions.wetransfer") },
|
||||
]}
|
||||
disabled={!onUpdateReverseShare}
|
||||
renderValue={(value) => (
|
||||
<Badge variant="secondary" className="bg-purple-500/20 text-purple-700 border-purple-200">
|
||||
{value === "WETRANSFER"
|
||||
? t("reverseShares.labels.layoutOptions.wetransfer")
|
||||
: t("reverseShares.labels.layoutOptions.default")}
|
||||
</Badge>
|
||||
)}
|
||||
/>
|
||||
<EditableField
|
||||
label={t("reverseShares.labels.pageLayout")}
|
||||
value={getDisplayValue(reverseShare, "pageLayout", pendingChanges)}
|
||||
onSave={(value) => handleUpdateField("pageLayout", value)}
|
||||
type="select"
|
||||
options={[
|
||||
{ value: "DEFAULT", label: t("reverseShares.labels.layoutOptions.default") },
|
||||
{ value: "WETRANSFER", label: t("reverseShares.labels.layoutOptions.wetransfer") },
|
||||
]}
|
||||
disabled={!onUpdateReverseShare}
|
||||
renderValue={(value) => (
|
||||
<Badge variant="secondary" className="bg-purple-500/20 text-purple-700 border-purple-200">
|
||||
{value === "WETRANSFER"
|
||||
? t("reverseShares.labels.layoutOptions.wetransfer")
|
||||
: t("reverseShares.labels.layoutOptions.default")}
|
||||
</Badge>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* QR Code */}
|
||||
{reverseShareLink && (
|
||||
<div className="space-y-3">
|
||||
<div className="flex items-center gap-2 border-b pb-2">
|
||||
<h3
|
||||
className="text-base font-medium text-foreground cursor-pointer"
|
||||
onClick={() => onViewQrCode && onViewQrCode(reverseShare)}
|
||||
>
|
||||
{t("qrCodeModal.title")}
|
||||
</h3>
|
||||
<Button
|
||||
size="icon"
|
||||
variant="ghost"
|
||||
className="h-5 w-5 text-muted-foreground hover:text-foreground"
|
||||
onClick={() => {
|
||||
const svg = document.getElementById("reverse-share-details-qr-code");
|
||||
if (!svg) return;
|
||||
|
||||
setIsDownloading(true);
|
||||
const canvas = document.createElement("canvas");
|
||||
const ctx = canvas.getContext("2d");
|
||||
const padding = 20;
|
||||
canvas.width = 200 + padding * 2;
|
||||
canvas.height = 200 + padding * 2;
|
||||
|
||||
if (ctx) {
|
||||
ctx.fillStyle = "#FFFFFF";
|
||||
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
||||
|
||||
const svgData = new XMLSerializer().serializeToString(svg);
|
||||
const img = new Image();
|
||||
|
||||
img.onload = () => {
|
||||
ctx.drawImage(img, padding, padding, 200, 200);
|
||||
const link = document.createElement("a");
|
||||
link.download = `${reverseShare?.name?.replace(/[^a-z0-9]/gi, "-").toLowerCase() || "reverse-share"}-qr-code.png`;
|
||||
link.href = canvas.toDataURL("image/png");
|
||||
link.click();
|
||||
setIsDownloading(false);
|
||||
};
|
||||
|
||||
img.src = `data:image/svg+xml;base64,${btoa(svgData)}`;
|
||||
} else {
|
||||
setIsDownloading(false);
|
||||
}
|
||||
}}
|
||||
disabled={isDownloading}
|
||||
title={t("qrCodeModal.download")}
|
||||
>
|
||||
<IconDownload className="h-3 w-3" />
|
||||
</Button>
|
||||
</div>
|
||||
<div className="flex flex-col items-start justify-start">
|
||||
<div
|
||||
className="p-2 bg-white rounded-lg cursor-pointer hover:opacity-80 transition-opacity duration-300"
|
||||
onClick={() => onViewQrCode && onViewQrCode(reverseShare)}
|
||||
title={t("reverseShares.actions.viewQrCode")}
|
||||
>
|
||||
<QRCode
|
||||
id="reverse-share-details-qr-code"
|
||||
value={reverseShareLink}
|
||||
size={100}
|
||||
level="H"
|
||||
fgColor="#000000"
|
||||
bgColor="#FFFFFF"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Link de Compartilhamento */}
|
||||
|
@@ -10,6 +10,7 @@ interface ReverseSharesCardsContainerProps {
|
||||
onGenerateLink: (reverseShare: ReverseShare) => void;
|
||||
onViewDetails: (reverseShare: ReverseShare) => void;
|
||||
onViewFiles: (reverseShare: ReverseShare) => void;
|
||||
onViewQrCode?: (reverseShare: ReverseShare) => void;
|
||||
onCreateReverseShare: () => void;
|
||||
onUpdateReverseShare?: (id: string, data: any) => Promise<any>;
|
||||
onToggleActive?: (id: string, isActive: boolean) => Promise<any>;
|
||||
@@ -24,6 +25,7 @@ export function ReverseSharesCardsContainer({
|
||||
onGenerateLink,
|
||||
onViewDetails,
|
||||
onViewFiles,
|
||||
onViewQrCode,
|
||||
onCreateReverseShare,
|
||||
onUpdateReverseShare,
|
||||
onToggleActive,
|
||||
@@ -45,6 +47,7 @@ export function ReverseSharesCardsContainer({
|
||||
onGenerateLink={onGenerateLink}
|
||||
onViewDetails={onViewDetails}
|
||||
onViewFiles={onViewFiles}
|
||||
onViewQrCode={onViewQrCode}
|
||||
onUpdateReverseShare={onUpdateReverseShare}
|
||||
onToggleActive={onToggleActive}
|
||||
onUpdatePassword={onUpdatePassword}
|
||||
|
@@ -1,3 +1,4 @@
|
||||
import { QrCodeModal } from "@/components/modals/qr-code-modal";
|
||||
import type { CreateReverseShareBody, UpdateReverseShareBody } from "@/http/endpoints/reverse-shares/types";
|
||||
import { ReverseShare } from "../hooks/use-reverse-shares";
|
||||
import { CreateReverseShareModal } from "./create-reverse-share-modal";
|
||||
@@ -20,14 +21,17 @@ interface ReverseSharesModalsProps {
|
||||
reverseShareToGenerateLink: ReverseShare | null;
|
||||
reverseShareToDelete: ReverseShare | null;
|
||||
reverseShareToViewFiles: ReverseShare | null;
|
||||
reverseShareToViewQrCode: ReverseShare | null;
|
||||
isDeleting: boolean;
|
||||
onCloseViewDetails: () => void;
|
||||
onCloseGenerateLink: () => void;
|
||||
onCloseDeleteModal: () => void;
|
||||
onCloseViewFiles: () => void;
|
||||
onCloseViewQrCode: () => void;
|
||||
onConfirmDelete: (reverseShare: ReverseShare) => Promise<void>;
|
||||
onCreateAlias: (reverseShareId: string, alias: string) => Promise<void>;
|
||||
onCopyLink: (reverseShare: ReverseShare) => void;
|
||||
onViewQrCode: (reverseShare: ReverseShare) => void;
|
||||
onUpdateReverseShareData?: (id: string, data: any) => Promise<any>;
|
||||
onUpdatePassword?: (id: string, data: { hasPassword: boolean; password?: string }) => Promise<any>;
|
||||
onToggleActive?: (id: string, isActive: boolean) => Promise<any>;
|
||||
@@ -48,14 +52,17 @@ export function ReverseSharesModals({
|
||||
reverseShareToGenerateLink,
|
||||
reverseShareToDelete,
|
||||
reverseShareToViewFiles,
|
||||
reverseShareToViewQrCode,
|
||||
isDeleting,
|
||||
onCloseViewDetails,
|
||||
onCloseGenerateLink,
|
||||
onCloseDeleteModal,
|
||||
onCloseViewFiles,
|
||||
onCloseViewQrCode,
|
||||
onConfirmDelete,
|
||||
onCreateAlias,
|
||||
onCopyLink,
|
||||
onViewQrCode,
|
||||
onUpdateReverseShareData,
|
||||
onUpdatePassword,
|
||||
onToggleActive,
|
||||
@@ -103,6 +110,7 @@ export function ReverseSharesModals({
|
||||
onCopyLink={onCopyLink}
|
||||
onUpdatePassword={onUpdatePassword}
|
||||
onToggleActive={onToggleActive}
|
||||
onViewQrCode={onViewQrCode}
|
||||
/>
|
||||
|
||||
<ReceivedFilesModal
|
||||
@@ -112,6 +120,17 @@ export function ReverseSharesModals({
|
||||
onRefresh={onRefreshData}
|
||||
refreshReverseShare={refreshReverseShare}
|
||||
/>
|
||||
|
||||
<QrCodeModal
|
||||
isOpen={!!reverseShareToViewQrCode}
|
||||
onClose={onCloseViewQrCode}
|
||||
shareLink={
|
||||
reverseShareToViewQrCode?.alias?.alias
|
||||
? `${typeof window !== "undefined" ? window.location.origin : ""}/r/${reverseShareToViewQrCode.alias.alias}`
|
||||
: ""
|
||||
}
|
||||
shareName={reverseShareToViewQrCode?.name || "Reverse Share"}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
@@ -30,6 +30,7 @@ export function useReverseShares() {
|
||||
const [reverseShareToDelete, setReverseShareToDelete] = useState<ReverseShare | null>(null);
|
||||
const [reverseShareToEdit, setReverseShareToEdit] = useState<ReverseShare | null>(null);
|
||||
const [reverseShareToViewFiles, setReverseShareToViewFiles] = useState<ReverseShare | null>(null);
|
||||
const [reverseShareToViewQrCode, setReverseShareToViewQrCode] = useState<ReverseShare | null>(null);
|
||||
const [isDeleting, setIsDeleting] = useState(false);
|
||||
const [isCreateModalOpen, setIsCreateModalOpen] = useState(false);
|
||||
const [isCreating, setIsCreating] = useState(false);
|
||||
@@ -277,6 +278,7 @@ export function useReverseShares() {
|
||||
reverseShareToDelete,
|
||||
reverseShareToEdit,
|
||||
reverseShareToViewFiles,
|
||||
reverseShareToViewQrCode,
|
||||
isDeleting,
|
||||
isCreateModalOpen,
|
||||
isCreating,
|
||||
@@ -288,6 +290,7 @@ export function useReverseShares() {
|
||||
setReverseShareToDelete,
|
||||
setReverseShareToEdit,
|
||||
setReverseShareToViewFiles,
|
||||
setReverseShareToViewQrCode,
|
||||
setIsCreateModalOpen,
|
||||
handleCopyLink,
|
||||
handleDeleteReverseShare,
|
||||
|
@@ -23,6 +23,7 @@ export default function ReverseSharesPage() {
|
||||
reverseShareToDelete,
|
||||
reverseShareToEdit,
|
||||
reverseShareToViewFiles,
|
||||
reverseShareToViewQrCode,
|
||||
isDeleting,
|
||||
isCreateModalOpen,
|
||||
isCreating,
|
||||
@@ -37,6 +38,7 @@ export default function ReverseSharesPage() {
|
||||
setReverseShareToDelete,
|
||||
setReverseShareToEdit,
|
||||
setReverseShareToViewFiles,
|
||||
setReverseShareToViewQrCode,
|
||||
handleCreateAlias,
|
||||
handleUpdatePassword,
|
||||
handleUpdateReverseShareData,
|
||||
@@ -77,6 +79,7 @@ export default function ReverseSharesPage() {
|
||||
onGenerateLink={setReverseShareToGenerateLink}
|
||||
onViewDetails={setReverseShareToViewDetails}
|
||||
onViewFiles={setReverseShareToViewFiles}
|
||||
onViewQrCode={setReverseShareToViewQrCode}
|
||||
onCreateReverseShare={() => setIsCreateModalOpen(true)}
|
||||
onUpdateReverseShare={handleUpdateReverseShareData}
|
||||
onToggleActive={handleToggleActive}
|
||||
@@ -99,14 +102,17 @@ export default function ReverseSharesPage() {
|
||||
reverseShareToViewDetails={reverseShareToViewDetails}
|
||||
reverseShareToDelete={reverseShareToDelete}
|
||||
reverseShareToViewFiles={reverseShareToViewFiles}
|
||||
reverseShareToViewQrCode={reverseShareToViewQrCode}
|
||||
isDeleting={isDeleting}
|
||||
onCloseGenerateLink={() => setReverseShareToGenerateLink(null)}
|
||||
onCloseViewDetails={() => setReverseShareToViewDetails(null)}
|
||||
onCloseDeleteModal={() => setReverseShareToDelete(null)}
|
||||
onCloseViewFiles={() => setReverseShareToViewFiles(null)}
|
||||
onCloseViewQrCode={() => setReverseShareToViewQrCode(null)}
|
||||
onConfirmDelete={handleDeleteReverseShare}
|
||||
onCreateAlias={handleCreateAlias}
|
||||
onCopyLink={handleCopyLink}
|
||||
onViewQrCode={setReverseShareToViewQrCode}
|
||||
onUpdateReverseShareData={handleUpdateReverseShareData}
|
||||
onUpdatePassword={handleUpdatePassword}
|
||||
onToggleActive={handleToggleActive}
|
||||
|
@@ -4,6 +4,7 @@ import { useTranslations } from "next-intl";
|
||||
import { CreateShareModal } from "@/components/modals/create-share-modal";
|
||||
import { DeleteConfirmationModal } from "@/components/modals/delete-confirmation-modal";
|
||||
import { GenerateShareLinkModal } from "@/components/modals/generate-share-link-modal";
|
||||
import { QrCodeModal } from "@/components/modals/qr-code-modal";
|
||||
import { ShareActionsModals } from "@/components/modals/share-actions-modals";
|
||||
import { ShareDetailsModal } from "@/components/modals/share-details-modal";
|
||||
import { ShareExpirationModal } from "@/components/modals/share-expiration-modal";
|
||||
@@ -30,6 +31,11 @@ export function SharesModals({
|
||||
onSuccess();
|
||||
};
|
||||
|
||||
const getShareLink = (share: any) => {
|
||||
if (!share?.alias?.alias) return "";
|
||||
return `${window.location.origin}/s/${share.alias.alias}`;
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<CreateShareModal isOpen={isCreateModalOpen} onClose={onCloseCreateModal} onSuccess={handleShareSuccess} />
|
||||
@@ -51,6 +57,13 @@ export function SharesModals({
|
||||
onEditFile={fileManager.handleRename}
|
||||
/>
|
||||
|
||||
<QrCodeModal
|
||||
isOpen={!!shareManager.shareToViewQrCode}
|
||||
onClose={() => shareManager.setShareToViewQrCode(null)}
|
||||
shareLink={getShareLink(shareManager.shareToViewQrCode)}
|
||||
shareName={shareManager.shareToViewQrCode?.name || "Share"}
|
||||
/>
|
||||
|
||||
<DeleteConfirmationModal
|
||||
isOpen={!!shareManager.sharesToDelete}
|
||||
onClose={() => shareManager.setSharesToDelete(null)}
|
||||
|
@@ -20,6 +20,7 @@ export function SharesTableContainer({ shares, onCopyLink, onCreateShare, shareM
|
||||
onManageFiles={shareManager.setShareToManageFiles}
|
||||
onManageRecipients={shareManager.setShareToManageRecipients}
|
||||
onNotifyRecipients={shareManager.handleNotifyRecipients}
|
||||
onViewQrCode={shareManager.setShareToViewQrCode}
|
||||
onViewDetails={shareManager.setShareToViewDetails}
|
||||
setClearSelectionCallback={shareManager.setClearSelectionCallback}
|
||||
/>
|
||||
|
@@ -62,6 +62,7 @@ export function RecentShares({ shares, shareManager, onOpenCreateModal, onCopyLi
|
||||
onManageFiles={shareManager.setShareToManageFiles}
|
||||
onManageRecipients={shareManager.setShareToManageRecipients}
|
||||
onNotifyRecipients={shareManager.handleNotifyRecipients}
|
||||
onViewQrCode={shareManager.setShareToViewQrCode}
|
||||
onViewDetails={shareManager.setShareToViewDetails}
|
||||
setClearSelectionCallback={shareManager.setClearSelectionCallback}
|
||||
/>
|
||||
|
@@ -7,6 +7,7 @@ import { DeleteConfirmationModal } from "@/components/modals/delete-confirmation
|
||||
import { FileActionsModals } from "@/components/modals/file-actions-modals";
|
||||
import { FilePreviewModal } from "@/components/modals/file-preview-modal";
|
||||
import { GenerateShareLinkModal } from "@/components/modals/generate-share-link-modal";
|
||||
import { QrCodeModal } from "@/components/modals/qr-code-modal";
|
||||
import { ShareActionsModals } from "@/components/modals/share-actions-modals";
|
||||
import { ShareDetailsModal } from "@/components/modals/share-details-modal";
|
||||
import { ShareExpirationModal } from "@/components/modals/share-expiration-modal";
|
||||
@@ -25,6 +26,11 @@ export function DashboardModals({ modals, fileManager, shareManager, onSuccess }
|
||||
onSuccess();
|
||||
};
|
||||
|
||||
const getShareLink = (share: any) => {
|
||||
if (!share?.alias?.alias) return "";
|
||||
return `${window.location.origin}/s/${share.alias.alias}`;
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<UploadFileModal isOpen={modals.isUploadModalOpen} onClose={modals.onCloseUploadModal} onSuccess={onSuccess} />
|
||||
@@ -144,6 +150,13 @@ export function DashboardModals({ modals, fileManager, shareManager, onSuccess }
|
||||
onGenerate={shareManager.handleGenerateLink}
|
||||
onSuccess={onSuccess}
|
||||
/>
|
||||
|
||||
<QrCodeModal
|
||||
isOpen={!!shareManager.shareToViewQrCode}
|
||||
onClose={() => shareManager.setShareToViewQrCode(null)}
|
||||
shareLink={getShareLink(shareManager.shareToViewQrCode)}
|
||||
shareName={shareManager.shareToViewQrCode?.name || "Share"}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
@@ -1,8 +1,9 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
import { IconCopy } from "@tabler/icons-react";
|
||||
import { IconCopy, IconDownload } from "@tabler/icons-react";
|
||||
import { useTranslations } from "next-intl";
|
||||
import QRCode from "react-qr-code";
|
||||
import { toast } from "sonner";
|
||||
|
||||
import { Button } from "@/components/ui/button";
|
||||
@@ -33,6 +34,7 @@ export function GenerateShareLinkModal({
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [generatedLink, setGeneratedLink] = useState("");
|
||||
const [isEdit, setIsEdit] = useState(false);
|
||||
const [isDownloading, setIsDownloading] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (shareId && share?.alias?.alias) {
|
||||
@@ -68,9 +70,56 @@ export function GenerateShareLinkModal({
|
||||
toast.success(t("generateShareLink.copied"));
|
||||
};
|
||||
|
||||
const downloadQRCode = () => {
|
||||
setIsDownloading(true);
|
||||
|
||||
// Get the SVG element
|
||||
const svg = document.getElementById("share-link-qr-code");
|
||||
if (!svg) {
|
||||
setIsDownloading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
// Create a canvas
|
||||
const canvas = document.createElement("canvas");
|
||||
const ctx = canvas.getContext("2d");
|
||||
|
||||
// Set dimensions (with some padding)
|
||||
const padding = 20;
|
||||
canvas.width = 256 + padding * 2;
|
||||
canvas.height = 256 + padding * 2;
|
||||
|
||||
// Fill white background
|
||||
if (ctx) {
|
||||
ctx.fillStyle = "#FFFFFF";
|
||||
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
||||
|
||||
// Convert SVG to data URL
|
||||
const svgData = new XMLSerializer().serializeToString(svg);
|
||||
const img = new Image();
|
||||
|
||||
img.onload = () => {
|
||||
// Draw the image in the center of the canvas with padding
|
||||
ctx.drawImage(img, padding, padding, 256, 256);
|
||||
|
||||
// Create a download link
|
||||
const link = document.createElement("a");
|
||||
link.download = `${share?.name?.replace(/[^a-z0-9]/gi, "-").toLowerCase() || "share"}-qr-code.png`;
|
||||
link.href = canvas.toDataURL("image/png");
|
||||
link.click();
|
||||
|
||||
setIsDownloading(false);
|
||||
};
|
||||
|
||||
img.src = `data:image/svg+xml;base64,${btoa(svgData)}`;
|
||||
} else {
|
||||
setIsDownloading(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog open={!!shareId} onOpenChange={() => onClose()}>
|
||||
<DialogContent>
|
||||
<DialogContent className="sm:max-w-md">
|
||||
<DialogHeader>
|
||||
<DialogTitle>
|
||||
{isEdit ? t("generateShareLink.updateTitle") : t("generateShareLink.generateTitle")}
|
||||
@@ -88,23 +137,55 @@ export function GenerateShareLinkModal({
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-4">
|
||||
<p className="text-sm text-muted-foreground">{t("generateShareLink.linkReady")}</p>
|
||||
<Input readOnly value={generatedLink} />
|
||||
<div className="space-y-6">
|
||||
<p className="text-sm text-muted-foreground text-left">
|
||||
{t("generateShareLink.readyDescription", {
|
||||
defaultValue:
|
||||
"Your share link is ready. You can scan the QR code directly, download it for later use, or copy the link below.",
|
||||
})}
|
||||
</p>
|
||||
<div className="flex flex-col items-center justify-center">
|
||||
<div className="p-4 bg-white rounded-lg">
|
||||
<QRCode
|
||||
id="share-link-qr-code"
|
||||
value={generatedLink}
|
||||
size={200}
|
||||
level="H"
|
||||
fgColor="#000000"
|
||||
bgColor="#FFFFFF"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<div className="flex space-x-2">
|
||||
<Input readOnly value={generatedLink} className="flex-1" />
|
||||
<Button
|
||||
variant="outline"
|
||||
size="icon"
|
||||
onClick={handleCopyLink}
|
||||
title={t("generateShareLink.copyButton")}
|
||||
>
|
||||
<IconCopy className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<DialogFooter>
|
||||
<Button onClick={downloadQRCode} disabled={isDownloading}>
|
||||
<IconDownload className="h-4 w-4" />
|
||||
{t("qrCodeModal.download")}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</div>
|
||||
)}
|
||||
<DialogFooter>
|
||||
{!generatedLink ? (
|
||||
{!generatedLink && (
|
||||
<DialogFooter>
|
||||
<Button disabled={!alias || isLoading} onClick={handleGenerate}>
|
||||
{isEdit ? t("generateShareLink.updateButton") : t("generateShareLink.generateButton")}
|
||||
</Button>
|
||||
) : (
|
||||
<Button onClick={handleCopyLink}>
|
||||
<IconCopy className="h-4 w-4" />
|
||||
{t("generateShareLink.copyButton")}
|
||||
</Button>
|
||||
)}
|
||||
</DialogFooter>
|
||||
</DialogFooter>
|
||||
)}
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
|
13
apps/web/src/components/modals/index.ts
Normal file
13
apps/web/src/components/modals/index.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
export { QrCodeModal } from "./qr-code-modal";
|
||||
export { UploadFileModal } from "./upload-file-modal";
|
||||
export { CreateShareModal } from "./create-share-modal";
|
||||
export { ShareSecurityModal } from "./share-security-modal";
|
||||
export { ShareFileModal } from "./share-file-modal";
|
||||
export { ShareMultipleFilesModal } from "./share-multiple-files-modal";
|
||||
export { ShareDetailsModal } from "./share-details-modal";
|
||||
export { FilePreviewModal } from "./file-preview-modal";
|
||||
export { GenerateShareLinkModal } from "./generate-share-link-modal";
|
||||
export { ImageEditModal } from "./image-edit-modal";
|
||||
export { DeleteConfirmationModal } from "./delete-confirmation-modal";
|
||||
export { BulkDownloadModal } from "./bulk-download-modal";
|
||||
export { ShareExpirationModal } from "./share-expiration-modal";
|
103
apps/web/src/components/modals/qr-code-modal.tsx
Normal file
103
apps/web/src/components/modals/qr-code-modal.tsx
Normal file
@@ -0,0 +1,103 @@
|
||||
import { useState } from "react";
|
||||
import { IconDownload } from "@tabler/icons-react";
|
||||
import { useTranslations } from "next-intl";
|
||||
import QRCode from "react-qr-code";
|
||||
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from "@/components/ui/dialog";
|
||||
|
||||
interface QrCodeModalProps {
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
shareLink: string;
|
||||
shareName: string;
|
||||
}
|
||||
|
||||
export function QrCodeModal({ isOpen, onClose, shareLink, shareName }: QrCodeModalProps) {
|
||||
const t = useTranslations();
|
||||
const [isDownloading, setIsDownloading] = useState(false);
|
||||
|
||||
const downloadQRCode = () => {
|
||||
setIsDownloading(true);
|
||||
|
||||
// Get the SVG element
|
||||
const svg = document.getElementById("share-qr-code");
|
||||
if (!svg) {
|
||||
setIsDownloading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
// Create a canvas
|
||||
const canvas = document.createElement("canvas");
|
||||
const ctx = canvas.getContext("2d");
|
||||
|
||||
// Set dimensions (with some padding)
|
||||
const padding = 20;
|
||||
canvas.width = 256 + padding * 2;
|
||||
canvas.height = 256 + padding * 2;
|
||||
|
||||
// Fill white background
|
||||
if (ctx) {
|
||||
ctx.fillStyle = "#FFFFFF";
|
||||
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
||||
|
||||
// Convert SVG to data URL
|
||||
const svgData = new XMLSerializer().serializeToString(svg);
|
||||
const img = new Image();
|
||||
|
||||
img.onload = () => {
|
||||
// Draw the image in the center of the canvas with padding
|
||||
ctx.drawImage(img, padding, padding, 256, 256);
|
||||
|
||||
// Create a download link
|
||||
const link = document.createElement("a");
|
||||
link.download = `${shareName.replace(/[^a-z0-9]/gi, "-").toLowerCase()}-qr-code.png`;
|
||||
link.href = canvas.toDataURL("image/png");
|
||||
link.click();
|
||||
|
||||
setIsDownloading(false);
|
||||
};
|
||||
|
||||
img.src = `data:image/svg+xml;base64,${btoa(svgData)}`;
|
||||
} else {
|
||||
setIsDownloading(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog open={isOpen} onOpenChange={(open) => !open && onClose()}>
|
||||
<DialogContent className="sm:max-w-md">
|
||||
<DialogHeader>
|
||||
<DialogTitle>{t("qrCodeModal.title", { defaultValue: "Share QR Code" })}</DialogTitle>
|
||||
<DialogDescription>
|
||||
{t("qrCodeModal.description", { defaultValue: "Scan this QR code to access the shared files." })}
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
<div className="flex flex-col items-center justify-center">
|
||||
<div className="p-4 bg-white rounded-lg">
|
||||
<QRCode id="share-qr-code" value={shareLink} size={256} level="H" fgColor="#000000" bgColor="#FFFFFF" />
|
||||
</div>
|
||||
<p className="mt-4 text-sm text-muted-foreground text-center max-w-full break-all">{shareLink}</p>
|
||||
</div>
|
||||
|
||||
<DialogFooter className="sm:justify-between flex-row">
|
||||
<Button variant="outline" onClick={onClose} className="mt-2 sm:mt-0">
|
||||
{t("common.close")}
|
||||
</Button>
|
||||
<Button onClick={downloadQRCode} className="mt-2 sm:mt-0" disabled={isDownloading}>
|
||||
<IconDownload className="h-4 w-4" />
|
||||
{t("qrCodeModal.download", { defaultValue: "Download QR Code" })}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
@@ -4,6 +4,7 @@ import { useCallback, useEffect, useRef, useState } from "react";
|
||||
import {
|
||||
IconCheck,
|
||||
IconCopy,
|
||||
IconDownload,
|
||||
IconEdit,
|
||||
IconExternalLink,
|
||||
IconLock,
|
||||
@@ -13,6 +14,7 @@ import {
|
||||
} from "@tabler/icons-react";
|
||||
import { format } from "date-fns";
|
||||
import { useTranslations } from "next-intl";
|
||||
import QRCode from "react-qr-code";
|
||||
import { toast } from "sonner";
|
||||
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
@@ -30,6 +32,7 @@ import { Loader } from "@/components/ui/loader";
|
||||
import { getShare } from "@/http/endpoints";
|
||||
import { getFileIcon } from "@/utils/file-icons";
|
||||
import { GenerateShareLinkModal } from "./generate-share-link-modal";
|
||||
import { QrCodeModal } from "./qr-code-modal";
|
||||
import { ShareExpirationModal } from "./share-expiration-modal";
|
||||
import { ShareSecurityModal } from "./share-security-modal";
|
||||
|
||||
@@ -86,6 +89,8 @@ export function ShareDetailsModal({
|
||||
const [showLinkModal, setShowLinkModal] = useState(false);
|
||||
const [showSecurityModal, setShowSecurityModal] = useState(false);
|
||||
const [showExpirationModal, setShowExpirationModal] = useState(false);
|
||||
const [showQrCodeModal, setShowQrCodeModal] = useState(false);
|
||||
const [isDownloading, setIsDownloading] = useState(false);
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
|
||||
const loadShareDetails = useCallback(async () => {
|
||||
@@ -209,6 +214,53 @@ export function ShareDetailsModal({
|
||||
}
|
||||
};
|
||||
|
||||
const downloadQRCode = () => {
|
||||
setIsDownloading(true);
|
||||
|
||||
// Get the SVG element
|
||||
const svg = document.getElementById("share-details-qr-code");
|
||||
if (!svg) {
|
||||
setIsDownloading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
// Create a canvas
|
||||
const canvas = document.createElement("canvas");
|
||||
const ctx = canvas.getContext("2d");
|
||||
|
||||
// Set dimensions (with some padding)
|
||||
const padding = 20;
|
||||
canvas.width = 200 + padding * 2;
|
||||
canvas.height = 200 + padding * 2;
|
||||
|
||||
// Fill white background
|
||||
if (ctx) {
|
||||
ctx.fillStyle = "#FFFFFF";
|
||||
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
||||
|
||||
// Convert SVG to data URL
|
||||
const svgData = new XMLSerializer().serializeToString(svg);
|
||||
const img = new Image();
|
||||
|
||||
img.onload = () => {
|
||||
// Draw the image in the center of the canvas with padding
|
||||
ctx.drawImage(img, padding, padding, 200, 200);
|
||||
|
||||
// Create a download link
|
||||
const link = document.createElement("a");
|
||||
link.download = `${share?.name?.replace(/[^a-z0-9]/gi, "-").toLowerCase() || "share"}-qr-code.png`;
|
||||
link.href = canvas.toDataURL("image/png");
|
||||
link.click();
|
||||
|
||||
setIsDownloading(false);
|
||||
};
|
||||
|
||||
img.src = `data:image/svg+xml;base64,${btoa(svgData)}`;
|
||||
} else {
|
||||
setIsDownloading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleLinkGenerated = async () => {
|
||||
setShowLinkModal(false);
|
||||
await loadShareDetails();
|
||||
@@ -258,7 +310,7 @@ export function ShareDetailsModal({
|
||||
<div className="space-y-4">
|
||||
<div className="grid grid-cols-3 gap-3">
|
||||
<div className="text-center p-2 bg-muted/30 rounded-lg">
|
||||
<p className="text-lg font-semibold text-green-600">{share.viewCount || 0}</p>
|
||||
<p className="text-lg font-semibold text-green-600">{share.views || 0}</p>
|
||||
<p className="text-xs text-muted-foreground">{t("shareDetails.views")}</p>
|
||||
</div>
|
||||
<div className="text-center p-2 bg-muted/30 rounded-lg">
|
||||
@@ -271,105 +323,148 @@ export function ShareDetailsModal({
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-3">
|
||||
<div className="flex items-center gap-2 border-b pb-2">
|
||||
<h3 className="text-base font-medium text-foreground">{t("shareDetails.basicInfo")}</h3>
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
{/* Basic Information */}
|
||||
<div className="space-y-3">
|
||||
<div className="flex items-center gap-2 border-b pb-2">
|
||||
<h3 className="text-base font-medium text-foreground">{t("shareDetails.basicInfo")}</h3>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div className="flex items-center gap-2 mb-1">
|
||||
<label className="text-sm font-medium text-muted-foreground">{t("shareDetails.name")}</label>
|
||||
{onUpdateName && !isEditingName && (
|
||||
<Button
|
||||
size="icon"
|
||||
variant="ghost"
|
||||
className="h-5 w-5 text-muted-foreground hover:text-foreground"
|
||||
onClick={() => startEdit("name", displayName || "")}
|
||||
>
|
||||
<IconEdit className="h-3 w-3" />
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
{isEditingName ? (
|
||||
<div className="flex items-center gap-2">
|
||||
<Input
|
||||
ref={inputRef}
|
||||
value={editValue}
|
||||
onChange={(e) => setEditValue(e.target.value)}
|
||||
onKeyDown={handleKeyDown}
|
||||
className="h-8 flex-1 text-sm"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
/>
|
||||
<Button
|
||||
size="icon"
|
||||
variant="ghost"
|
||||
className="h-6 w-6 text-green-600 hover:text-green-700"
|
||||
onClick={saveEdit}
|
||||
>
|
||||
<IconCheck className="h-3 w-3" />
|
||||
</Button>
|
||||
<Button
|
||||
size="icon"
|
||||
variant="ghost"
|
||||
className="h-6 w-6 text-red-600 hover:text-red-700"
|
||||
onClick={cancelEdit}
|
||||
>
|
||||
<IconX className="h-3 w-3" />
|
||||
</Button>
|
||||
</div>
|
||||
) : (
|
||||
<span className="text-sm font-medium block">{displayName || t("shareDetails.untitled")}</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div className="flex items-center gap-2 mb-1">
|
||||
<label className="text-sm font-medium text-muted-foreground">
|
||||
{t("shareDetails.description")}
|
||||
</label>
|
||||
{onUpdateDescription && !isEditingDescription && (
|
||||
<Button
|
||||
size="icon"
|
||||
variant="ghost"
|
||||
className="h-5 w-5 text-muted-foreground hover:text-foreground"
|
||||
onClick={() => startEdit("description", displayDescription || "")}
|
||||
>
|
||||
<IconEdit className="h-3 w-3" />
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
{isEditingDescription ? (
|
||||
<div className="flex items-center gap-2">
|
||||
<Input
|
||||
ref={inputRef}
|
||||
value={editValue}
|
||||
onChange={(e) => setEditValue(e.target.value)}
|
||||
onKeyDown={handleKeyDown}
|
||||
className="h-8 flex-1 text-sm"
|
||||
placeholder={t("shareDetails.noDescription")}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
/>
|
||||
<Button
|
||||
size="icon"
|
||||
variant="ghost"
|
||||
className="h-6 w-6 text-green-600 hover:text-green-700"
|
||||
onClick={saveEdit}
|
||||
>
|
||||
<IconCheck className="h-3 w-3" />
|
||||
</Button>
|
||||
<Button
|
||||
size="icon"
|
||||
variant="ghost"
|
||||
className="h-6 w-6 text-red-600 hover:text-red-700"
|
||||
onClick={cancelEdit}
|
||||
>
|
||||
<IconX className="h-3 w-3" />
|
||||
</Button>
|
||||
</div>
|
||||
) : (
|
||||
<span className="text-sm block">{displayDescription || t("shareDetails.noDescription")}</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div className="flex items-center gap-2 mb-1">
|
||||
<label className="text-sm font-medium text-muted-foreground">{t("shareDetails.name")}</label>
|
||||
{onUpdateName && !isEditingName && (
|
||||
{/* QR Code */}
|
||||
{shareLink && (
|
||||
<div className="space-y-3">
|
||||
<div className="flex items-center gap-2 border-b pb-2">
|
||||
<h3
|
||||
className="text-base font-medium text-foreground cursor-pointer"
|
||||
onClick={() => setShowQrCodeModal(true)}
|
||||
>
|
||||
{t("shareDetails.qrCode", { defaultValue: "QR Code" })}
|
||||
</h3>
|
||||
<Button
|
||||
size="icon"
|
||||
variant="ghost"
|
||||
className="h-5 w-5 text-muted-foreground hover:text-foreground"
|
||||
onClick={() => startEdit("name", displayName || "")}
|
||||
onClick={downloadQRCode}
|
||||
disabled={isDownloading}
|
||||
title={t("shareDetails.downloadQrCode")}
|
||||
>
|
||||
<IconEdit className="h-3 w-3" />
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
{isEditingName ? (
|
||||
<div className="flex items-center gap-2">
|
||||
<Input
|
||||
ref={inputRef}
|
||||
value={editValue}
|
||||
onChange={(e) => setEditValue(e.target.value)}
|
||||
onKeyDown={handleKeyDown}
|
||||
className="h-8 flex-1 text-sm"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
/>
|
||||
<Button
|
||||
size="icon"
|
||||
variant="ghost"
|
||||
className="h-6 w-6 text-green-600 hover:text-green-700"
|
||||
onClick={saveEdit}
|
||||
>
|
||||
<IconCheck className="h-3 w-3" />
|
||||
</Button>
|
||||
<Button
|
||||
size="icon"
|
||||
variant="ghost"
|
||||
className="h-6 w-6 text-red-600 hover:text-red-700"
|
||||
onClick={cancelEdit}
|
||||
>
|
||||
<IconX className="h-3 w-3" />
|
||||
<IconDownload className="h-3 w-3" />
|
||||
</Button>
|
||||
</div>
|
||||
) : (
|
||||
<span className="text-sm font-medium block">{displayName || t("shareDetails.untitled")}</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div className="flex items-center gap-2 mb-1">
|
||||
<label className="text-sm font-medium text-muted-foreground">
|
||||
{t("shareDetails.description")}
|
||||
</label>
|
||||
{onUpdateDescription && !isEditingDescription && (
|
||||
<Button
|
||||
size="icon"
|
||||
variant="ghost"
|
||||
className="h-5 w-5 text-muted-foreground hover:text-foreground"
|
||||
onClick={() => startEdit("description", displayDescription || "")}
|
||||
<div className="flex flex-col items-start justify-start ">
|
||||
<div
|
||||
className="p-2 bg-white rounded-lg cursor-pointer hover:opacity-80 transition-opacity duration-300"
|
||||
onClick={() => setShowQrCodeModal(true)}
|
||||
title={t("shareDetails.clickToEnlargeQrCode", { defaultValue: "Click to enlarge QR Code" })}
|
||||
>
|
||||
<IconEdit className="h-3 w-3" />
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
{isEditingDescription ? (
|
||||
<div className="flex items-center gap-2">
|
||||
<Input
|
||||
ref={inputRef}
|
||||
value={editValue}
|
||||
onChange={(e) => setEditValue(e.target.value)}
|
||||
onKeyDown={handleKeyDown}
|
||||
className="h-8 flex-1 text-sm"
|
||||
placeholder={t("shareDetails.noDescription")}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
/>
|
||||
<Button
|
||||
size="icon"
|
||||
variant="ghost"
|
||||
className="h-6 w-6 text-green-600 hover:text-green-700"
|
||||
onClick={saveEdit}
|
||||
>
|
||||
<IconCheck className="h-3 w-3" />
|
||||
</Button>
|
||||
<Button
|
||||
size="icon"
|
||||
variant="ghost"
|
||||
className="h-6 w-6 text-red-600 hover:text-red-700"
|
||||
onClick={cancelEdit}
|
||||
>
|
||||
<IconX className="h-3 w-3" />
|
||||
</Button>
|
||||
<QRCode
|
||||
id="share-details-qr-code"
|
||||
value={shareLink}
|
||||
size={100}
|
||||
level="H"
|
||||
fgColor="#000000"
|
||||
bgColor="#FFFFFF"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<span className="text-sm block">{displayDescription || t("shareDetails.noDescription")}</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="space-y-3">
|
||||
@@ -556,16 +651,16 @@ export function ShareDetailsModal({
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
{showLinkModal && onGenerateLink && (
|
||||
{showLinkModal && shareId && (
|
||||
<GenerateShareLinkModal
|
||||
shareId={shareId}
|
||||
share={share}
|
||||
onClose={() => setShowLinkModal(false)}
|
||||
onGenerate={onGenerateLink}
|
||||
onSuccess={handleLinkGenerated}
|
||||
onGenerate={onGenerateLink || (() => Promise.resolve())}
|
||||
/>
|
||||
)}
|
||||
{showSecurityModal && (
|
||||
{showSecurityModal && shareId && onUpdateSecurity && (
|
||||
<ShareSecurityModal
|
||||
shareId={shareId}
|
||||
share={share}
|
||||
@@ -573,7 +668,7 @@ export function ShareDetailsModal({
|
||||
onSuccess={handleSecurityUpdated}
|
||||
/>
|
||||
)}
|
||||
{showExpirationModal && (
|
||||
{showExpirationModal && shareId && onUpdateExpiration && (
|
||||
<ShareExpirationModal
|
||||
shareId={shareId}
|
||||
share={share}
|
||||
@@ -581,6 +676,14 @@ export function ShareDetailsModal({
|
||||
onSuccess={handleExpirationUpdated}
|
||||
/>
|
||||
)}
|
||||
{showQrCodeModal && shareLink && (
|
||||
<QrCodeModal
|
||||
isOpen={showQrCodeModal}
|
||||
onClose={() => setShowQrCodeModal(false)}
|
||||
shareLink={shareLink}
|
||||
shareName={share?.name || "Share"}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
@@ -1,8 +1,9 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
import { IconCalendar, IconCopy, IconEye, IconLink, IconLock, IconShare } from "@tabler/icons-react";
|
||||
import { IconCalendar, IconCopy, IconDownload, IconEye, IconLink, IconLock, IconShare } from "@tabler/icons-react";
|
||||
import { useTranslations } from "next-intl";
|
||||
import QRCode from "react-qr-code";
|
||||
import { toast } from "sonner";
|
||||
|
||||
import { Button } from "@/components/ui/button";
|
||||
@@ -123,6 +124,18 @@ export function ShareFileModal({ isOpen, file, onClose, onSuccess }: ShareFileMo
|
||||
toast.success(t("generateShareLink.copied"));
|
||||
};
|
||||
|
||||
const downloadQRCode = () => {
|
||||
const canvas = document.getElementById("share-file-qr-code") as HTMLCanvasElement;
|
||||
if (canvas) {
|
||||
const link = document.createElement("a");
|
||||
link.download = "share-file-qr-code.png";
|
||||
link.href = canvas.toDataURL("image/png");
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
}
|
||||
};
|
||||
|
||||
const handleClose = () => {
|
||||
onClose();
|
||||
setTimeout(() => {
|
||||
@@ -259,8 +272,26 @@ export function ShareFileModal({ isOpen, file, onClose, onSuccess }: ShareFileMo
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<div className="flex flex-col items-center justify-center">
|
||||
<div className="p-4 bg-white rounded-lg">
|
||||
<svg style={{ display: "none" }} /> {/* For SSR safety */}
|
||||
<QRCode
|
||||
id="share-file-qr-code"
|
||||
value={generatedLink}
|
||||
size={250}
|
||||
level="H"
|
||||
fgColor="#000000"
|
||||
bgColor="#FFFFFF"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<p className="text-sm text-muted-foreground">{t("shareFile.linkReady")}</p>
|
||||
<Input readOnly value={generatedLink} />
|
||||
<div className="flex gap-2">
|
||||
<Input readOnly value={generatedLink} className="flex-1" />
|
||||
<Button size="icon" variant="outline" onClick={handleCopyLink} title={t("shareFile.copyLink")}>
|
||||
<IconCopy className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
@@ -294,9 +325,9 @@ export function ShareFileModal({ isOpen, file, onClose, onSuccess }: ShareFileMo
|
||||
<Button variant="outline" onClick={handleSuccess}>
|
||||
{t("common.close")}
|
||||
</Button>
|
||||
<Button onClick={handleCopyLink}>
|
||||
<IconCopy className="h-4 w-4" />
|
||||
{t("shareFile.copyLink")}
|
||||
<Button onClick={downloadQRCode}>
|
||||
<IconDownload className="h-4 w-4" />
|
||||
{t("qrCodeModal.download")}
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
|
@@ -1,8 +1,9 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
import { IconCalendar, IconCopy, IconEye, IconLink, IconLock, IconShare } from "@tabler/icons-react";
|
||||
import { IconCalendar, IconCopy, IconDownload, IconEye, IconLink, IconLock, IconShare } from "@tabler/icons-react";
|
||||
import { useTranslations } from "next-intl";
|
||||
import QRCode from "react-qr-code";
|
||||
import { toast } from "sonner";
|
||||
|
||||
import { Button } from "@/components/ui/button";
|
||||
@@ -115,6 +116,19 @@ export function ShareMultipleFilesModal({ files, isOpen, onClose, onSuccess }: S
|
||||
toast.success(t("generateShareLink.copied"));
|
||||
};
|
||||
|
||||
const downloadQRCode = () => {
|
||||
const qrCodeElement = document.getElementById("share-multiple-files-qr-code");
|
||||
if (qrCodeElement) {
|
||||
const canvas = qrCodeElement.querySelector("canvas");
|
||||
if (canvas) {
|
||||
const link = document.createElement("a");
|
||||
link.download = "share-multiple-files-qr-code.png";
|
||||
link.href = canvas.toDataURL("image/png");
|
||||
link.click();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleClose = () => {
|
||||
onClose();
|
||||
setTimeout(() => {
|
||||
@@ -282,8 +296,26 @@ export function ShareMultipleFilesModal({ files, isOpen, onClose, onSuccess }: S
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<div className="flex flex-col items-center justify-center">
|
||||
<div className="p-4 bg-white rounded-lg">
|
||||
<svg style={{ display: "none" }} /> {/* For SSR safety */}
|
||||
<QRCode
|
||||
id="share-multiple-files-qr-code"
|
||||
value={generatedLink}
|
||||
size={250}
|
||||
level="H"
|
||||
fgColor="#000000"
|
||||
bgColor="#FFFFFF"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<p className="text-sm text-muted-foreground">{t("shareFile.linkReady")}</p>
|
||||
<Input readOnly value={generatedLink} />
|
||||
<div className="flex gap-2">
|
||||
<Input readOnly value={generatedLink} className="flex-1" />
|
||||
<Button variant="outline" size="icon" onClick={handleCopyLink} title={t("shareFile.copyLink")}>
|
||||
<IconCopy className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
@@ -323,9 +355,9 @@ export function ShareMultipleFilesModal({ files, isOpen, onClose, onSuccess }: S
|
||||
<Button variant="outline" onClick={handleSuccess}>
|
||||
{t("common.close")}
|
||||
</Button>
|
||||
<Button onClick={handleCopyLink}>
|
||||
<IconCopy className="h-4 w-4" />
|
||||
{t("shareFile.copyLink")}
|
||||
<Button onClick={downloadQRCode}>
|
||||
<IconDownload className="h-4 w-4" />
|
||||
{t("qrCodeModal.download")}
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
|
@@ -12,6 +12,7 @@ import {
|
||||
IconLock,
|
||||
IconLockOpen,
|
||||
IconMail,
|
||||
IconQrcode,
|
||||
IconTrash,
|
||||
IconUsers,
|
||||
IconX,
|
||||
@@ -46,6 +47,7 @@ export interface SharesTableProps {
|
||||
onGenerateLink: (share: any) => void;
|
||||
onCopyLink: (share: any) => void;
|
||||
onNotifyRecipients: (share: any) => void;
|
||||
onViewQrCode?: (share: any) => void;
|
||||
onDownloadShareFiles?: (share: any) => void;
|
||||
onBulkDelete?: (shares: any[]) => void;
|
||||
onBulkDownload?: (shares: any[]) => void;
|
||||
@@ -66,6 +68,7 @@ export function SharesTable({
|
||||
onGenerateLink,
|
||||
onCopyLink,
|
||||
onNotifyRecipients,
|
||||
onViewQrCode,
|
||||
onDownloadShareFiles,
|
||||
onBulkDelete,
|
||||
onBulkDownload,
|
||||
@@ -604,6 +607,12 @@ export function SharesTable({
|
||||
{t("sharesTable.actions.copyLink")}
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
{share.alias && onViewQrCode && (
|
||||
<DropdownMenuItem className="cursor-pointer py-2" onClick={() => onViewQrCode(share)}>
|
||||
<IconQrcode className="h-4 w-4" />
|
||||
{t("sharesTable.actions.viewQrCode", { defaultValue: "View QR Code" })}
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
{share.recipients?.length > 0 && share.alias && smtpEnabled === "true" && (
|
||||
<DropdownMenuItem className="cursor-pointer py-2" onClick={() => onNotifyRecipients(share)}>
|
||||
<IconMail className="h-4 w-4" />
|
||||
|
@@ -24,6 +24,7 @@ export interface ShareManagerHook {
|
||||
shareToManageExpiration: Share | null;
|
||||
shareToViewDetails: Share | null;
|
||||
shareToGenerateLink: Share | null;
|
||||
shareToViewQrCode: Share | null;
|
||||
sharesToDelete: Share[] | null;
|
||||
setShareToDelete: (share: Share | null) => void;
|
||||
setShareToEdit: (share: Share | null) => void;
|
||||
@@ -33,6 +34,7 @@ export interface ShareManagerHook {
|
||||
setShareToManageExpiration: (share: Share | null) => void;
|
||||
setShareToViewDetails: (share: Share | null) => void;
|
||||
setShareToGenerateLink: (share: Share | null) => void;
|
||||
setShareToViewQrCode: (share: Share | null) => void;
|
||||
setSharesToDelete: (shares: Share[] | null) => void;
|
||||
handleDelete: (shareId: string) => Promise<void>;
|
||||
handleBulkDelete: (shares: Share[]) => void;
|
||||
@@ -62,6 +64,7 @@ export function useShareManager(onSuccess: () => void) {
|
||||
const [shareToManageExpiration, setShareToManageExpiration] = useState<Share | null>(null);
|
||||
const [shareToViewDetails, setShareToViewDetails] = useState<Share | null>(null);
|
||||
const [shareToGenerateLink, setShareToGenerateLink] = useState<Share | null>(null);
|
||||
const [shareToViewQrCode, setShareToViewQrCode] = useState<Share | null>(null);
|
||||
const [sharesToDelete, setSharesToDelete] = useState<Share[] | null>(null);
|
||||
const [clearSelectionCallback, setClearSelectionCallbackState] = useState<(() => void) | null>(null);
|
||||
|
||||
@@ -308,6 +311,7 @@ export function useShareManager(onSuccess: () => void) {
|
||||
shareToManageExpiration,
|
||||
shareToViewDetails,
|
||||
shareToGenerateLink,
|
||||
shareToViewQrCode,
|
||||
sharesToDelete,
|
||||
setShareToDelete,
|
||||
setShareToEdit,
|
||||
@@ -317,6 +321,7 @@ export function useShareManager(onSuccess: () => void) {
|
||||
setShareToManageExpiration,
|
||||
setShareToViewDetails,
|
||||
setShareToGenerateLink,
|
||||
setShareToViewQrCode,
|
||||
setSharesToDelete,
|
||||
handleDelete,
|
||||
handleBulkDelete,
|
||||
|
@@ -1,11 +1,7 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2017",
|
||||
"lib": [
|
||||
"dom",
|
||||
"dom.iterable",
|
||||
"esnext"
|
||||
],
|
||||
"lib": ["dom", "dom.iterable", "esnext"],
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
"strict": true,
|
||||
@@ -23,20 +19,9 @@
|
||||
}
|
||||
],
|
||||
"paths": {
|
||||
"@/*": [
|
||||
"./src/*"
|
||||
]
|
||||
"@/*": ["./src/*"]
|
||||
}
|
||||
},
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
".next/types/app/api/(proxy)/**/*",
|
||||
".next/types/**/*.ts"
|
||||
],
|
||||
"include": [
|
||||
"**/*.ts",
|
||||
"**/*.tsx",
|
||||
"next-env.d.ts",
|
||||
".next/types/**/*.ts"
|
||||
]
|
||||
}
|
||||
"exclude": ["node_modules", ".next/types/app/api/(proxy)/**/*", ".next/types/**/*.ts"],
|
||||
"include": ["**/*.ts", "**/*.tsx", "next-env.d.ts", ".next/types/**/*.ts"]
|
||||
}
|
||||
|
Reference in New Issue
Block a user