305 lines
12 KiB
TypeScript
305 lines
12 KiB
TypeScript
"use client";
|
|
import { useState, useEffect } from "react";
|
|
import { useForm } from "react-hook-form";
|
|
import { zodResolver } from "@hookform/resolvers/zod";
|
|
import { ParticipaSchema, ParticipaFormData } from "@/utils/validations";
|
|
import { Input } from "../ui/Input";
|
|
import { Button } from "../ui/Button";
|
|
import { Checkbox } from "../ui/Checkbox";
|
|
import { Textarea } from "../ui/Textarea";
|
|
import { motion, AnimatePresence } from "framer-motion";
|
|
import { useLanguage } from "@/context/LanguageContext";
|
|
import { supabase } from "@/lib/supabase/client";
|
|
|
|
export const ParticipaForm = () => {
|
|
const { t, locale } = useLanguage();
|
|
|
|
const [isSubmitting, setIsSubmitting] = useState(false);
|
|
const [success, setSuccess] = useState(false);
|
|
const [errorMsg, setErrorMsg] = useState<string | null>(null);
|
|
|
|
const [campanas, setCampanas] = useState<any[]>([]);
|
|
|
|
// Estados para manejar las preguntas dinámicas (El Screener)
|
|
const [preguntasDinamicas, setPreguntasDinamicas] = useState<any[]>([]);
|
|
const [respuestasDinamicas, setRespuestasDinamicas] = useState<Record<string, any>>({});
|
|
|
|
const {
|
|
register,
|
|
handleSubmit,
|
|
watch,
|
|
formState: { errors },
|
|
reset
|
|
} = useForm<ParticipaFormData>({
|
|
resolver: zodResolver(ParticipaSchema),
|
|
defaultValues: {
|
|
interes: "" as any
|
|
}
|
|
});
|
|
|
|
// Vigila en tiempo real qué campaña ha seleccionado el usuario
|
|
const campanaSeleccionadaId = watch("interes");
|
|
|
|
useEffect(() => {
|
|
const fetchCampanas = async () => {
|
|
try {
|
|
const { data, error } = await supabase
|
|
.from("campanas")
|
|
.select(`
|
|
id,
|
|
nombre,
|
|
encuestas ( configuracion )
|
|
`)
|
|
.eq('estado', 'activa');
|
|
|
|
if (data && !error) {
|
|
setCampanas(data);
|
|
}
|
|
} catch (err) {
|
|
console.error("Error al cargar las campañas:", err);
|
|
}
|
|
};
|
|
|
|
fetchCampanas();
|
|
}, []);
|
|
|
|
// Efecto que se dispara cuando el usuario cambia de campaña en el select
|
|
useEffect(() => {
|
|
if (campanaSeleccionadaId) {
|
|
const campana = campanas.find(c => c.id === campanaSeleccionadaId);
|
|
|
|
if (campana && campana.encuestas && campana.encuestas.length > 0) {
|
|
const config = campana.encuestas[0].configuracion;
|
|
if (config && config.preguntas_filtro) {
|
|
setPreguntasDinamicas(config.preguntas_filtro);
|
|
setRespuestasDinamicas({});
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
setPreguntasDinamicas([]);
|
|
setRespuestasDinamicas({});
|
|
}, [campanaSeleccionadaId, campanas]);
|
|
|
|
// Manejador para los cambios en los inputs dinámicos
|
|
const handleDynamicChange = (preguntaId: string, valor: any) => {
|
|
setRespuestasDinamicas(prev => ({
|
|
...prev,
|
|
[preguntaId]: valor
|
|
}));
|
|
};
|
|
|
|
const onSubmit = async (data: ParticipaFormData) => {
|
|
setIsSubmitting(true);
|
|
setErrorMsg(null);
|
|
|
|
const payload: any = {
|
|
datos_participante: {
|
|
nombre: data.nombre,
|
|
apellidos: data.apellidos,
|
|
email: data.email,
|
|
telefono: data.telefono,
|
|
perfil_demografico: {
|
|
provincia: data.provincia,
|
|
edad: Number(data.edad),
|
|
mensaje: data.mensaje || ""
|
|
}
|
|
}
|
|
};
|
|
|
|
if (data.interes && data.interes !== "") {
|
|
payload.campana_id = data.interes;
|
|
payload.datos_especificos = respuestasDinamicas;
|
|
}
|
|
|
|
try {
|
|
const { data: responseData, error } = await supabase.functions.invoke('validate-participant-data', {
|
|
body: payload
|
|
});
|
|
|
|
if (error) {
|
|
throw new Error(error.message || "Error al procesar la solicitud en el servidor.");
|
|
}
|
|
|
|
setSuccess(true);
|
|
reset();
|
|
} catch (err: any) {
|
|
console.error("Error en Screener:", err);
|
|
setErrorMsg(err.message || "Hubo un problema de conexión al enviar tus datos.");
|
|
} finally {
|
|
setIsSubmitting(false);
|
|
}
|
|
};
|
|
|
|
return (
|
|
<div className="w-full">
|
|
<AnimatePresence mode="wait">
|
|
{success ? (
|
|
<motion.div
|
|
initial={{ opacity: 0, scale: 0.95 }}
|
|
animate={{ opacity: 1, scale: 1 }}
|
|
className="p-12 border border-ocean rounded-[32px] text-center bg-ice/10"
|
|
>
|
|
<h3 className="text-2xl font-bold text-navy mb-4">
|
|
{t.participa.success.title}
|
|
</h3>
|
|
<p className="text-navy/70 mb-8">
|
|
{t.participa.success.desc}
|
|
</p>
|
|
<Button onClick={() => window.location.reload()} variant="outline">
|
|
{t.participa.success.btn}
|
|
</Button>
|
|
</motion.div>
|
|
) : (
|
|
<form onSubmit={handleSubmit(onSubmit)} className="space-y-6">
|
|
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
|
<Input label={t.participa.fields.nombre} {...register("nombre")} error={errors.nombre?.message} />
|
|
<Input label={t.participa.fields.apellidos} {...register("apellidos")} error={errors.apellidos?.message} />
|
|
</div>
|
|
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
|
<Input label={t.participa.fields.email} type="email" {...register("email")} error={errors.email?.message} />
|
|
<Input label={t.participa.fields.telefono} type="tel" {...register("telefono")} error={errors.telefono?.message} />
|
|
</div>
|
|
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
|
<Input label={t.participa.fields.provincia} {...register("provincia")} error={errors.provincia?.message} />
|
|
<Input label={t.participa.fields.edad} type="number" {...register("edad")} error={errors.edad?.message} />
|
|
</div>
|
|
|
|
<div className="flex flex-col gap-2">
|
|
<label className="text-[13px] font-extrabold text-navy uppercase tracking-widest">
|
|
{locale === 'en' ? "Select a Campaign (Optional)" : locale === 'ca' ? "Selecciona una Campanya (Opcional)" : "Selecciona una Campaña (Opcional)"}
|
|
</label>
|
|
<div className="relative">
|
|
<select
|
|
{...register("interes")}
|
|
className={`
|
|
w-full bg-ice/10 border rounded-[12px] px-4 py-3.5 text-[15px]
|
|
font-medium text-navy outline-none focus:border-ocean focus:bg-white
|
|
transition-colors appearance-none cursor-pointer
|
|
${errors.interes ? "border-red-500" : "border-crisp"}
|
|
`}
|
|
>
|
|
<option value="">
|
|
{locale === 'en' ? "None / General Registration" : locale === 'ca' ? "Cap / Registre General" : "Ninguna / Registro General"}
|
|
</option>
|
|
|
|
{campanas.map((campana) => (
|
|
<option key={campana.id} value={campana.id}>
|
|
{campana.nombre}
|
|
</option>
|
|
))}
|
|
</select>
|
|
<div className="absolute right-4 top-1/2 -translate-y-1/2 pointer-events-none opacity-40 text-navy">
|
|
<svg width="12" height="8" viewBox="0 0 12 8" fill="none" stroke="currentColor">
|
|
<path d="M1 1L6 6L11 1" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
|
|
</svg>
|
|
</div>
|
|
</div>
|
|
{errors.interes && <span className="text-red-500 text-xs font-bold">{errors.interes.message}</span>}
|
|
</div>
|
|
|
|
{/* --- BLOQUE DINÁMICO MULTIDIOMA: EL SCREENER --- */}
|
|
{preguntasDinamicas.length > 0 && (
|
|
<motion.div
|
|
initial={{ opacity: 0, height: 0 }}
|
|
animate={{ opacity: 1, height: "auto" }}
|
|
className="p-6 bg-sky/5 border border-sky/20 rounded-[20px] space-y-6 mt-4"
|
|
>
|
|
<h4 className="text-[15px] font-extrabold text-ocean uppercase tracking-widest border-b border-sky/20 pb-2">
|
|
{locale === 'en' ? "Specific Campaign Questions" : locale === 'ca' ? "Preguntes de la Campanya" : "Preguntas de la Campaña"}
|
|
</h4>
|
|
|
|
{preguntasDinamicas.map((pregunta) => {
|
|
|
|
// HELPER MULTIDIOMA: Lee el JSON para el idioma actual o usa el string antiguo
|
|
const obtenerTexto = (campo: any) => {
|
|
if (!campo) return "";
|
|
if (typeof campo === 'string') return campo;
|
|
return campo[locale] || campo['es'] || "";
|
|
};
|
|
|
|
const obtenerOpciones = (campo: any): string[] => {
|
|
if (!campo) return [];
|
|
if (Array.isArray(campo)) return campo;
|
|
return campo[locale] || campo['es'] || [];
|
|
};
|
|
|
|
const etiquetaTraducida = obtenerTexto(pregunta.etiqueta);
|
|
const opcionesTraducidas = obtenerOpciones(pregunta.opciones);
|
|
|
|
return (
|
|
<div key={pregunta.id_pregunta} className="flex flex-col gap-2">
|
|
<label className="text-[14px] font-bold text-navy">
|
|
{etiquetaTraducida} {pregunta.requerido && <span className="text-ocean">*</span>}
|
|
</label>
|
|
|
|
{pregunta.tipo === "select" && (
|
|
<select
|
|
required={pregunta.requerido}
|
|
onChange={(e) => handleDynamicChange(pregunta.id_pregunta, e.target.value)}
|
|
className="w-full bg-white border border-crisp rounded-[12px] px-4 py-3 text-[15px] font-medium text-navy focus:border-ocean outline-none"
|
|
>
|
|
<option value="">{locale === 'en' ? "Select..." : locale === 'ca' ? "Selecciona..." : "Selecciona..."}</option>
|
|
{opcionesTraducidas.map((opt: string) => (
|
|
<option key={opt} value={opt}>{opt}</option>
|
|
))}
|
|
</select>
|
|
)}
|
|
|
|
{pregunta.tipo === "checkbox" && (
|
|
<div className="flex flex-wrap gap-4 mt-2">
|
|
{opcionesTraducidas.map((opt: string) => (
|
|
<label key={opt} className="flex items-center gap-2 text-[14px] text-navy/80 cursor-pointer">
|
|
<input
|
|
type="checkbox"
|
|
value={opt}
|
|
onChange={(e) => {
|
|
const currentAnswers = respuestasDinamicas[pregunta.id_pregunta] || [];
|
|
let newAnswers;
|
|
if (e.target.checked) {
|
|
newAnswers = [...currentAnswers, opt];
|
|
} else {
|
|
newAnswers = currentAnswers.filter((a: string) => a !== opt);
|
|
}
|
|
handleDynamicChange(pregunta.id_pregunta, newAnswers);
|
|
}}
|
|
className="w-4 h-4 rounded border-crisp text-ocean focus:ring-ocean"
|
|
/>
|
|
{opt}
|
|
</label>
|
|
))}
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
})}
|
|
</motion.div>
|
|
)}
|
|
{/* --- FIN BLOQUE DINÁMICO --- */}
|
|
|
|
<Textarea label={t.participa.fields.mensaje} rows={3} {...register("mensaje")} error={errors.mensaje?.message} />
|
|
|
|
<Checkbox label={t.contacto.form.privacy} {...register("privacidad")} error={errors.privacidad?.message} />
|
|
|
|
{errorMsg && (
|
|
<motion.p initial={{ opacity: 0 }} animate={{ opacity: 1 }} className="text-red-600 bg-red-50 p-4 rounded-lg text-sm font-bold">
|
|
{errorMsg}
|
|
</motion.p>
|
|
)}
|
|
|
|
<div className="pt-4">
|
|
<Button type="submit" disabled={isSubmitting} className="w-full md:w-auto" variant="primary">
|
|
{isSubmitting
|
|
? (locale === 'en' ? "Sending application..." : locale === 'ca' ? "Enviant sol·licitud..." : "Enviando solicitud...")
|
|
: (locale === 'en' ? "Send application" : locale === 'ca' ? "Enviar sol·licitud" : "Enviar solicitud")}
|
|
</Button>
|
|
</div>
|
|
</form>
|
|
)}
|
|
</AnimatePresence>
|
|
</div>
|
|
);
|
|
}; |