feat(web): added image cropping to avatar uploads (#185)
* feat(web): added image cropping to avatar uploads * chore: add translations * refactor: move react crop css import to avatar component * refactor: remove duplicate interface --------- Co-authored-by: Henry <henry_ball@hotmail.co.uk>
This commit is contained in:
@@ -30,6 +30,7 @@ checksums:
|
||||
added%20checklist%20item%20%3C0%3E%7B0%7D%3C%2F0%3E/singular: bdd202da20b1fffbec21792c5453f90c
|
||||
added%20label%20%3C0%3E%7B0%7D%3C%2F0%3E/singular: b32be052b3d57de0c9120fa7f9fc86ee
|
||||
Adding%20a%20new%20member%20will%20cost%20an%20additional%20%7Bprice%7D%20(%7BbillingType%7D)%20per%20seat./singular: 12e88573028306110fbc15ef1e714892
|
||||
Adjust%20the%20square%20crop%20to%20fit%20your%20avatar./singular: a4df26bbce6f14c6962fac1324db00a8
|
||||
Admin%20roles/singular: 32a5d78073b9bb9a246773afba8831df
|
||||
All%20systems%20operational/singular: ee943a4046b09e6334cceeea9fda2bfc
|
||||
Already%20have%20an%20account%3F%20%3C0%3E%3C1%3ESign%20in%3C%2F1%3E%3C%2F0%3E/singular: 2959fd276248208b65cb27ed46b20135
|
||||
@@ -119,6 +120,7 @@ checksums:
|
||||
Create%20workspace/singular: 2e6718e79964ea5ce22d76c2189c77ca
|
||||
created%20the%20card/singular: 605475f5aaeb4dbccbf7c4eb9107b43d
|
||||
Critical/singular: eb327cd411b50aee954f8d1d215d003a
|
||||
Crop%20your%20avatar/singular: eb25e2d5972ec36a0b40481c8136ab15
|
||||
Current%20password%20is%20required/singular: 72536bca9598680027f2be8ce80ac280
|
||||
Custom%20domain/singular: b09e7a9c187b7163b4a6cfc78042fe42
|
||||
Custom%20URLs%20require%20upgrading%20to%20a%20Pro%20plan/singular: f7275e3b473b8f7b39dab6b37eb26fea
|
||||
|
||||
@@ -59,6 +59,7 @@
|
||||
"react-dom": "catalog:react18",
|
||||
"react-hook-form": "^7.51.1",
|
||||
"react-icons": "^5.5.0",
|
||||
"react-image-crop": "^11.0.10",
|
||||
"react-lottie-player": "^1.5.5",
|
||||
"react-markdown": "^10.1.0",
|
||||
"superjson": "2.2.1",
|
||||
|
||||
@@ -140,6 +140,10 @@ msgstr "hat Label <0>{0}</0> hinzugefügt"
|
||||
msgid "Adding a new member will cost an additional {price} ({billingType}) per seat."
|
||||
msgstr "Das Hinzufügen eines neuen Mitglieds kostet zusätzlich {price} ({billingType}) pro Platz."
|
||||
|
||||
#: src/views/settings/components/Avatar.tsx:278
|
||||
msgid "Adjust the square crop to fit your avatar."
|
||||
msgstr "Passe den quadratischen Zuschnitt an deinen Avatar an."
|
||||
|
||||
#: src/views/home/components/Pricing.tsx:55
|
||||
msgid "Admin roles"
|
||||
msgstr "Administratorrollen"
|
||||
@@ -338,6 +342,7 @@ msgstr "Fehlerbericht"
|
||||
#: src/views/card/components/DeleteChecklistConfirmation.tsx:63
|
||||
#: src/views/card/components/DeleteCommentConfirmation.tsx:76
|
||||
#: src/views/members/components/DeleteMemberConfirmation.tsx:55
|
||||
#: src/views/settings/components/Avatar.tsx:309
|
||||
#: src/views/settings/components/ChangePasswordConfirmation.tsx:168
|
||||
#: src/views/settings/components/DeleteAccountConfirmation.tsx:88
|
||||
#: src/views/settings/components/DeleteWorkspaceConfirmation.tsx:98
|
||||
@@ -527,6 +532,10 @@ msgstr "hat die Karte erstellt"
|
||||
msgid "Critical"
|
||||
msgstr "Kritisch"
|
||||
|
||||
#: src/views/settings/components/Avatar.tsx:275
|
||||
msgid "Crop your avatar"
|
||||
msgstr "Schneide deinen Avatar zu"
|
||||
|
||||
#: src/views/settings/components/ChangePasswordConfirmation.tsx:19
|
||||
msgid "Current password is required"
|
||||
msgstr "Aktuelles Passwort ist erforderlich"
|
||||
@@ -749,7 +758,7 @@ msgstr "Fehler beim Einladen des Mitglieds"
|
||||
msgid "Error updating display name"
|
||||
msgstr "Fehler beim Aktualisieren des Anzeigenamens"
|
||||
|
||||
#: src/views/settings/components/Avatar.tsx:39
|
||||
#: src/views/settings/components/Avatar.tsx:80
|
||||
msgid "Error updating profile image"
|
||||
msgstr "Fehler beim Aktualisieren des Profilbilds"
|
||||
|
||||
@@ -774,8 +783,8 @@ msgstr "Fehler beim Upgrade des Abonnements"
|
||||
msgid "Error upgrading to Pro"
|
||||
msgstr "Fehler beim Upgrade auf Pro"
|
||||
|
||||
#: src/views/settings/components/Avatar.tsx:56
|
||||
#: src/views/settings/components/Avatar.tsx:97
|
||||
#: src/views/settings/components/Avatar.tsx:94
|
||||
#: src/views/settings/components/Avatar.tsx:221
|
||||
msgid "Error uploading profile image"
|
||||
msgstr "Fehler beim Hochladen des Profilbilds"
|
||||
|
||||
@@ -1321,7 +1330,7 @@ msgstr "Bitte gib einen gültigen Namen ein"
|
||||
msgid "Please enter a valid password"
|
||||
msgstr "Bitte gib ein gültiges Passwort ein"
|
||||
|
||||
#: src/views/settings/components/Avatar.tsx:57
|
||||
#: src/views/settings/components/Avatar.tsx:95
|
||||
msgid "Please select a file to upload."
|
||||
msgstr "Bitte wähle eine Datei zum Hochladen aus."
|
||||
|
||||
@@ -1352,8 +1361,8 @@ msgstr "Bitte wähle eine Datei zum Hochladen aus."
|
||||
#: src/views/members/components/DeleteMemberConfirmation.tsx:28
|
||||
#: src/views/members/components/InviteMemberForm.tsx:78
|
||||
#: src/views/members/components/InviteMemberForm.tsx:131
|
||||
#: src/views/settings/components/Avatar.tsx:40
|
||||
#: src/views/settings/components/Avatar.tsx:98
|
||||
#: src/views/settings/components/Avatar.tsx:81
|
||||
#: src/views/settings/components/Avatar.tsx:222
|
||||
#: src/views/settings/components/DeleteAccountConfirmation.tsx:40
|
||||
#: src/views/settings/components/DeleteWorkspaceConfirmation.tsx:46
|
||||
#: src/views/settings/components/UpdateDisplayNameForm.tsx:55
|
||||
@@ -1391,7 +1400,7 @@ msgstr "Pro-Plan"
|
||||
msgid "Pro Plan ∞"
|
||||
msgstr "Pro-Plan ∞"
|
||||
|
||||
#: src/views/settings/components/Avatar.tsx:26
|
||||
#: src/views/settings/components/Avatar.tsx:67
|
||||
msgid "Profile image updated"
|
||||
msgstr "Profilbild aktualisiert"
|
||||
|
||||
@@ -1492,6 +1501,7 @@ msgid "Run on your own infrastructure"
|
||||
msgstr "Auf eigener Infrastruktur betreiben"
|
||||
|
||||
#: src/views/card/components/Comment.tsx:165
|
||||
#: src/views/settings/components/Avatar.tsx:312
|
||||
msgid "Save"
|
||||
msgstr "Speichern"
|
||||
|
||||
@@ -2118,7 +2128,7 @@ msgstr "Dein Anzeigename wurde aktualisiert."
|
||||
msgid "Your password has been changed."
|
||||
msgstr "Ihr Passwort wurde geändert."
|
||||
|
||||
#: src/views/settings/components/Avatar.tsx:27
|
||||
#: src/views/settings/components/Avatar.tsx:68
|
||||
msgid "Your profile image has been updated."
|
||||
msgstr "Dein Profilbild wurde aktualisiert."
|
||||
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -160,6 +160,10 @@ msgstr "added label <0>{0}</0>"
|
||||
msgid "Adding a new member will cost an additional {price} ({billingType}) per seat."
|
||||
msgstr "Adding a new member will cost an additional {price} ({billingType}) per seat."
|
||||
|
||||
#: src/views/settings/components/Avatar.tsx:278
|
||||
msgid "Adjust the square crop to fit your avatar."
|
||||
msgstr "Adjust the square crop to fit your avatar."
|
||||
|
||||
#: src/views/home/components/Pricing.tsx:55
|
||||
msgid "Admin roles"
|
||||
msgstr "Admin roles"
|
||||
@@ -387,6 +391,7 @@ msgstr "Bug Report"
|
||||
#: src/views/card/components/DeleteChecklistConfirmation.tsx:63
|
||||
#: src/views/card/components/DeleteCommentConfirmation.tsx:76
|
||||
#: src/views/members/components/DeleteMemberConfirmation.tsx:55
|
||||
#: src/views/settings/components/Avatar.tsx:309
|
||||
#: src/views/settings/components/ChangePasswordConfirmation.tsx:168
|
||||
#: src/views/settings/components/DeleteAccountConfirmation.tsx:88
|
||||
#: src/views/settings/components/DeleteWorkspaceConfirmation.tsx:98
|
||||
@@ -592,6 +597,10 @@ msgstr "created the card"
|
||||
msgid "Critical"
|
||||
msgstr "Critical"
|
||||
|
||||
#: src/views/settings/components/Avatar.tsx:275
|
||||
msgid "Crop your avatar"
|
||||
msgstr "Crop your avatar"
|
||||
|
||||
#: src/views/settings/components/ChangePasswordConfirmation.tsx:19
|
||||
msgid "Current password is required"
|
||||
msgstr "Current password is required"
|
||||
@@ -822,7 +831,7 @@ msgstr "Error inviting member"
|
||||
msgid "Error updating display name"
|
||||
msgstr "Error updating display name"
|
||||
|
||||
#: src/views/settings/components/Avatar.tsx:39
|
||||
#: src/views/settings/components/Avatar.tsx:80
|
||||
msgid "Error updating profile image"
|
||||
msgstr "Error updating profile image"
|
||||
|
||||
@@ -847,8 +856,8 @@ msgstr "Error upgrading subscription"
|
||||
msgid "Error upgrading to Pro"
|
||||
msgstr "Error upgrading to Pro"
|
||||
|
||||
#: src/views/settings/components/Avatar.tsx:56
|
||||
#: src/views/settings/components/Avatar.tsx:97
|
||||
#: src/views/settings/components/Avatar.tsx:94
|
||||
#: src/views/settings/components/Avatar.tsx:221
|
||||
msgid "Error uploading profile image"
|
||||
msgstr "Error uploading profile image"
|
||||
|
||||
@@ -1411,7 +1420,7 @@ msgstr "Please enter a valid name"
|
||||
msgid "Please enter a valid password"
|
||||
msgstr "Please enter a valid password"
|
||||
|
||||
#: src/views/settings/components/Avatar.tsx:57
|
||||
#: src/views/settings/components/Avatar.tsx:95
|
||||
msgid "Please select a file to upload."
|
||||
msgstr "Please select a file to upload."
|
||||
|
||||
@@ -1442,8 +1451,8 @@ msgstr "Please select a file to upload."
|
||||
#: src/views/members/components/DeleteMemberConfirmation.tsx:28
|
||||
#: src/views/members/components/InviteMemberForm.tsx:78
|
||||
#: src/views/members/components/InviteMemberForm.tsx:131
|
||||
#: src/views/settings/components/Avatar.tsx:40
|
||||
#: src/views/settings/components/Avatar.tsx:98
|
||||
#: src/views/settings/components/Avatar.tsx:81
|
||||
#: src/views/settings/components/Avatar.tsx:222
|
||||
#: src/views/settings/components/DeleteAccountConfirmation.tsx:40
|
||||
#: src/views/settings/components/DeleteWorkspaceConfirmation.tsx:46
|
||||
#: src/views/settings/components/UpdateDisplayNameForm.tsx:55
|
||||
@@ -1481,7 +1490,7 @@ msgstr "Pro Plan"
|
||||
msgid "Pro Plan ∞"
|
||||
msgstr "Pro Plan ∞"
|
||||
|
||||
#: src/views/settings/components/Avatar.tsx:26
|
||||
#: src/views/settings/components/Avatar.tsx:67
|
||||
msgid "Profile image updated"
|
||||
msgstr "Profile image updated"
|
||||
|
||||
@@ -1590,6 +1599,7 @@ msgid "Run on your own infrastructure"
|
||||
msgstr "Run on your own infrastructure"
|
||||
|
||||
#: src/views/card/components/Comment.tsx:165
|
||||
#: src/views/settings/components/Avatar.tsx:312
|
||||
msgid "Save"
|
||||
msgstr "Save"
|
||||
|
||||
@@ -2240,7 +2250,7 @@ msgstr "Your display name has been updated."
|
||||
msgid "Your password has been changed."
|
||||
msgstr "Your password has been changed."
|
||||
|
||||
#: src/views/settings/components/Avatar.tsx:27
|
||||
#: src/views/settings/components/Avatar.tsx:68
|
||||
msgid "Your profile image has been updated."
|
||||
msgstr "Your profile image has been updated."
|
||||
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -140,6 +140,10 @@ msgstr "añadió la etiqueta <0>{0}</0>"
|
||||
msgid "Adding a new member will cost an additional {price} ({billingType}) per seat."
|
||||
msgstr "Añadir un nuevo miembro costará {price} adicionales ({billingType}) por asiento."
|
||||
|
||||
#: src/views/settings/components/Avatar.tsx:278
|
||||
msgid "Adjust the square crop to fit your avatar."
|
||||
msgstr "Ajusta el recorte cuadrado para que se adapte a tu avatar."
|
||||
|
||||
#: src/views/home/components/Pricing.tsx:55
|
||||
msgid "Admin roles"
|
||||
msgstr "Roles de administrador"
|
||||
@@ -338,6 +342,7 @@ msgstr "Informe de error"
|
||||
#: src/views/card/components/DeleteChecklistConfirmation.tsx:63
|
||||
#: src/views/card/components/DeleteCommentConfirmation.tsx:76
|
||||
#: src/views/members/components/DeleteMemberConfirmation.tsx:55
|
||||
#: src/views/settings/components/Avatar.tsx:309
|
||||
#: src/views/settings/components/ChangePasswordConfirmation.tsx:168
|
||||
#: src/views/settings/components/DeleteAccountConfirmation.tsx:88
|
||||
#: src/views/settings/components/DeleteWorkspaceConfirmation.tsx:98
|
||||
@@ -527,6 +532,10 @@ msgstr "creó la tarjeta"
|
||||
msgid "Critical"
|
||||
msgstr "Crítico"
|
||||
|
||||
#: src/views/settings/components/Avatar.tsx:275
|
||||
msgid "Crop your avatar"
|
||||
msgstr "Recorta tu avatar"
|
||||
|
||||
#: src/views/settings/components/ChangePasswordConfirmation.tsx:19
|
||||
msgid "Current password is required"
|
||||
msgstr "Se requiere la contraseña actual"
|
||||
@@ -749,7 +758,7 @@ msgstr "Error al invitar al miembro"
|
||||
msgid "Error updating display name"
|
||||
msgstr "Error al actualizar el nombre visible"
|
||||
|
||||
#: src/views/settings/components/Avatar.tsx:39
|
||||
#: src/views/settings/components/Avatar.tsx:80
|
||||
msgid "Error updating profile image"
|
||||
msgstr "Error al actualizar la imagen de perfil"
|
||||
|
||||
@@ -774,8 +783,8 @@ msgstr "Error al actualizar la suscripción"
|
||||
msgid "Error upgrading to Pro"
|
||||
msgstr "Error al actualizar a Pro"
|
||||
|
||||
#: src/views/settings/components/Avatar.tsx:56
|
||||
#: src/views/settings/components/Avatar.tsx:97
|
||||
#: src/views/settings/components/Avatar.tsx:94
|
||||
#: src/views/settings/components/Avatar.tsx:221
|
||||
msgid "Error uploading profile image"
|
||||
msgstr "Error al subir la imagen de perfil"
|
||||
|
||||
@@ -1321,7 +1330,7 @@ msgstr "Por favor, introduce un nombre válido"
|
||||
msgid "Please enter a valid password"
|
||||
msgstr "Por favor, introduce una contraseña válida"
|
||||
|
||||
#: src/views/settings/components/Avatar.tsx:57
|
||||
#: src/views/settings/components/Avatar.tsx:95
|
||||
msgid "Please select a file to upload."
|
||||
msgstr "Por favor selecciona un archivo para subir."
|
||||
|
||||
@@ -1352,8 +1361,8 @@ msgstr "Por favor selecciona un archivo para subir."
|
||||
#: src/views/members/components/DeleteMemberConfirmation.tsx:28
|
||||
#: src/views/members/components/InviteMemberForm.tsx:78
|
||||
#: src/views/members/components/InviteMemberForm.tsx:131
|
||||
#: src/views/settings/components/Avatar.tsx:40
|
||||
#: src/views/settings/components/Avatar.tsx:98
|
||||
#: src/views/settings/components/Avatar.tsx:81
|
||||
#: src/views/settings/components/Avatar.tsx:222
|
||||
#: src/views/settings/components/DeleteAccountConfirmation.tsx:40
|
||||
#: src/views/settings/components/DeleteWorkspaceConfirmation.tsx:46
|
||||
#: src/views/settings/components/UpdateDisplayNameForm.tsx:55
|
||||
@@ -1391,7 +1400,7 @@ msgstr "Plan Pro"
|
||||
msgid "Pro Plan ∞"
|
||||
msgstr "Plan Pro ∞"
|
||||
|
||||
#: src/views/settings/components/Avatar.tsx:26
|
||||
#: src/views/settings/components/Avatar.tsx:67
|
||||
msgid "Profile image updated"
|
||||
msgstr "Imagen de perfil actualizada"
|
||||
|
||||
@@ -1492,6 +1501,7 @@ msgid "Run on your own infrastructure"
|
||||
msgstr "Ejecuta en tu propia infraestructura"
|
||||
|
||||
#: src/views/card/components/Comment.tsx:165
|
||||
#: src/views/settings/components/Avatar.tsx:312
|
||||
msgid "Save"
|
||||
msgstr "Guardar"
|
||||
|
||||
@@ -2118,7 +2128,7 @@ msgstr "Tu nombre de visualización ha sido actualizado."
|
||||
msgid "Your password has been changed."
|
||||
msgstr "Tu contraseña ha sido cambiada."
|
||||
|
||||
#: src/views/settings/components/Avatar.tsx:27
|
||||
#: src/views/settings/components/Avatar.tsx:68
|
||||
msgid "Your profile image has been updated."
|
||||
msgstr "Tu imagen de perfil ha sido actualizada."
|
||||
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -140,6 +140,10 @@ msgstr "a ajouté l'étiquette <0>{0}</0>"
|
||||
msgid "Adding a new member will cost an additional {price} ({billingType}) per seat."
|
||||
msgstr "L'ajout d'un nouveau membre coûtera {price} supplémentaires ({billingType}) par siège."
|
||||
|
||||
#: src/views/settings/components/Avatar.tsx:278
|
||||
msgid "Adjust the square crop to fit your avatar."
|
||||
msgstr "Ajustez le recadrage carré pour adapter votre avatar."
|
||||
|
||||
#: src/views/home/components/Pricing.tsx:55
|
||||
msgid "Admin roles"
|
||||
msgstr "Rôles d'administrateur"
|
||||
@@ -338,6 +342,7 @@ msgstr "Rapport de bug"
|
||||
#: src/views/card/components/DeleteChecklistConfirmation.tsx:63
|
||||
#: src/views/card/components/DeleteCommentConfirmation.tsx:76
|
||||
#: src/views/members/components/DeleteMemberConfirmation.tsx:55
|
||||
#: src/views/settings/components/Avatar.tsx:309
|
||||
#: src/views/settings/components/ChangePasswordConfirmation.tsx:168
|
||||
#: src/views/settings/components/DeleteAccountConfirmation.tsx:88
|
||||
#: src/views/settings/components/DeleteWorkspaceConfirmation.tsx:98
|
||||
@@ -527,6 +532,10 @@ msgstr "a créé la carte"
|
||||
msgid "Critical"
|
||||
msgstr "Critique"
|
||||
|
||||
#: src/views/settings/components/Avatar.tsx:275
|
||||
msgid "Crop your avatar"
|
||||
msgstr "Recadrez votre avatar"
|
||||
|
||||
#: src/views/settings/components/ChangePasswordConfirmation.tsx:19
|
||||
msgid "Current password is required"
|
||||
msgstr "Le mot de passe actuel est requis"
|
||||
@@ -749,7 +758,7 @@ msgstr "Erreur lors de l'invitation du membre"
|
||||
msgid "Error updating display name"
|
||||
msgstr "Erreur lors de la mise à jour du nom d'affichage"
|
||||
|
||||
#: src/views/settings/components/Avatar.tsx:39
|
||||
#: src/views/settings/components/Avatar.tsx:80
|
||||
msgid "Error updating profile image"
|
||||
msgstr "Erreur lors de la mise à jour de l'image de profil"
|
||||
|
||||
@@ -774,8 +783,8 @@ msgstr "Erreur lors de la mise à niveau de l'abonnement"
|
||||
msgid "Error upgrading to Pro"
|
||||
msgstr "Erreur lors de la mise à niveau vers Pro"
|
||||
|
||||
#: src/views/settings/components/Avatar.tsx:56
|
||||
#: src/views/settings/components/Avatar.tsx:97
|
||||
#: src/views/settings/components/Avatar.tsx:94
|
||||
#: src/views/settings/components/Avatar.tsx:221
|
||||
msgid "Error uploading profile image"
|
||||
msgstr "Erreur lors du téléchargement de l'image de profil"
|
||||
|
||||
@@ -1321,7 +1330,7 @@ msgstr "Veuillez saisir un nom valide"
|
||||
msgid "Please enter a valid password"
|
||||
msgstr "Veuillez saisir un mot de passe valide"
|
||||
|
||||
#: src/views/settings/components/Avatar.tsx:57
|
||||
#: src/views/settings/components/Avatar.tsx:95
|
||||
msgid "Please select a file to upload."
|
||||
msgstr "Veuillez sélectionner un fichier à télécharger."
|
||||
|
||||
@@ -1352,8 +1361,8 @@ msgstr "Veuillez sélectionner un fichier à télécharger."
|
||||
#: src/views/members/components/DeleteMemberConfirmation.tsx:28
|
||||
#: src/views/members/components/InviteMemberForm.tsx:78
|
||||
#: src/views/members/components/InviteMemberForm.tsx:131
|
||||
#: src/views/settings/components/Avatar.tsx:40
|
||||
#: src/views/settings/components/Avatar.tsx:98
|
||||
#: src/views/settings/components/Avatar.tsx:81
|
||||
#: src/views/settings/components/Avatar.tsx:222
|
||||
#: src/views/settings/components/DeleteAccountConfirmation.tsx:40
|
||||
#: src/views/settings/components/DeleteWorkspaceConfirmation.tsx:46
|
||||
#: src/views/settings/components/UpdateDisplayNameForm.tsx:55
|
||||
@@ -1391,7 +1400,7 @@ msgstr "Plan Pro"
|
||||
msgid "Pro Plan ∞"
|
||||
msgstr "Plan Pro ∞"
|
||||
|
||||
#: src/views/settings/components/Avatar.tsx:26
|
||||
#: src/views/settings/components/Avatar.tsx:67
|
||||
msgid "Profile image updated"
|
||||
msgstr "Image de profil mise à jour"
|
||||
|
||||
@@ -1492,6 +1501,7 @@ msgid "Run on your own infrastructure"
|
||||
msgstr "Exécutez sur votre propre infrastructure"
|
||||
|
||||
#: src/views/card/components/Comment.tsx:165
|
||||
#: src/views/settings/components/Avatar.tsx:312
|
||||
msgid "Save"
|
||||
msgstr "Enregistrer"
|
||||
|
||||
@@ -2118,7 +2128,7 @@ msgstr "Votre nom d'affichage a été mis à jour."
|
||||
msgid "Your password has been changed."
|
||||
msgstr "Votre mot de passe a été modifié."
|
||||
|
||||
#: src/views/settings/components/Avatar.tsx:27
|
||||
#: src/views/settings/components/Avatar.tsx:68
|
||||
msgid "Your profile image has been updated."
|
||||
msgstr "Votre image de profil a été mise à jour."
|
||||
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -140,6 +140,10 @@ msgstr "ha aggiunto l'etichetta <0>{0}</0>"
|
||||
msgid "Adding a new member will cost an additional {price} ({billingType}) per seat."
|
||||
msgstr "L'aggiunta di un nuovo membro costerà un supplemento di {price} ({billingType}) per posto."
|
||||
|
||||
#: src/views/settings/components/Avatar.tsx:278
|
||||
msgid "Adjust the square crop to fit your avatar."
|
||||
msgstr "Regola il ritaglio quadrato per adattarlo al tuo avatar."
|
||||
|
||||
#: src/views/home/components/Pricing.tsx:55
|
||||
msgid "Admin roles"
|
||||
msgstr "Ruoli amministratore"
|
||||
@@ -338,6 +342,7 @@ msgstr "Segnalazione bug"
|
||||
#: src/views/card/components/DeleteChecklistConfirmation.tsx:63
|
||||
#: src/views/card/components/DeleteCommentConfirmation.tsx:76
|
||||
#: src/views/members/components/DeleteMemberConfirmation.tsx:55
|
||||
#: src/views/settings/components/Avatar.tsx:309
|
||||
#: src/views/settings/components/ChangePasswordConfirmation.tsx:168
|
||||
#: src/views/settings/components/DeleteAccountConfirmation.tsx:88
|
||||
#: src/views/settings/components/DeleteWorkspaceConfirmation.tsx:98
|
||||
@@ -527,6 +532,10 @@ msgstr "ha creato la carta"
|
||||
msgid "Critical"
|
||||
msgstr "Critico"
|
||||
|
||||
#: src/views/settings/components/Avatar.tsx:275
|
||||
msgid "Crop your avatar"
|
||||
msgstr "Ritaglia il tuo avatar"
|
||||
|
||||
#: src/views/settings/components/ChangePasswordConfirmation.tsx:19
|
||||
msgid "Current password is required"
|
||||
msgstr "La password attuale è obbligatoria"
|
||||
@@ -749,7 +758,7 @@ msgstr "Errore durante l'invito del membro"
|
||||
msgid "Error updating display name"
|
||||
msgstr "Errore durante l'aggiornamento del nome visualizzato"
|
||||
|
||||
#: src/views/settings/components/Avatar.tsx:39
|
||||
#: src/views/settings/components/Avatar.tsx:80
|
||||
msgid "Error updating profile image"
|
||||
msgstr "Errore durante l'aggiornamento dell'immagine del profilo"
|
||||
|
||||
@@ -774,8 +783,8 @@ msgstr "Errore nell'aggiornamento dell'abbonamento"
|
||||
msgid "Error upgrading to Pro"
|
||||
msgstr "Errore durante l'aggiornamento a Pro"
|
||||
|
||||
#: src/views/settings/components/Avatar.tsx:56
|
||||
#: src/views/settings/components/Avatar.tsx:97
|
||||
#: src/views/settings/components/Avatar.tsx:94
|
||||
#: src/views/settings/components/Avatar.tsx:221
|
||||
msgid "Error uploading profile image"
|
||||
msgstr "Errore durante il caricamento dell'immagine del profilo"
|
||||
|
||||
@@ -1321,7 +1330,7 @@ msgstr "Inserisci un nome valido"
|
||||
msgid "Please enter a valid password"
|
||||
msgstr "Inserisci una password valida"
|
||||
|
||||
#: src/views/settings/components/Avatar.tsx:57
|
||||
#: src/views/settings/components/Avatar.tsx:95
|
||||
msgid "Please select a file to upload."
|
||||
msgstr "Seleziona un file da caricare."
|
||||
|
||||
@@ -1352,8 +1361,8 @@ msgstr "Seleziona un file da caricare."
|
||||
#: src/views/members/components/DeleteMemberConfirmation.tsx:28
|
||||
#: src/views/members/components/InviteMemberForm.tsx:78
|
||||
#: src/views/members/components/InviteMemberForm.tsx:131
|
||||
#: src/views/settings/components/Avatar.tsx:40
|
||||
#: src/views/settings/components/Avatar.tsx:98
|
||||
#: src/views/settings/components/Avatar.tsx:81
|
||||
#: src/views/settings/components/Avatar.tsx:222
|
||||
#: src/views/settings/components/DeleteAccountConfirmation.tsx:40
|
||||
#: src/views/settings/components/DeleteWorkspaceConfirmation.tsx:46
|
||||
#: src/views/settings/components/UpdateDisplayNameForm.tsx:55
|
||||
@@ -1391,7 +1400,7 @@ msgstr "Piano Pro"
|
||||
msgid "Pro Plan ∞"
|
||||
msgstr "Piano Pro ∞"
|
||||
|
||||
#: src/views/settings/components/Avatar.tsx:26
|
||||
#: src/views/settings/components/Avatar.tsx:67
|
||||
msgid "Profile image updated"
|
||||
msgstr "Immagine del profilo aggiornata"
|
||||
|
||||
@@ -1492,6 +1501,7 @@ msgid "Run on your own infrastructure"
|
||||
msgstr "Esegui sulla tua infrastruttura"
|
||||
|
||||
#: src/views/card/components/Comment.tsx:165
|
||||
#: src/views/settings/components/Avatar.tsx:312
|
||||
msgid "Save"
|
||||
msgstr "Salva"
|
||||
|
||||
@@ -2118,7 +2128,7 @@ msgstr "Il tuo nome visualizzato è stato aggiornato."
|
||||
msgid "Your password has been changed."
|
||||
msgstr "La tua password è stata modificata."
|
||||
|
||||
#: src/views/settings/components/Avatar.tsx:27
|
||||
#: src/views/settings/components/Avatar.tsx:68
|
||||
msgid "Your profile image has been updated."
|
||||
msgstr "La tua immagine del profilo è stata aggiornata."
|
||||
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -140,6 +140,10 @@ msgstr "heeft label <0>{0}</0> toegevoegd"
|
||||
msgid "Adding a new member will cost an additional {price} ({billingType}) per seat."
|
||||
msgstr "Het toevoegen van een nieuw lid kost een extra {price} ({billingType}) per plaats."
|
||||
|
||||
#: src/views/settings/components/Avatar.tsx:278
|
||||
msgid "Adjust the square crop to fit your avatar."
|
||||
msgstr "Pas de vierkante uitsnede aan zodat je avatar goed past."
|
||||
|
||||
#: src/views/home/components/Pricing.tsx:55
|
||||
msgid "Admin roles"
|
||||
msgstr "Beheerdersrollen"
|
||||
@@ -338,6 +342,7 @@ msgstr "Bugrapport"
|
||||
#: src/views/card/components/DeleteChecklistConfirmation.tsx:63
|
||||
#: src/views/card/components/DeleteCommentConfirmation.tsx:76
|
||||
#: src/views/members/components/DeleteMemberConfirmation.tsx:55
|
||||
#: src/views/settings/components/Avatar.tsx:309
|
||||
#: src/views/settings/components/ChangePasswordConfirmation.tsx:168
|
||||
#: src/views/settings/components/DeleteAccountConfirmation.tsx:88
|
||||
#: src/views/settings/components/DeleteWorkspaceConfirmation.tsx:98
|
||||
@@ -527,6 +532,10 @@ msgstr "heeft de kaart aangemaakt"
|
||||
msgid "Critical"
|
||||
msgstr "Kritiek"
|
||||
|
||||
#: src/views/settings/components/Avatar.tsx:275
|
||||
msgid "Crop your avatar"
|
||||
msgstr "Snijd je avatar bij"
|
||||
|
||||
#: src/views/settings/components/ChangePasswordConfirmation.tsx:19
|
||||
msgid "Current password is required"
|
||||
msgstr "Huidig wachtwoord is vereist"
|
||||
@@ -749,7 +758,7 @@ msgstr "Fout bij het uitnodigen van lid"
|
||||
msgid "Error updating display name"
|
||||
msgstr "Fout bij bijwerken weergavenaam"
|
||||
|
||||
#: src/views/settings/components/Avatar.tsx:39
|
||||
#: src/views/settings/components/Avatar.tsx:80
|
||||
msgid "Error updating profile image"
|
||||
msgstr "Fout bij het bijwerken van profielafbeelding"
|
||||
|
||||
@@ -774,8 +783,8 @@ msgstr "Fout bij het upgraden van abonnement"
|
||||
msgid "Error upgrading to Pro"
|
||||
msgstr "Fout bij upgraden naar Pro"
|
||||
|
||||
#: src/views/settings/components/Avatar.tsx:56
|
||||
#: src/views/settings/components/Avatar.tsx:97
|
||||
#: src/views/settings/components/Avatar.tsx:94
|
||||
#: src/views/settings/components/Avatar.tsx:221
|
||||
msgid "Error uploading profile image"
|
||||
msgstr "Fout bij het uploaden van profielafbeelding"
|
||||
|
||||
@@ -1321,7 +1330,7 @@ msgstr "Voer een geldige naam in"
|
||||
msgid "Please enter a valid password"
|
||||
msgstr "Voer een geldig wachtwoord in"
|
||||
|
||||
#: src/views/settings/components/Avatar.tsx:57
|
||||
#: src/views/settings/components/Avatar.tsx:95
|
||||
msgid "Please select a file to upload."
|
||||
msgstr "Selecteer een bestand om te uploaden."
|
||||
|
||||
@@ -1352,8 +1361,8 @@ msgstr "Selecteer een bestand om te uploaden."
|
||||
#: src/views/members/components/DeleteMemberConfirmation.tsx:28
|
||||
#: src/views/members/components/InviteMemberForm.tsx:78
|
||||
#: src/views/members/components/InviteMemberForm.tsx:131
|
||||
#: src/views/settings/components/Avatar.tsx:40
|
||||
#: src/views/settings/components/Avatar.tsx:98
|
||||
#: src/views/settings/components/Avatar.tsx:81
|
||||
#: src/views/settings/components/Avatar.tsx:222
|
||||
#: src/views/settings/components/DeleteAccountConfirmation.tsx:40
|
||||
#: src/views/settings/components/DeleteWorkspaceConfirmation.tsx:46
|
||||
#: src/views/settings/components/UpdateDisplayNameForm.tsx:55
|
||||
@@ -1391,7 +1400,7 @@ msgstr "Pro Plan"
|
||||
msgid "Pro Plan ∞"
|
||||
msgstr "Pro Plan ∞"
|
||||
|
||||
#: src/views/settings/components/Avatar.tsx:26
|
||||
#: src/views/settings/components/Avatar.tsx:67
|
||||
msgid "Profile image updated"
|
||||
msgstr "Profielafbeelding bijgewerkt"
|
||||
|
||||
@@ -1492,6 +1501,7 @@ msgid "Run on your own infrastructure"
|
||||
msgstr "Draai op je eigen infrastructuur"
|
||||
|
||||
#: src/views/card/components/Comment.tsx:165
|
||||
#: src/views/settings/components/Avatar.tsx:312
|
||||
msgid "Save"
|
||||
msgstr "Opslaan"
|
||||
|
||||
@@ -2118,7 +2128,7 @@ msgstr "Je weergavenaam is bijgewerkt."
|
||||
msgid "Your password has been changed."
|
||||
msgstr "Je wachtwoord is gewijzigd."
|
||||
|
||||
#: src/views/settings/components/Avatar.tsx:27
|
||||
#: src/views/settings/components/Avatar.tsx:68
|
||||
msgid "Your profile image has been updated."
|
||||
msgstr "Je profielafbeelding is bijgewerkt."
|
||||
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -140,6 +140,10 @@ msgstr "добавлена метка <0>{0}</0>"
|
||||
msgid "Adding a new member will cost an additional {price} ({billingType}) per seat."
|
||||
msgstr "Добавление нового участника будет стоить дополнительно {price} ({billingType}) за место."
|
||||
|
||||
#: src/views/settings/components/Avatar.tsx:278
|
||||
msgid "Adjust the square crop to fit your avatar."
|
||||
msgstr "Настройте квадратное кадрирование, чтобы оно подходило для вашего аватара."
|
||||
|
||||
#: src/views/home/components/Pricing.tsx:55
|
||||
msgid "Admin roles"
|
||||
msgstr "Роли администратора"
|
||||
@@ -338,6 +342,7 @@ msgstr "Отчет об ошибке"
|
||||
#: src/views/card/components/DeleteChecklistConfirmation.tsx:63
|
||||
#: src/views/card/components/DeleteCommentConfirmation.tsx:76
|
||||
#: src/views/members/components/DeleteMemberConfirmation.tsx:55
|
||||
#: src/views/settings/components/Avatar.tsx:309
|
||||
#: src/views/settings/components/ChangePasswordConfirmation.tsx:168
|
||||
#: src/views/settings/components/DeleteAccountConfirmation.tsx:88
|
||||
#: src/views/settings/components/DeleteWorkspaceConfirmation.tsx:98
|
||||
@@ -527,6 +532,10 @@ msgstr "создал(а) карточку"
|
||||
msgid "Critical"
|
||||
msgstr "Критический"
|
||||
|
||||
#: src/views/settings/components/Avatar.tsx:275
|
||||
msgid "Crop your avatar"
|
||||
msgstr "Обрежьте ваш аватар"
|
||||
|
||||
#: src/views/settings/components/ChangePasswordConfirmation.tsx:19
|
||||
msgid "Current password is required"
|
||||
msgstr "Требуется текущий пароль"
|
||||
@@ -749,7 +758,7 @@ msgstr "Ошибка при приглашении участника"
|
||||
msgid "Error updating display name"
|
||||
msgstr "Ошибка при обновлении отображаемого имени"
|
||||
|
||||
#: src/views/settings/components/Avatar.tsx:39
|
||||
#: src/views/settings/components/Avatar.tsx:80
|
||||
msgid "Error updating profile image"
|
||||
msgstr "Ошибка при обновлении изображения профиля"
|
||||
|
||||
@@ -774,8 +783,8 @@ msgstr "Ошибка при обновлении подписки"
|
||||
msgid "Error upgrading to Pro"
|
||||
msgstr "Ошибка при обновлении до Pro"
|
||||
|
||||
#: src/views/settings/components/Avatar.tsx:56
|
||||
#: src/views/settings/components/Avatar.tsx:97
|
||||
#: src/views/settings/components/Avatar.tsx:94
|
||||
#: src/views/settings/components/Avatar.tsx:221
|
||||
msgid "Error uploading profile image"
|
||||
msgstr "Ошибка загрузки изображения профиля"
|
||||
|
||||
@@ -1280,6 +1289,10 @@ msgstr "Пароль должен содержать не менее 8 симв
|
||||
msgid "Passwords do not match"
|
||||
msgstr "Пароли не совпадают"
|
||||
|
||||
#: src/views/members/index.tsx:134
|
||||
msgid "Paused"
|
||||
msgstr "Приостановлено"
|
||||
|
||||
#: src/views/home/components/Pricing.tsx:102
|
||||
msgid "Payment frequency"
|
||||
msgstr "Частота платежей"
|
||||
@@ -1317,7 +1330,7 @@ msgstr "Пожалуйста, введите действительное имя
|
||||
msgid "Please enter a valid password"
|
||||
msgstr "Пожалуйста, введите действительный пароль"
|
||||
|
||||
#: src/views/settings/components/Avatar.tsx:57
|
||||
#: src/views/settings/components/Avatar.tsx:95
|
||||
msgid "Please select a file to upload."
|
||||
msgstr "Пожалуйста, выберите файл для загрузки."
|
||||
|
||||
@@ -1348,8 +1361,8 @@ msgstr "Пожалуйста, выберите файл для загрузки.
|
||||
#: src/views/members/components/DeleteMemberConfirmation.tsx:28
|
||||
#: src/views/members/components/InviteMemberForm.tsx:78
|
||||
#: src/views/members/components/InviteMemberForm.tsx:131
|
||||
#: src/views/settings/components/Avatar.tsx:40
|
||||
#: src/views/settings/components/Avatar.tsx:98
|
||||
#: src/views/settings/components/Avatar.tsx:81
|
||||
#: src/views/settings/components/Avatar.tsx:222
|
||||
#: src/views/settings/components/DeleteAccountConfirmation.tsx:40
|
||||
#: src/views/settings/components/DeleteWorkspaceConfirmation.tsx:46
|
||||
#: src/views/settings/components/UpdateDisplayNameForm.tsx:55
|
||||
@@ -1387,7 +1400,7 @@ msgstr "Профессиональный план"
|
||||
msgid "Pro Plan ∞"
|
||||
msgstr "Профессиональный план ∞"
|
||||
|
||||
#: src/views/settings/components/Avatar.tsx:26
|
||||
#: src/views/settings/components/Avatar.tsx:67
|
||||
msgid "Profile image updated"
|
||||
msgstr "Изображение профиля обновлено"
|
||||
|
||||
@@ -1488,6 +1501,7 @@ msgid "Run on your own infrastructure"
|
||||
msgstr "Запустите на своей инфраструктуре"
|
||||
|
||||
#: src/views/card/components/Comment.tsx:165
|
||||
#: src/views/settings/components/Avatar.tsx:312
|
||||
msgid "Save"
|
||||
msgstr "Сохранить"
|
||||
|
||||
@@ -2114,7 +2128,7 @@ msgstr "Ваше отображаемое имя было обновлено."
|
||||
msgid "Your password has been changed."
|
||||
msgstr "Ваш пароль был изменён."
|
||||
|
||||
#: src/views/settings/components/Avatar.tsx:27
|
||||
#: src/views/settings/components/Avatar.tsx:68
|
||||
msgid "Your profile image has been updated."
|
||||
msgstr "Ваше изображение профиля было обновлено."
|
||||
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -7,6 +7,7 @@ import type { ReactElement, ReactNode } from "react";
|
||||
import { Plus_Jakarta_Sans } from "next/font/google";
|
||||
import Script from "next/script";
|
||||
import { env } from "next-runtime-env";
|
||||
import { ThemeProvider } from "next-themes";
|
||||
import posthog from "posthog-js";
|
||||
import { PostHogProvider } from "posthog-js/react";
|
||||
import { useEffect } from "react";
|
||||
@@ -14,7 +15,6 @@ import { useEffect } from "react";
|
||||
import { LinguiProviderWrapper } from "~/providers/lingui";
|
||||
import { ModalProvider } from "~/providers/modal";
|
||||
import { PopupProvider } from "~/providers/popup";
|
||||
import { ThemeProvider } from "next-themes";
|
||||
import { api } from "~/utils/api";
|
||||
|
||||
const jakarta = Plus_Jakarta_Sans({
|
||||
@@ -82,8 +82,8 @@ const MyApp: AppType = ({ Component, pageProps }: AppPropsWithLayout) => {
|
||||
<main className="font-sans">
|
||||
<LinguiProviderWrapper>
|
||||
<ThemeProvider attribute="class" defaultTheme="system" enableSystem>
|
||||
<ModalProvider>
|
||||
<PopupProvider>
|
||||
<ModalProvider>
|
||||
<PopupProvider>
|
||||
{posthogKey ? (
|
||||
<PostHogProvider client={posthog}>
|
||||
{getLayout(<Component {...pageProps} />)}
|
||||
@@ -91,8 +91,8 @@ const MyApp: AppType = ({ Component, pageProps }: AppPropsWithLayout) => {
|
||||
) : (
|
||||
getLayout(<Component {...pageProps} />)
|
||||
)}
|
||||
</PopupProvider>
|
||||
</ModalProvider>
|
||||
</PopupProvider>
|
||||
</ModalProvider>
|
||||
</ThemeProvider>
|
||||
</LinguiProviderWrapper>
|
||||
</main>
|
||||
|
||||
@@ -1,14 +1,45 @@
|
||||
import Image from "next/image";
|
||||
import { t } from "@lingui/core/macro";
|
||||
import { env } from "next-runtime-env";
|
||||
import { useState } from "react";
|
||||
import { useCallback, useRef, useState } from "react";
|
||||
import ReactCrop from "react-image-crop";
|
||||
|
||||
import "react-image-crop/dist/ReactCrop.css";
|
||||
|
||||
import { generateUID } from "@kan/shared/utils";
|
||||
|
||||
import Button from "~/components/Button";
|
||||
import Modal from "~/components/modal";
|
||||
import { usePopup } from "~/providers/popup";
|
||||
import { api } from "~/utils/api";
|
||||
import { getAvatarUrl } from "~/utils/helpers";
|
||||
|
||||
interface PercentCrop {
|
||||
unit: "%";
|
||||
x: number;
|
||||
y: number;
|
||||
width: number;
|
||||
height: number;
|
||||
}
|
||||
|
||||
interface LocalPixelCrop {
|
||||
x: number;
|
||||
y: number;
|
||||
width: number;
|
||||
height: number;
|
||||
}
|
||||
|
||||
interface ReactCropProps {
|
||||
crop: PercentCrop | undefined;
|
||||
onChange: (crop: LocalPixelCrop, percentCrop: PercentCrop) => void;
|
||||
aspect?: number;
|
||||
className?: string;
|
||||
circularCrop?: boolean;
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
const AnyReactCrop = ReactCrop as unknown as React.FC<ReactCropProps>;
|
||||
|
||||
export default function Avatar({
|
||||
userId,
|
||||
userImage,
|
||||
@@ -19,6 +50,13 @@ export default function Avatar({
|
||||
const utils = api.useUtils();
|
||||
const { showPopup } = usePopup();
|
||||
const [uploading, setUploading] = useState(false);
|
||||
const [cropDialogOpen, setCropDialogOpen] = useState(false);
|
||||
const [selectedFile, setSelectedFile] = useState<File | null>(null);
|
||||
const [selectedPreviewUrl, setSelectedPreviewUrl] = useState<string | null>(
|
||||
null,
|
||||
);
|
||||
const [crop, setCrop] = useState<PercentCrop>();
|
||||
const imgRef = useRef<HTMLImageElement | null>(null);
|
||||
|
||||
const updateUser = api.user.update.useMutation({
|
||||
onSuccess: async () => {
|
||||
@@ -45,24 +83,109 @@ export default function Avatar({
|
||||
|
||||
const avatarUrl = userImage ? getAvatarUrl(userImage) : undefined;
|
||||
|
||||
const uploadAvatar = async (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
try {
|
||||
event.preventDefault();
|
||||
const onFileChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
event.preventDefault();
|
||||
const file = event.target.files?.[0] ?? null;
|
||||
if (!file || !userId) {
|
||||
return showPopup({
|
||||
header: t`Error uploading profile image`,
|
||||
message: t`Please select a file to upload.`,
|
||||
icon: "error",
|
||||
});
|
||||
}
|
||||
// Open crop dialog with preview
|
||||
setSelectedFile(file);
|
||||
const objUrl = URL.createObjectURL(file);
|
||||
setSelectedPreviewUrl(objUrl);
|
||||
setCropDialogOpen(true);
|
||||
};
|
||||
|
||||
const file = event.target.files?.[0];
|
||||
|
||||
if (!file || !userId) {
|
||||
return showPopup({
|
||||
header: t`Error uploading profile image`,
|
||||
message: t`Please select a file to upload.`,
|
||||
icon: "error",
|
||||
});
|
||||
const onImageLoad = useCallback(
|
||||
(e: React.SyntheticEvent<HTMLImageElement>) => {
|
||||
const { naturalWidth, naturalHeight } = e.currentTarget;
|
||||
// Create a centered square crop at ~90% of the smaller dimension
|
||||
// Compute width% so that the square fits within the image
|
||||
let widthPercent: number;
|
||||
let heightPercent: number;
|
||||
if (naturalWidth >= naturalHeight) {
|
||||
// landscape: height is limiting
|
||||
heightPercent = 90;
|
||||
widthPercent = (naturalHeight / naturalWidth) * heightPercent;
|
||||
} else {
|
||||
// portrait: width is limiting
|
||||
widthPercent = 90;
|
||||
heightPercent = (naturalWidth / naturalHeight) * widthPercent;
|
||||
}
|
||||
const x = (100 - widthPercent) / 2;
|
||||
const y = (100 - heightPercent) / 2;
|
||||
setCrop({ unit: "%", x, y, width: widthPercent, height: heightPercent });
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
const fileExt = file.name.split(".").pop();
|
||||
const fileName = `${userId}/avatar-${generateUID()}.${fileExt}`;
|
||||
const getCroppedBlob = useCallback(async (): Promise<Blob> => {
|
||||
if (!imgRef.current || !crop) throw new Error("No crop to save");
|
||||
const image = imgRef.current;
|
||||
|
||||
const canvas = document.createElement("canvas");
|
||||
const cropXpx = (crop.x / 100) * image.naturalWidth;
|
||||
const cropYpx = (crop.y / 100) * image.naturalHeight;
|
||||
const cropWpx = (crop.width / 100) * image.naturalWidth;
|
||||
const cropHpx = (crop.height / 100) * image.naturalHeight;
|
||||
canvas.width = Math.max(1, Math.floor(cropWpx));
|
||||
canvas.height = Math.max(1, Math.floor(cropHpx));
|
||||
const ctx = canvas.getContext("2d");
|
||||
if (!ctx) throw new Error("Canvas not supported");
|
||||
|
||||
// For better quality on HiDPI screens
|
||||
const pixelRatio = window.devicePixelRatio || 1;
|
||||
canvas.width = canvas.width * pixelRatio;
|
||||
canvas.height = canvas.height * pixelRatio;
|
||||
ctx.setTransform(pixelRatio, 0, 0, pixelRatio, 0, 0);
|
||||
ctx.imageSmoothingQuality = "high";
|
||||
|
||||
ctx.drawImage(
|
||||
image,
|
||||
cropXpx,
|
||||
cropYpx,
|
||||
cropWpx,
|
||||
cropHpx,
|
||||
0,
|
||||
0,
|
||||
canvas.width / pixelRatio,
|
||||
canvas.height / pixelRatio,
|
||||
);
|
||||
|
||||
const mime = selectedFile?.type ?? "image/jpeg";
|
||||
const blob: Blob = await new Promise((resolve, reject) => {
|
||||
canvas.toBlob(
|
||||
(b) => (b ? resolve(b) : reject(new Error("toBlob failed"))),
|
||||
mime,
|
||||
);
|
||||
});
|
||||
return blob;
|
||||
}, [crop, selectedFile]);
|
||||
|
||||
const resetCropState = useCallback(() => {
|
||||
setCrop(undefined);
|
||||
setSelectedFile(null);
|
||||
if (selectedPreviewUrl) URL.revokeObjectURL(selectedPreviewUrl);
|
||||
setSelectedPreviewUrl(null);
|
||||
}, [selectedPreviewUrl]);
|
||||
|
||||
const handleCancelCrop = useCallback(() => {
|
||||
setCropDialogOpen(false);
|
||||
resetCropState();
|
||||
}, [resetCropState]);
|
||||
|
||||
const handleSaveCrop = useCallback(async () => {
|
||||
try {
|
||||
if (!userId || !selectedFile) return;
|
||||
setUploading(true);
|
||||
const blob = await getCroppedBlob();
|
||||
|
||||
const originalExt = selectedFile.name.split(".").pop() ?? "jpg";
|
||||
const fileName = `${userId}/avatar-${generateUID()}.${originalExt}`;
|
||||
|
||||
const response = await fetch(
|
||||
env("NEXT_PUBLIC_BASE_URL") + "/api/upload/image",
|
||||
@@ -71,26 +194,24 @@ export default function Avatar({
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({ filename: fileName, contentType: file.type }),
|
||||
body: JSON.stringify({ filename: fileName, contentType: blob.type }),
|
||||
},
|
||||
);
|
||||
|
||||
if (!response.ok) throw new Error("Failed to get pre-signed URL");
|
||||
|
||||
const { url } = (await response.json()) as {
|
||||
url: string;
|
||||
};
|
||||
const { url } = (await response.json()) as { url: string };
|
||||
|
||||
const uploadResponse = await fetch(url, {
|
||||
method: "PUT",
|
||||
body: file,
|
||||
body: blob,
|
||||
});
|
||||
|
||||
if (!uploadResponse.ok) throw new Error("Failed to upload profile image");
|
||||
|
||||
updateUser.mutate({
|
||||
image: fileName,
|
||||
});
|
||||
updateUser.mutate({ image: fileName });
|
||||
setCropDialogOpen(false);
|
||||
resetCropState();
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
showPopup({
|
||||
@@ -101,7 +222,14 @@ export default function Avatar({
|
||||
} finally {
|
||||
setUploading(false);
|
||||
}
|
||||
};
|
||||
}, [
|
||||
getCroppedBlob,
|
||||
resetCropState,
|
||||
selectedFile,
|
||||
showPopup,
|
||||
updateUser,
|
||||
userId,
|
||||
]);
|
||||
|
||||
return (
|
||||
<div>
|
||||
@@ -111,7 +239,7 @@ export default function Avatar({
|
||||
type="file"
|
||||
id="single"
|
||||
accept="image/*"
|
||||
onChange={uploadAvatar}
|
||||
onChange={onFileChange}
|
||||
disabled={uploading}
|
||||
/>
|
||||
{avatarUrl ? (
|
||||
@@ -134,6 +262,56 @@ export default function Avatar({
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Crop Dialog */}
|
||||
{cropDialogOpen && (
|
||||
<Modal modalSize="md" positionFromTop="sm" isVisible>
|
||||
<div className="p-4 sm:p-6">
|
||||
<div className="mb-4">
|
||||
<h3 className="text-base font-semibold text-light-1000 dark:text-dark-1000">
|
||||
{t`Crop your avatar`}
|
||||
</h3>
|
||||
<p className="mt-1 text-sm text-light-800 dark:text-dark-800">
|
||||
{t`Adjust the square crop to fit your avatar.`}
|
||||
</p>
|
||||
</div>
|
||||
<div className="max-h-[80vh]">
|
||||
<div className="rounded-md border border-light-600 p-2 dark:border-dark-600">
|
||||
<AnyReactCrop
|
||||
crop={crop}
|
||||
onChange={(_crop: LocalPixelCrop, percentCrop: PercentCrop) =>
|
||||
setCrop(percentCrop)
|
||||
}
|
||||
aspect={1}
|
||||
circularCrop
|
||||
className="w-full"
|
||||
>
|
||||
{/* eslint-disable-next-line @next/next/no-img-element */}
|
||||
<img
|
||||
ref={imgRef}
|
||||
src={selectedPreviewUrl ?? undefined}
|
||||
alt="Avatar to crop"
|
||||
onLoad={onImageLoad}
|
||||
className="h-auto max-h-[50vh] w-full object-contain"
|
||||
/>
|
||||
</AnyReactCrop>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-6 flex items-center justify-end gap-2">
|
||||
<Button
|
||||
variant="ghost"
|
||||
onClick={handleCancelCrop}
|
||||
disabled={uploading}
|
||||
>
|
||||
{t`Cancel`}
|
||||
</Button>
|
||||
<Button onClick={handleSaveCrop} isLoading={uploading}>
|
||||
{t`Save`}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
12
pnpm-lock.yaml
generated
12
pnpm-lock.yaml
generated
@@ -205,6 +205,9 @@ importers:
|
||||
react-icons:
|
||||
specifier: ^5.5.0
|
||||
version: 5.5.0(react@18.3.1)
|
||||
react-image-crop:
|
||||
specifier: ^11.0.10
|
||||
version: 11.0.10(react@18.3.1)
|
||||
react-lottie-player:
|
||||
specifier: ^1.5.5
|
||||
version: 1.5.6(react@18.3.1)
|
||||
@@ -6116,6 +6119,11 @@ packages:
|
||||
peerDependencies:
|
||||
react: '*'
|
||||
|
||||
react-image-crop@11.0.10:
|
||||
resolution: {integrity: sha512-+5FfDXUgYLLqBh1Y/uQhIycpHCbXkI50a+nbfkB1C0xXXUTwkisHDo2QCB1SQJyHCqIuia4FeyReqXuMDKWQTQ==}
|
||||
peerDependencies:
|
||||
react: '>=16.13.1'
|
||||
|
||||
react-is@16.13.1:
|
||||
resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==}
|
||||
|
||||
@@ -13696,6 +13704,10 @@ snapshots:
|
||||
dependencies:
|
||||
react: 18.3.1
|
||||
|
||||
react-image-crop@11.0.10(react@18.3.1):
|
||||
dependencies:
|
||||
react: 18.3.1
|
||||
|
||||
react-is@16.13.1: {}
|
||||
|
||||
react-is@17.0.2: {}
|
||||
|
||||
Reference in New Issue
Block a user