Enlace del email = subir solo fotos (sin re-preguntar ni re-llamar)
Arregla 2 problemas del flujo de subir fotos desde el email: - El enlace iba a /formulario (form completo) y al enviarlo re-ejecutaba procesarLead, que VOLVÍA a llamar. Ahora el email apunta a /solicitud/[id]/fotos, una página ligera (SubirFotos): solo sube fotos (+ nota opcional) al lead de la URL, re-señala perfilCompleto y NO llama. - Guarda en procesarLead: si el lead ya tiene llamada_completada, no se vuelve a llamar (ni se pisa la transcripción real del webhook). Copy de la página en COPY-GUIDE §3. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
88
mvp/b2c/src/components/funnel/SubirFotos.tsx
Normal file
88
mvp/b2c/src/components/funnel/SubirFotos.tsx
Normal file
@@ -0,0 +1,88 @@
|
||||
'use client';
|
||||
|
||||
import { useState } from 'react';
|
||||
import { useFormStatus } from 'react-dom';
|
||||
|
||||
const MAX_FOTOS = 6;
|
||||
|
||||
const inputClass =
|
||||
'w-full px-4 py-3 text-base font-sans text-dark bg-white border-[1.5px] border-gray-200 rounded-lg transition-all duration-150 outline-none placeholder:text-gray-400 focus:border-black focus:shadow-[0_0_0_3px_rgba(0,0,0,0.06)]';
|
||||
|
||||
function SubmitButton() {
|
||||
const { pending } = useFormStatus();
|
||||
return (
|
||||
<button
|
||||
type="submit"
|
||||
className="btn btn-primary btn-lg w-full justify-center mt-1 disabled:opacity-60 disabled:cursor-not-allowed disabled:transform-none"
|
||||
disabled={pending}
|
||||
aria-busy={pending}
|
||||
>
|
||||
{pending ? (
|
||||
<>
|
||||
<span
|
||||
className="w-[18px] h-[18px] border-2 border-white/30 border-t-white rounded-full animate-[spin_0.7s_linear_infinite] shrink-0"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
Enviando…
|
||||
</>
|
||||
) : (
|
||||
'Enviar mis fotos'
|
||||
)}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
export default function SubirFotos({
|
||||
action,
|
||||
}: {
|
||||
action: (formData: FormData) => void | Promise<void>;
|
||||
}) {
|
||||
const [previews, setPreviews] = useState<string[]>([]);
|
||||
|
||||
const handleFiles = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const files = Array.from(e.target.files ?? []).slice(0, MAX_FOTOS);
|
||||
previews.forEach((url) => URL.revokeObjectURL(url));
|
||||
setPreviews(files.map((f) => URL.createObjectURL(f)));
|
||||
};
|
||||
|
||||
return (
|
||||
<form action={action} className="flex flex-col gap-5">
|
||||
<div className="flex flex-col gap-2">
|
||||
<label htmlFor="fotos" className="text-sm font-semibold text-dark">
|
||||
Fotos del espacio <span className="text-gray-400 font-normal">(hasta {MAX_FOTOS})</span>
|
||||
</label>
|
||||
<input
|
||||
id="fotos"
|
||||
name="fotos"
|
||||
type="file"
|
||||
accept="image/*"
|
||||
multiple
|
||||
onChange={handleFiles}
|
||||
className="block w-full text-sm text-gray-600 file:mr-4 file:py-2.5 file:px-4 file:rounded-lg file:border-0 file:text-sm file:font-semibold file:bg-black file:text-white hover:file:bg-gray-800 file:cursor-pointer cursor-pointer"
|
||||
/>
|
||||
{previews.length > 0 && (
|
||||
<div className="flex flex-wrap gap-3 mt-2">
|
||||
{previews.map((url, i) => (
|
||||
// eslint-disable-next-line @next/next/no-img-element
|
||||
<img
|
||||
key={i}
|
||||
src={url}
|
||||
alt=""
|
||||
className="w-24 h-24 object-cover rounded-lg border border-gray-200"
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-2">
|
||||
<label htmlFor="nota" className="text-sm font-semibold text-dark">
|
||||
¿Algo que quieras añadir? <span className="text-gray-400 font-normal">(opcional)</span>
|
||||
</label>
|
||||
<textarea id="nota" name="nota" rows={3} className={inputClass} />
|
||||
</div>
|
||||
|
||||
<SubmitButton />
|
||||
</form>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user