diff --git a/lang/en/common.php b/lang/en/common.php
index b90f4a9e..a73d7bbc 100644
--- a/lang/en/common.php
+++ b/lang/en/common.php
@@ -19,6 +19,11 @@
'uploading' => 'Uploading...',
'remove' => 'Remove photo',
'hint' => 'Recommended: square image, max 2 MB.',
+ 'crop_title' => 'Crop image',
+ 'crop_description' => 'Drag and zoom to frame it. The area inside the circle will be used.',
+ 'crop_hint' => 'Drag to position',
+ 'crop_save' => 'Save',
+ 'crop_cancel' => 'Cancel',
],
'timezone' => [
diff --git a/lang/es/common.php b/lang/es/common.php
index c85c9c0f..5de37818 100644
--- a/lang/es/common.php
+++ b/lang/es/common.php
@@ -19,6 +19,11 @@
'uploading' => 'Subiendo...',
'remove' => 'Eliminar foto',
'hint' => 'Recomendado: imagen cuadrada, máximo 2 MB.',
+ 'crop_title' => 'Recortar imagen',
+ 'crop_description' => 'Arrastra y usa el zoom para encuadrar. Se usará el área dentro del círculo.',
+ 'crop_hint' => 'Arrastra para posicionar',
+ 'crop_save' => 'Guardar',
+ 'crop_cancel' => 'Cancelar',
],
'timezone' => [
diff --git a/lang/pt-BR/common.php b/lang/pt-BR/common.php
index 6cfae2e2..ee3eefcb 100644
--- a/lang/pt-BR/common.php
+++ b/lang/pt-BR/common.php
@@ -19,6 +19,11 @@
'uploading' => 'Enviando...',
'remove' => 'Remover foto',
'hint' => 'Recomendado: imagem quadrada, máximo 2 MB.',
+ 'crop_title' => 'Recortar imagem',
+ 'crop_description' => 'Arraste e use o zoom para enquadrar. A área dentro do círculo será usada.',
+ 'crop_hint' => 'Arraste para posicionar',
+ 'crop_save' => 'Salvar',
+ 'crop_cancel' => 'Cancelar',
],
'timezone' => [
diff --git a/package-lock.json b/package-lock.json
index 3941c5b3..bff42b4e 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -34,6 +34,7 @@
"tw-animate-css": "^1.2.5",
"vaul-vue": "^0.4.1",
"vue": "^3.5.13",
+ "vue-advanced-cropper": "^2.8.9",
"vue-input-otp": "^0.3.2",
"vue-sonner": "^2.0.9"
},
@@ -4030,6 +4031,12 @@
"url": "https://polar.sh/cva"
}
},
+ "node_modules/classnames": {
+ "version": "2.5.1",
+ "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz",
+ "integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==",
+ "license": "MIT"
+ },
"node_modules/cliui": {
"version": "9.0.1",
"resolved": "https://registry.npmjs.org/cliui/-/cliui-9.0.1.tgz",
@@ -4817,6 +4824,12 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/debounce": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/debounce/-/debounce-1.2.1.tgz",
+ "integrity": "sha512-XRRe6Glud4rd/ZGQfiV1ruXSfbvfJedlV9Y6zOlP+2K04vBYiJEte6stfFkCP03aMnY5tsipamumUjL14fofug==",
+ "license": "MIT"
+ },
"node_modules/debug": {
"version": "4.4.3",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
@@ -4954,6 +4967,12 @@
"dev": true,
"license": "ISC"
},
+ "node_modules/easy-bem": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/easy-bem/-/easy-bem-1.1.1.tgz",
+ "integrity": "sha512-GJRqdiy2h+EXy6a8E6R+ubmqUM08BK0FWNq41k24fup6045biQ8NXxoXimiwegMQvFFV3t1emADdGNL1TlS61A==",
+ "license": "MIT"
+ },
"node_modules/elkjs": {
"version": "0.10.2",
"resolved": "https://registry.npmjs.org/elkjs/-/elkjs-0.10.2.tgz",
@@ -9588,6 +9607,24 @@
}
}
},
+ "node_modules/vue-advanced-cropper": {
+ "version": "2.8.9",
+ "resolved": "https://registry.npmjs.org/vue-advanced-cropper/-/vue-advanced-cropper-2.8.9.tgz",
+ "integrity": "sha512-1jc5gO674kVGpJKekoaol6ZlwaF5VYDLSBwBOUpViW0IOrrRsyLw6XNszjEqgbavvqinlKNS6Kqlom3B5M72Tw==",
+ "license": "MIT",
+ "dependencies": {
+ "classnames": "^2.2.6",
+ "debounce": "^1.2.0",
+ "easy-bem": "^1.0.2"
+ },
+ "engines": {
+ "node": ">=8",
+ "npm": ">=5"
+ },
+ "peerDependencies": {
+ "vue": "^3.0.0"
+ }
+ },
"node_modules/vue-eslint-parser": {
"version": "10.2.0",
"resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-10.2.0.tgz",
diff --git a/package.json b/package.json
index 9f650655..c01ae957 100644
--- a/package.json
+++ b/package.json
@@ -67,6 +67,7 @@
"tw-animate-css": "^1.2.5",
"vaul-vue": "^0.4.1",
"vue": "^3.5.13",
+ "vue-advanced-cropper": "^2.8.9",
"vue-input-otp": "^0.3.2",
"vue-sonner": "^2.0.9"
},
diff --git a/resources/js/components/ImageCropperDialog.vue b/resources/js/components/ImageCropperDialog.vue
new file mode 100644
index 00000000..37facabd
--- /dev/null
+++ b/resources/js/components/ImageCropperDialog.vue
@@ -0,0 +1,142 @@
+
+
+
+
+
diff --git a/resources/js/components/PhotoUpload.vue b/resources/js/components/PhotoUpload.vue
index 3be75a6f..033212c5 100644
--- a/resources/js/components/PhotoUpload.vue
+++ b/resources/js/components/PhotoUpload.vue
@@ -4,6 +4,7 @@ import { IconTrash } from '@tabler/icons-vue';
import { trans } from 'laravel-vue-i18n';
import { ref } from 'vue';
+import ImageCropperDialog from '@/components/ImageCropperDialog.vue';
import { Avatar } from '@/components/ui/avatar';
import { Button } from '@/components/ui/button';
import {
@@ -34,6 +35,12 @@ const props = withDefaults(defineProps