Feat: Cambios Varios 2
This commit is contained in:
107
frontend/public-web/src/pages/Steps/PhotoUploadStep.tsx
Normal file
107
frontend/public-web/src/pages/Steps/PhotoUploadStep.tsx
Normal file
@@ -0,0 +1,107 @@
|
||||
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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user