diff --git a/mvp/b2c/package-lock.json b/mvp/b2c/package-lock.json
index b885150..568ee9b 100644
--- a/mvp/b2c/package-lock.json
+++ b/mvp/b2c/package-lock.json
@@ -17,6 +17,7 @@
"postgres": "^3.4.9",
"react": "19.2.4",
"react-dom": "19.2.4",
+ "react-easy-crop": "^5.5.7",
"tailwindcss": "^4.3.0",
"zod": "^4.4.3"
},
@@ -7209,6 +7210,12 @@
"svg-arc-to-cubic-bezier": "^3.0.0"
}
},
+ "node_modules/normalize-wheel": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/normalize-wheel/-/normalize-wheel-1.0.1.tgz",
+ "integrity": "sha512-1OnlAPZ3zgrk8B91HyRj+eVv+kS5u+Z0SCsak6Xil/kmgEia50ga7zfkumayonZrImffAxPU/5WcyGhzetHNPA==",
+ "license": "BSD-3-Clause"
+ },
"node_modules/object-assign": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
@@ -7635,6 +7642,20 @@
"react": "^19.2.4"
}
},
+ "node_modules/react-easy-crop": {
+ "version": "5.5.7",
+ "resolved": "https://registry.npmjs.org/react-easy-crop/-/react-easy-crop-5.5.7.tgz",
+ "integrity": "sha512-kYo4NtMeXFQB7h1U+h5yhUkE46WQbQdq7if54uDlbMdZHdRgNehfvaFrXnFw5NR1PNoUOJIfTwLnWmEx/MaZnA==",
+ "license": "MIT",
+ "dependencies": {
+ "normalize-wheel": "^1.0.1",
+ "tslib": "^2.0.1"
+ },
+ "peerDependencies": {
+ "react": ">=16.4.0",
+ "react-dom": ">=16.4.0"
+ }
+ },
"node_modules/react-is": {
"version": "16.13.1",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
diff --git a/mvp/b2c/package.json b/mvp/b2c/package.json
index f4fba54..633a7a3 100644
--- a/mvp/b2c/package.json
+++ b/mvp/b2c/package.json
@@ -26,6 +26,7 @@
"postgres": "^3.4.9",
"react": "19.2.4",
"react-dom": "19.2.4",
+ "react-easy-crop": "^5.5.7",
"tailwindcss": "^4.3.0",
"zod": "^4.4.3"
},
diff --git a/mvp/b2c/src/components/panel/AboutFotoUploader.tsx b/mvp/b2c/src/components/panel/AboutFotoUploader.tsx
index 631597d..8f51e5e 100644
--- a/mvp/b2c/src/components/panel/AboutFotoUploader.tsx
+++ b/mvp/b2c/src/components/panel/AboutFotoUploader.tsx
@@ -6,6 +6,7 @@ import {
quitarAboutFoto,
type LogoResult,
} from '@/app/panel/empresa/actions';
+import ImageCropperField from '@/components/panel/ImageCropperField';
export default function AboutFotoUploader({ fotoUrl }: { fotoUrl: string | null }) {
const [state, formAction, pending] = useActionState
Foto añadida ✓
} -PNG, JPG o WEBP · máx. 2 MB.
++ PNG, JPG o WEBP. Se recorta y optimiza a WebP (máx. 1200 px). +
); } diff --git a/mvp/b2c/src/components/panel/ImageCropperField.tsx b/mvp/b2c/src/components/panel/ImageCropperField.tsx new file mode 100644 index 0000000..e950801 --- /dev/null +++ b/mvp/b2c/src/components/panel/ImageCropperField.tsx @@ -0,0 +1,211 @@ +'use client'; + +import { useCallback, useEffect, useRef, useState } from 'react'; +import Cropper from 'react-easy-crop'; +import { getCroppedWebp, type CropArea } from '@/lib/image/crop'; + +type Props = { + name: string; + maxDimension: number; + accept: string; + buttonLabel?: string; + previewClassName?: string; + previewImgClassName?: string; + allowSvgPassthrough?: boolean; + quality?: number; + disabled?: boolean; +}; + +// Campo de imagen con recorte (zoom + pan) y reescalado a WebP en el navegador. +// El archivo optimizado se inyecta en un oculto con `name`, +// de modo que el formulario padre lo envía como si fuera la selección original. +export default function ImageCropperField({ + name, + maxDimension, + accept, + buttonLabel = 'Elegir imagen', + previewClassName = 'h-24 w-24 rounded-lg', + previewImgClassName = 'h-full w-full object-cover', + allowSvgPassthrough = false, + quality = 0.82, + disabled = false, +}: Props) { + const pickerRef = useRef{error}
} + + {modalSrc && ( +