mirror of
https://github.com/kyantech/Palmr.git
synced 2025-10-23 06:11:58 +00:00
feat: enhance reverse share functionality with QR code support
- Added QR code viewing and downloading capabilities in the reverse shares section. - Updated UI components to include QR code options in share details and cards. - Introduced new state management for handling QR code visibility. - Enhanced translations for QR code interactions across multiple languages.
This commit is contained in:
@@ -355,6 +355,12 @@
|
||||
"stats": "{iconCount} أيقونة من {libraryCount} مكتبة",
|
||||
"categoryBadge": "{category} ({count} أيقونات)"
|
||||
},
|
||||
"imageEdit": {
|
||||
"title": "تعديل الصورة",
|
||||
"rotate": "تدوير",
|
||||
"zoom": "تكبير/تصغير",
|
||||
"cropInstructions": "اسحب لإعادة تحديد الموضع، غير حجم الزوايا لضبط منطقة القص"
|
||||
},
|
||||
"login": {
|
||||
"welcome": "مرحبا بك",
|
||||
"signInToContinue": "قم بتسجيل الدخول للمتابعة",
|
||||
@@ -1721,11 +1727,5 @@
|
||||
"passwordRequired": "كلمة المرور مطلوبة",
|
||||
"nameRequired": "الاسم مطلوب",
|
||||
"required": "هذا الحقل مطلوب"
|
||||
},
|
||||
"imageEdit": {
|
||||
"title": "تعديل الصورة",
|
||||
"rotate": "تدوير",
|
||||
"zoom": "تكبير/تصغير",
|
||||
"cropInstructions": "اسحب لإعادة تحديد الموضع، غير حجم الزوايا لضبط منطقة القص"
|
||||
}
|
||||
}
|
@@ -355,6 +355,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",
|
||||
@@ -1719,11 +1725,5 @@
|
||||
"passwordRequired": "Passwort ist erforderlich",
|
||||
"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"
|
||||
}
|
||||
}
|
@@ -360,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",
|
||||
@@ -405,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",
|
||||
@@ -453,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",
|
||||
@@ -618,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",
|
||||
@@ -640,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",
|
||||
@@ -1461,11 +1468,6 @@
|
||||
"dark": "Dark",
|
||||
"system": "System"
|
||||
},
|
||||
"qrCodeModal": {
|
||||
"title": "Share QR Code",
|
||||
"description": "Scan this QR code to access the shared files.",
|
||||
"download": "Download QR Code"
|
||||
},
|
||||
"twoFactor": {
|
||||
"title": "Two-Factor Authentication",
|
||||
"description": "Add an extra layer of security to your account",
|
||||
|
@@ -355,6 +355,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",
|
||||
@@ -1719,11 +1725,5 @@
|
||||
"passwordRequired": "Se requiere la contraseña",
|
||||
"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"
|
||||
}
|
||||
}
|
@@ -355,6 +355,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",
|
||||
@@ -1719,11 +1725,5 @@
|
||||
"passwordRequired": "Le mot de passe est requis",
|
||||
"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"
|
||||
}
|
||||
}
|
@@ -355,6 +355,12 @@
|
||||
"stats": "{libraryCount} लाइब्रेरी से {iconCount} आइकन",
|
||||
"categoryBadge": "{category} ({count} आइकन)"
|
||||
},
|
||||
"imageEdit": {
|
||||
"title": "छवि संपादित करें",
|
||||
"rotate": "घुमाएं",
|
||||
"zoom": "ज़ूम",
|
||||
"cropInstructions": "छवि को पुनः स्थानांतरित करने के लिए खींचें, कोणों को समायोजित करने के लिए आकार बदलें"
|
||||
},
|
||||
"login": {
|
||||
"welcome": "स्वागत है में",
|
||||
"signInToContinue": "जारी रखने के लिए साइन इन करें",
|
||||
@@ -1719,11 +1725,5 @@
|
||||
"passwordRequired": "पासवर्ड आवश्यक है",
|
||||
"nameRequired": "नाम आवश्यक है",
|
||||
"required": "यह फ़ील्ड आवश्यक है"
|
||||
},
|
||||
"imageEdit": {
|
||||
"title": "छवि संपादित करें",
|
||||
"rotate": "घुमाएं",
|
||||
"zoom": "ज़ूम",
|
||||
"cropInstructions": "छवि को पुनः स्थानांतरित करने के लिए खींचें, कोणों को समायोजित करने के लिए आकार बदलें"
|
||||
}
|
||||
}
|
@@ -355,6 +355,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",
|
||||
@@ -1719,11 +1725,5 @@
|
||||
"passwordMinLength": "La password deve contenere almeno 6 caratteri",
|
||||
"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"
|
||||
}
|
||||
}
|
@@ -355,6 +355,12 @@
|
||||
"stats": "{libraryCount}ライブラリから{iconCount}個のアイコン",
|
||||
"categoryBadge": "{category}({count}個のアイコン)"
|
||||
},
|
||||
"imageEdit": {
|
||||
"title": "画像を編集",
|
||||
"rotate": "回転",
|
||||
"zoom": "ズーム",
|
||||
"cropInstructions": "位置を変更するにはドラッグし、カット領域を調整するには角をリサイズしてください"
|
||||
},
|
||||
"login": {
|
||||
"welcome": "ようこそへ",
|
||||
"signInToContinue": "続行するにはサインインしてください",
|
||||
@@ -1719,11 +1725,5 @@
|
||||
"passwordRequired": "パスワードは必須です",
|
||||
"nameRequired": "名前は必須です",
|
||||
"required": "このフィールドは必須です"
|
||||
},
|
||||
"imageEdit": {
|
||||
"title": "画像を編集",
|
||||
"rotate": "回転",
|
||||
"zoom": "ズーム",
|
||||
"cropInstructions": "位置を変更するにはドラッグし、カット領域を調整するには角をリサイズしてください"
|
||||
}
|
||||
}
|
@@ -355,6 +355,12 @@
|
||||
"stats": "{libraryCount}개의 라이브러리에서 {iconCount}개의 아이콘",
|
||||
"categoryBadge": "{category} ({count}개의 아이콘)"
|
||||
},
|
||||
"imageEdit": {
|
||||
"title": "이미지 편집",
|
||||
"rotate": "회전",
|
||||
"zoom": "확대/축소",
|
||||
"cropInstructions": "위치를 변경하려면 드래그하고, 자르기 영역을 조정하려면 모서리를 확대/축소하세요"
|
||||
},
|
||||
"login": {
|
||||
"welcome": "에 오신 것을 환영합니다",
|
||||
"signInToContinue": "계속하려면 로그인하세요",
|
||||
@@ -1719,11 +1725,5 @@
|
||||
"passwordRequired": "비밀번호는 필수입니다",
|
||||
"nameRequired": "이름은 필수입니다",
|
||||
"required": "이 필드는 필수입니다"
|
||||
},
|
||||
"imageEdit": {
|
||||
"title": "이미지 편집",
|
||||
"rotate": "회전",
|
||||
"zoom": "확대/축소",
|
||||
"cropInstructions": "위치를 변경하려면 드래그하고, 자르기 영역을 조정하려면 모서리를 확대/축소하세요"
|
||||
}
|
||||
}
|
@@ -355,6 +355,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",
|
||||
@@ -1719,11 +1725,5 @@
|
||||
"passwordMinLength": "Wachtwoord moet minimaal 6 tekens bevatten",
|
||||
"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"
|
||||
}
|
||||
}
|
@@ -355,6 +355,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ć",
|
||||
@@ -1719,11 +1725,5 @@
|
||||
"passwordMinLength": "Hasło musi mieć co najmniej 6 znaków",
|
||||
"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"
|
||||
}
|
||||
}
|
@@ -355,6 +355,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",
|
||||
@@ -1719,11 +1725,5 @@
|
||||
"lastNameRequired": "O sobrenome é necessário",
|
||||
"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"
|
||||
}
|
||||
}
|
@@ -355,6 +355,12 @@
|
||||
"stats": "{iconCount} иконок из {libraryCount} библиотек",
|
||||
"categoryBadge": "{category} ({count} иконок)"
|
||||
},
|
||||
"imageEdit": {
|
||||
"title": "Редактировать изображение",
|
||||
"rotate": "Повернуть",
|
||||
"zoom": "Увеличить",
|
||||
"cropInstructions": "Перетащите, чтобы переместить, измените размер углов, чтобы отрегулировать область обрезки"
|
||||
},
|
||||
"login": {
|
||||
"welcome": "Добро пожаловать в",
|
||||
"signInToContinue": "Войдите, чтобы продолжить",
|
||||
@@ -1719,11 +1725,5 @@
|
||||
"passwordRequired": "Требуется пароль",
|
||||
"nameRequired": "Требуется имя",
|
||||
"required": "Это поле обязательно"
|
||||
},
|
||||
"imageEdit": {
|
||||
"title": "Редактировать изображение",
|
||||
"rotate": "Повернуть",
|
||||
"zoom": "Увеличить",
|
||||
"cropInstructions": "Перетащите, чтобы переместить, измените размер углов, чтобы отрегулировать область обрезки"
|
||||
}
|
||||
}
|
@@ -355,6 +355,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",
|
||||
@@ -1719,11 +1725,5 @@
|
||||
"passwordRequired": "Şifre gerekli",
|
||||
"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"
|
||||
}
|
||||
}
|
@@ -355,6 +355,12 @@
|
||||
"stats": "来自 {libraryCount} 个库的 {iconCount} 个图标",
|
||||
"categoryBadge": "{category}({count} 个图标)"
|
||||
},
|
||||
"imageEdit": {
|
||||
"title": "编辑图片",
|
||||
"rotate": "旋转",
|
||||
"zoom": "缩放",
|
||||
"cropInstructions": "拖动以重新定位,调整角落大小以调整裁剪区域"
|
||||
},
|
||||
"login": {
|
||||
"welcome": "欢迎您",
|
||||
"signInToContinue": "请登录以继续",
|
||||
@@ -1483,11 +1489,7 @@
|
||||
"copyToClipboard": "复制到剪贴板",
|
||||
"savedMessage": "我已保存备用码",
|
||||
"available": "可用备用码:{count}个",
|
||||
"instructions": [
|
||||
"• 将这些代码保存在安全的位置",
|
||||
"• 每个备用码只能使用一次",
|
||||
"• 您可以随时生成新的备用码"
|
||||
]
|
||||
"instructions": ["• 将这些代码保存在安全的位置", "• 每个备用码只能使用一次", "• 您可以随时生成新的备用码"]
|
||||
},
|
||||
"verification": {
|
||||
"title": "双重认证",
|
||||
@@ -1719,11 +1721,5 @@
|
||||
"passwordRequired": "密码为必填项",
|
||||
"nameRequired": "名称为必填项",
|
||||
"required": "此字段为必填项"
|
||||
},
|
||||
"imageEdit": {
|
||||
"title": "编辑图片",
|
||||
"rotate": "旋转",
|
||||
"zoom": "缩放",
|
||||
"cropInstructions": "拖动以重新定位,调整角落大小以调整裁剪区域"
|
||||
}
|
||||
}
|
@@ -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,6 +145,7 @@ export function ReverseShareDetailsModal({
|
||||
isActive={reverseShare.isActive}
|
||||
/>
|
||||
|
||||
<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">
|
||||
@@ -182,6 +188,78 @@ export function ReverseShareDetailsModal({
|
||||
/>
|
||||
</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 */}
|
||||
<div className="space-y-3">
|
||||
<div className="flex items-center gap-2 border-b pb-2">
|
||||
|
@@ -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}
|
||||
|
@@ -173,8 +173,8 @@ export function GenerateShareLinkModal({
|
||||
|
||||
<DialogFooter>
|
||||
<Button onClick={downloadQRCode} disabled={isDownloading}>
|
||||
<IconDownload className="h-4 w-4 mr-2" />
|
||||
{t("qrCodeModal.download", { defaultValue: "Download QR Code" })}
|
||||
<IconDownload className="h-4 w-4" />
|
||||
{t("qrCodeModal.download")}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</div>
|
||||
|
@@ -93,7 +93,7 @@ export function QrCodeModal({ isOpen, onClose, shareLink, shareName }: QrCodeMod
|
||||
{t("common.close")}
|
||||
</Button>
|
||||
<Button onClick={downloadQRCode} className="mt-2 sm:mt-0" disabled={isDownloading}>
|
||||
<IconDownload className="h-4 w-4 mr-2" />
|
||||
<IconDownload className="h-4 w-4" />
|
||||
{t("qrCodeModal.download", { defaultValue: "Download QR Code" })}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
|
@@ -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