225 lines
8.6 KiB
TypeScript
225 lines
8.6 KiB
TypeScript
'use client';
|
|
import { useState } from 'react';
|
|
import { Icon } from '@iconify/react';
|
|
import type { ModalProps } from '@/app/(landing)/types/ui';
|
|
|
|
const API_URL = process.env.NEXT_PUBLIC_API_URL ?? 'https://localhost:7001';
|
|
|
|
type Status = 'idle' | 'loading' | 'success' | 'error';
|
|
|
|
const initialForm = { nombre: '', empresa: '', email: '', telefono: '', mensaje: '' };
|
|
|
|
export const ContactModal = ({ isOpen, onClose, isNavyBg }: ModalProps) => {
|
|
const [form, setForm] = useState(initialForm);
|
|
const [status, setStatus] = useState<Status>('idle');
|
|
|
|
if (!isOpen) return null;
|
|
|
|
const set = (k: keyof typeof form) =>
|
|
(e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement>) =>
|
|
setForm(f => ({ ...f, [k]: e.target.value }));
|
|
|
|
const handleClose = () => {
|
|
if (status !== 'loading') {
|
|
onClose();
|
|
setTimeout(() => { setForm(initialForm); setStatus('idle'); }, 300);
|
|
}
|
|
};
|
|
|
|
const handleSubmit = async (e: React.FormEvent) => {
|
|
e.preventDefault();
|
|
if (!form.nombre.trim() || !form.email.trim()) return;
|
|
|
|
setStatus('loading');
|
|
try {
|
|
const res = await fetch(`${API_URL}/api/contact`, {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({
|
|
name: form.nombre,
|
|
email: form.email,
|
|
phone: form.telefono || undefined,
|
|
company: form.empresa || undefined,
|
|
message: form.mensaje || 'Sin mensaje adicional.',
|
|
source: 'landing',
|
|
}),
|
|
});
|
|
if (!res.ok) throw new Error('Error del servidor');
|
|
setStatus('success');
|
|
} catch {
|
|
setStatus('error');
|
|
}
|
|
};
|
|
|
|
const card = isNavyBg ? 'var(--card)' : '#ffffff';
|
|
const bdr = isNavyBg ? 'rgba(255,255,255,0.12)' : 'rgba(0,0,0,0.1)';
|
|
const text = isNavyBg ? 'var(--foreground)' : '#0a0a0a';
|
|
const sub = isNavyBg ? 'rgba(255,255,255,0.45)' : '#6a6866';
|
|
const inputStyle: React.CSSProperties = {
|
|
width: '100%', borderRadius: 12, padding: '11px 14px',
|
|
fontSize: 14, fontFamily: 'inherit', outline: 'none',
|
|
transition: 'border-color 150ms',
|
|
...(isNavyBg
|
|
? { background: 'rgba(255,255,255,0.06)', border: '1px solid rgba(255,255,255,0.12)', color: '#fff' }
|
|
: { background: '#f8fafc', border: '1px solid rgba(0,0,0,0.1)', color: '#0a0a0a' }),
|
|
};
|
|
|
|
return (
|
|
<div
|
|
className="fixed inset-0 z-50 flex items-center justify-center p-4"
|
|
style={{ background: 'rgba(0,0,0,0.6)', backdropFilter: 'blur(6px)' }}
|
|
onClick={e => { if (e.target === e.currentTarget) handleClose(); }}
|
|
>
|
|
<div
|
|
className="relative w-full max-w-xl rounded-2xl shadow-2xl"
|
|
style={{ background: card, border: `1px solid ${bdr}` }}
|
|
>
|
|
{/* Header */}
|
|
<div className="flex items-center justify-between px-8 pt-8 pb-6" style={{ borderBottom: `1px solid ${bdr}` }}>
|
|
<div>
|
|
<h3 className="font-display font-bold text-xl" style={{ color: text, letterSpacing: '-0.02em' }}>
|
|
Hablemos de tu proyecto
|
|
</h3>
|
|
<p className="text-sm mt-0.5" style={{ color: sub }}>
|
|
Te respondemos en menos de 24 h
|
|
</p>
|
|
</div>
|
|
<button
|
|
onClick={handleClose}
|
|
disabled={status === 'loading'}
|
|
className="flex h-8 w-8 items-center justify-center rounded-full transition-colors"
|
|
style={{ background: 'rgba(0,0,0,0.06)', color: sub, border: 'none', cursor: 'pointer' }}
|
|
aria-label="Cerrar"
|
|
>
|
|
<Icon icon="ph:x" width={16} />
|
|
</button>
|
|
</div>
|
|
|
|
{/* Success state */}
|
|
{status === 'success' ? (
|
|
<div className="flex flex-col items-center justify-center gap-4 px-8 py-14 text-center">
|
|
<Icon icon="ph:check-circle" width={48} style={{ color: '#22c55e' }} />
|
|
<h4 className="font-display font-bold text-lg" style={{ color: text }}>
|
|
¡Mensaje enviado!
|
|
</h4>
|
|
<p className="text-sm leading-relaxed" style={{ color: sub, maxWidth: '32ch' }}>
|
|
Recibimos tu solicitud. Un miembro del equipo te contactará pronto.
|
|
</p>
|
|
<button
|
|
onClick={handleClose}
|
|
className="mt-2 px-6 py-2.5 text-sm font-bold text-white rounded-xl transition hover:opacity-90"
|
|
style={{ background: '#3b82f6', border: 'none', cursor: 'pointer' }}
|
|
>
|
|
Cerrar
|
|
</button>
|
|
</div>
|
|
) : (
|
|
/* Form */
|
|
<form onSubmit={handleSubmit} className="px-8 py-7 flex flex-col gap-5">
|
|
<div className="grid gap-4 sm:grid-cols-2">
|
|
<div>
|
|
<label className="block text-xs font-semibold mb-1.5" style={{ color: sub, letterSpacing: '0.04em' }}>
|
|
Nombre *
|
|
</label>
|
|
<input
|
|
type="text"
|
|
placeholder="Tu nombre"
|
|
value={form.nombre}
|
|
onChange={set('nombre')}
|
|
required
|
|
style={inputStyle}
|
|
/>
|
|
</div>
|
|
<div>
|
|
<label className="block text-xs font-semibold mb-1.5" style={{ color: sub, letterSpacing: '0.04em' }}>
|
|
Empresa
|
|
</label>
|
|
<input
|
|
type="text"
|
|
placeholder="Nombre de tu empresa"
|
|
value={form.empresa}
|
|
onChange={set('empresa')}
|
|
style={inputStyle}
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="grid gap-4 sm:grid-cols-2">
|
|
<div>
|
|
<label className="block text-xs font-semibold mb-1.5" style={{ color: sub, letterSpacing: '0.04em' }}>
|
|
Correo electrónico *
|
|
</label>
|
|
<input
|
|
type="email"
|
|
placeholder="[email protected]"
|
|
value={form.email}
|
|
onChange={set('email')}
|
|
required
|
|
style={inputStyle}
|
|
/>
|
|
</div>
|
|
<div>
|
|
<label className="block text-xs font-semibold mb-1.5" style={{ color: sub, letterSpacing: '0.04em' }}>
|
|
Teléfono
|
|
</label>
|
|
<input
|
|
type="tel"
|
|
placeholder="+52 998 000 0000"
|
|
value={form.telefono}
|
|
onChange={set('telefono')}
|
|
style={inputStyle}
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<div>
|
|
<label className="block text-xs font-semibold mb-1.5" style={{ color: sub, letterSpacing: '0.04em' }}>
|
|
Cuéntanos tu proyecto
|
|
</label>
|
|
<textarea
|
|
rows={4}
|
|
placeholder="¿Qué sistema necesitas? ¿Cuál es el problema que quieres resolver?"
|
|
value={form.mensaje}
|
|
onChange={set('mensaje')}
|
|
style={{ ...inputStyle, resize: 'none' }}
|
|
/>
|
|
</div>
|
|
|
|
{status === 'error' && (
|
|
<div className="flex items-center gap-2 px-4 py-3 rounded-lg" style={{ background: 'rgba(239,68,68,0.08)', border: '1px solid rgba(239,68,68,0.2)' }}>
|
|
<Icon icon="ph:warning-circle" width={15} style={{ color: '#ef4444', flexShrink: 0 }} />
|
|
<p className="text-sm" style={{ color: '#ef4444' }}>
|
|
Hubo un error al enviar. Intenta de nuevo o escríbenos a contacto@rynext.com.mx
|
|
</p>
|
|
</div>
|
|
)}
|
|
|
|
<button
|
|
type="submit"
|
|
disabled={status === 'loading' || !form.nombre.trim() || !form.email.trim()}
|
|
className="inline-flex items-center justify-center gap-2 w-full py-3.5 text-sm font-bold text-white rounded-xl transition hover:opacity-90 disabled:opacity-40 disabled:cursor-not-allowed"
|
|
style={{ background: '#3b82f6', border: 'none', cursor: 'pointer' }}
|
|
>
|
|
{status === 'loading' ? (
|
|
<>
|
|
<Icon icon="ph:spinner-gap" width={16} className="animate-spin" />
|
|
Enviando...
|
|
</>
|
|
) : (
|
|
<>
|
|
<Icon icon="ph:paper-plane-tilt" width={15} />
|
|
Enviar mensaje
|
|
</>
|
|
)}
|
|
</button>
|
|
|
|
<p className="text-center text-xs" style={{ color: sub, opacity: 0.7 }}>
|
|
Sin spam. Solo te contactamos para hablar de tu proyecto.
|
|
</p>
|
|
</form>
|
|
)}
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|