107 lines
5.9 KiB
TypeScript
107 lines
5.9 KiB
TypeScript
|
|
import { useCallback } from 'react';
|
||
|
|
import { useDropzone } from 'react-dropzone';
|
||
|
|
import { StepWrapper } from '../../components/StepWrapper';
|
||
|
|
import { useWizardStore } from '../../store/wizardStore';
|
||
|
|
import { Upload, Plus, Trash2, ArrowLeft, ChevronRight } from 'lucide-react';
|
||
|
|
import clsx from 'clsx';
|
||
|
|
|
||
|
|
export default function PhotoUploadStep() {
|
||
|
|
const { setStep, photos, existingImages, removePhoto, removeExistingImage, addPhoto } = useWizardStore();
|
||
|
|
const baseUrl = import.meta.env.VITE_BASE_URL;
|
||
|
|
|
||
|
|
const onDrop = useCallback((acceptedFiles: File[]) => {
|
||
|
|
acceptedFiles.forEach(file => addPhoto(file));
|
||
|
|
}, [addPhoto]);
|
||
|
|
|
||
|
|
const { getRootProps, getInputProps, isDragActive } = useDropzone({
|
||
|
|
onDrop,
|
||
|
|
accept: { 'image/*': [] },
|
||
|
|
maxFiles: 10
|
||
|
|
});
|
||
|
|
|
||
|
|
return (
|
||
|
|
<StepWrapper>
|
||
|
|
<div className="mb-10 text-center">
|
||
|
|
<h2 className="text-4xl font-black text-slate-900 tracking-tighter uppercase leading-none">
|
||
|
|
Galería de <br />
|
||
|
|
<span className="text-blue-600">Imágenes</span>
|
||
|
|
</h2>
|
||
|
|
<p className="text-slate-400 text-sm mt-3 font-medium">Gestiona las fotos de tu aviso. La primera será la portada.</p>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div className="space-y-10">
|
||
|
|
|
||
|
|
{/* GRILLA DE FOTOS (EXISTENTES + NUEVAS) */}
|
||
|
|
{(existingImages.length > 0 || photos.length > 0) && (
|
||
|
|
<section className="grid grid-cols-2 md:grid-cols-4 gap-4">
|
||
|
|
|
||
|
|
{/* Renderizado de Fotos Heredadas */}
|
||
|
|
{existingImages.map((img, idx) => (
|
||
|
|
<div key={`old-${idx}`} className="group relative aspect-square bg-slate-100 rounded-[2rem] overflow-hidden border-2 border-slate-100 shadow-sm transition-all hover:shadow-xl">
|
||
|
|
<img src={`${baseUrl}${img.url}`} className="w-full h-full object-cover" alt="Original" />
|
||
|
|
<div className="absolute inset-0 bg-black/40 opacity-0 group-hover:opacity-100 transition-opacity flex items-center justify-center">
|
||
|
|
<button onClick={() => removeExistingImage(idx)} className="bg-rose-500 text-white p-3 rounded-2xl hover:bg-rose-600 transition-colors shadow-lg">
|
||
|
|
<Trash2 size={20} />
|
||
|
|
</button>
|
||
|
|
</div>
|
||
|
|
{idx === 0 && (
|
||
|
|
<div className="absolute top-3 left-3 bg-blue-600 text-white text-[8px] font-black uppercase px-2 py-1 rounded-lg shadow-lg">Portada</div>
|
||
|
|
)}
|
||
|
|
</div>
|
||
|
|
))}
|
||
|
|
|
||
|
|
{/* Renderizado de Fotos Nuevas (Files) */}
|
||
|
|
{photos.map((file, idx) => (
|
||
|
|
<div key={`new-${idx}`} className="group relative aspect-square bg-blue-50 rounded-[2rem] overflow-hidden border-2 border-blue-200 shadow-sm transition-all hover:shadow-xl">
|
||
|
|
<img src={URL.createObjectURL(file)} className="w-full h-full object-cover" alt="Nueva" />
|
||
|
|
<div className="absolute inset-0 bg-black/40 opacity-0 group-hover:opacity-100 transition-opacity flex items-center justify-center">
|
||
|
|
<button onClick={() => removePhoto(idx)} className="bg-rose-500 text-white p-3 rounded-2xl shadow-lg">
|
||
|
|
<Trash2 size={20} />
|
||
|
|
</button>
|
||
|
|
</div>
|
||
|
|
{existingImages.length === 0 && idx === 0 && (
|
||
|
|
<div className="absolute top-3 left-3 bg-blue-600 text-white text-[8px] font-black uppercase px-2 py-1 rounded-lg shadow-lg">Portada</div>
|
||
|
|
)}
|
||
|
|
</div>
|
||
|
|
))}
|
||
|
|
|
||
|
|
{/* BOTÓN "AÑADIR MÁS" dentro de la grilla si ya hay fotos */}
|
||
|
|
<div {...getRootProps()} className="aspect-square bg-slate-50 border-2 border-dashed border-slate-200 rounded-[2rem] flex flex-col items-center justify-center cursor-pointer hover:bg-white hover:border-blue-400 transition-all group">
|
||
|
|
<input {...getInputProps()} />
|
||
|
|
<div className="p-3 bg-white rounded-2xl shadow-sm text-slate-400 group-hover:text-blue-500 group-hover:shadow-blue-100 transition-all">
|
||
|
|
<Plus size={24} />
|
||
|
|
</div>
|
||
|
|
<span className="text-[9px] font-black text-slate-400 uppercase mt-2 group-hover:text-blue-600">Añadir más</span>
|
||
|
|
</div>
|
||
|
|
</section>
|
||
|
|
)}
|
||
|
|
|
||
|
|
{/* ÁREA DE CARGA INICIAL (Solo si está todo vacío) */}
|
||
|
|
{existingImages.length === 0 && photos.length === 0 && (
|
||
|
|
<section {...getRootProps()} className={clsx(
|
||
|
|
"p-16 border-4 border-dashed rounded-[3rem] text-center transition-all cursor-pointer group",
|
||
|
|
isDragActive ? "border-blue-500 bg-blue-50" : "border-slate-100 bg-white hover:border-slate-200 hover:bg-slate-50"
|
||
|
|
)}>
|
||
|
|
<input {...getInputProps()} />
|
||
|
|
<div className="w-20 h-20 bg-blue-50 text-blue-600 rounded-[2rem] flex items-center justify-center mx-auto mb-6 group-hover:scale-110 transition-transform">
|
||
|
|
<Upload size={32} />
|
||
|
|
</div>
|
||
|
|
<h3 className="text-xl font-black text-slate-900 uppercase tracking-tighter mb-2">Sube tus fotografías</h3>
|
||
|
|
<p className="text-slate-400 text-xs font-medium max-w-xs mx-auto leading-relaxed">
|
||
|
|
Arrastra tus fotos aquí o haz clic para buscarlas. Soporta JPG, PNG y WebP.
|
||
|
|
</p>
|
||
|
|
</section>
|
||
|
|
)}
|
||
|
|
|
||
|
|
<div className="flex gap-4 pt-6">
|
||
|
|
<button onClick={() => setStep(4)} className="flex-1 py-5 bg-slate-100 text-slate-500 font-black uppercase text-xs rounded-2xl hover:bg-slate-200 transition-all flex items-center justify-center gap-3">
|
||
|
|
<ArrowLeft size={16} /> Volver
|
||
|
|
</button>
|
||
|
|
<button onClick={() => setStep(6)} className="flex-[2] py-5 bg-blue-600 text-white font-black uppercase text-xs rounded-2xl shadow-xl shadow-blue-200 hover:bg-blue-700 transition-all flex items-center justify-center gap-3">
|
||
|
|
Continuar al Resumen <ChevronRight size={18} />
|
||
|
|
</button>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</StepWrapper>
|
||
|
|
);
|
||
|
|
}
|