cambios
This commit is contained in:
1 parent
794a659369
commit
5f99b65611
349 files changed
+32252
-2180
No files matched your search
@@ -0,0 +1,192 @@
|
||||
'use client';
|
||||
import { useState } from 'react';
|
||||
import { motion } from 'framer-motion';
|
||||
import { Icon } from '@iconify/react';
|
||||
import type { ThemeProps } 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 ContactSection = ({ isNavyBg }: ThemeProps) => {
|
||||
const [form, setForm] = useState(initialForm);
|
||||
const [status, setStatus] = useState<Status>('idle');
|
||||
|
||||
const text = isNavyBg ? 'var(--foreground)' : '#0a0a0a';
|
||||
const sub = isNavyBg ? 'rgba(255,255,255,0.45)' : '#6a6866';
|
||||
const border = isNavyBg ? 'rgba(255,255,255,0.08)' : '#dddbd5';
|
||||
const card = isNavyBg ? 'rgba(255,255,255,0.03)' : '#ffffff';
|
||||
const inputStyle: React.CSSProperties = {
|
||||
width: '100%', borderRadius: 12, padding: '12px 14px',
|
||||
fontSize: 14, fontFamily: 'inherit', outline: 'none',
|
||||
transition: 'border-color 150ms',
|
||||
...(isNavyBg
|
||||
? { background: 'rgba(255,255,255,0.05)', border: '1px solid rgba(255,255,255,0.1)', color: '#fff' }
|
||||
: { background: '#f9f9f7', border: `1px solid ${border}`, color: '#0a0a0a' }),
|
||||
};
|
||||
|
||||
const set = (k: keyof typeof form) =>
|
||||
(e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) =>
|
||||
setForm(f => ({ ...f, [k]: e.target.value }));
|
||||
|
||||
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();
|
||||
setStatus('success');
|
||||
setForm(initialForm);
|
||||
} catch {
|
||||
setStatus('error');
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<section
|
||||
id="contacto"
|
||||
aria-labelledby="contact-heading"
|
||||
className="relative px-8 py-20 md:px-14 transition-colors duration-500"
|
||||
>
|
||||
<div
|
||||
aria-hidden="true"
|
||||
className="pointer-events-none absolute inset-0 bg-dot-grid text-border opacity-40"
|
||||
/>
|
||||
<div
|
||||
aria-hidden="true"
|
||||
className="pointer-events-none absolute -right-32 -top-24 h-[24rem] w-[24rem] rounded-full blur-[110px]"
|
||||
style={{ background: 'rgba(139,92,246,0.12)' }}
|
||||
/>
|
||||
<div className="relative mx-auto max-w-7xl grid gap-16 lg:grid-cols-[1fr_1.2fr] lg:items-start">
|
||||
|
||||
{/* Left — copy */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 24 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ duration: 0.6, ease: [0.22, 1, 0.36, 1] }}
|
||||
>
|
||||
<h2 id="contact-heading" className="text-headline mb-6" style={{ color: text, maxWidth: '20ch' }}>
|
||||
Hablemos de tu proyecto.
|
||||
</h2>
|
||||
<p className="text-base leading-relaxed mb-10" style={{ color: sub, maxWidth: '38ch' }}>
|
||||
Agenda una sesión técnica sin costo. Analizamos tu operación, identificamos los cuellos de botella y presentamos una ruta concreta de implementación.
|
||||
</p>
|
||||
|
||||
<ul className="flex flex-col gap-4">
|
||||
{[
|
||||
{ label: 'Respuesta en menos de 24 h' },
|
||||
{ label: 'Sin compromisos ni presupuesto automático' },
|
||||
{ label: 'Sesión técnica, no comercial' },
|
||||
].map((item) => (
|
||||
<li key={item.label} className="flex items-center gap-3 text-sm" style={{ color: sub }}>
|
||||
<span style={{ width: 6, height: 6, borderRadius: '50%', background: '#3b82f6', flexShrink: 0, display: 'inline-block' }} />
|
||||
{item.label}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</motion.div>
|
||||
|
||||
{/* Right — form */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 24 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ duration: 0.6, delay: 0.1, ease: [0.22, 1, 0.36, 1] }}
|
||||
style={{ background: card, border: `1px solid ${border}`, borderRadius: 24, padding: '40px 36px' }}
|
||||
>
|
||||
{status === 'success' ? (
|
||||
<div className="flex flex-col items-center justify-center gap-4 py-10 text-center">
|
||||
<Icon icon="ph:check-circle" width={48} style={{ color: "#22c55e" }} />
|
||||
<h3 className="font-display font-bold text-lg" style={{ color: text }}>¡Mensaje enviado!</h3>
|
||||
<p className="text-sm leading-relaxed" style={{ color: sub, maxWidth: '32ch' }}>
|
||||
Recibimos tu solicitud. Un miembro del equipo te contactará pronto.
|
||||
</p>
|
||||
<button
|
||||
onClick={() => setStatus('idle')}
|
||||
className="mt-2 px-6 py-2.5 text-sm font-bold text-white rounded-lg transition hover:opacity-90"
|
||||
style={{ background: '#3b82f6', border: 'none', cursor: 'pointer' }}
|
||||
>
|
||||
Enviar otro mensaje
|
||||
</button>
|
||||
</div>
|
||||
) : (
|
||||
<form onSubmit={handleSubmit} className="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-lg 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.6 }}>
|
||||
Sin spam. Solo te contactamos para hablar de tu proyecto.
|
||||
</p>
|
||||
</form>
|
||||
)}
|
||||
</motion.div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
Reference in new issue
Block a user