Files
loyalyweb/src/components/GetInTouch.tsx

272 lines
14 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'use client';
import { useState, FormEvent } from 'react';
type FormState = 'idle' | 'sending' | 'success' | 'error';
const DETAILS = [
{
label: 'Visit us',
value: <>No.424, Red Rose Towers,<br />DB Road, RS Puram,<br />Coimbatore 641002</>,
icon: (
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round">
<path d="M21 10c0 7-9 13-9 13s-9-6-9-13a9 9 0 0118 0z" /><circle cx="12" cy="10" r="3" />
</svg>
),
},
{
label: 'Email us',
value: <a href="mailto:loyalydigital@gmail.com" style={{ color: '#fff', textDecoration: 'none', fontWeight: 600 }}>loyalydigital@gmail.com</a>,
icon: (
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round">
<rect x="2" y="4" width="20" height="16" rx="2" /><path d="m22 7-10 5L2 7" />
</svg>
),
},
];
const SOCIALS = [
{ label: 'LinkedIn', href: 'https://www.linkedin.com/company/loyaly-ai', icon: <svg width="17" height="17" viewBox="0 0 24 24" fill="currentColor"><path d="M19.7 3H4.3C3.58 3 3 3.58 3 4.3v15.4C3 20.42 3.58 21 4.3 21h15.4c.72 0 1.3-.58 1.3-1.3V4.3C21 3.58 20.42 3 19.7 3zM8.34 18.34H5.67V9.75h2.67v8.59zM7 8.57a1.55 1.55 0 1 1 0-3.1 1.55 1.55 0 0 1 0 3.1zm11.34 9.77h-2.67v-4.18c0-1-.02-2.28-1.39-2.28-1.39 0-1.6 1.09-1.6 2.21v4.25h-2.67V9.75h2.56v1.17h.04c.36-.68 1.23-1.39 2.53-1.39 2.7 0 3.2 1.78 3.2 4.09v4.72z" /></svg> },
{ label: 'X / Twitter', href: 'https://twitter.com/loyalyai', icon: <svg width="17" height="17" viewBox="0 0 24 24" fill="currentColor"><path d="M13.98 10.62 20.54 3h-1.55l-5.7 6.62L8.74 3H3.5l6.88 10.01L3.5 21h1.55l6.01-6.99L15.87 21h5.24l-7.13-10.38z" /></svg> },
{ label: 'Instagram', href: 'https://instagram.com/loyalyai', icon: <svg width="17" height="17" viewBox="0 0 24 24" fill="currentColor"><path d="M12 2.16c3.2 0 3.58.01 4.85.07 3.25.15 4.77 1.69 4.92 4.92.06 1.27.07 1.65.07 4.85 0 3.2-.01 3.58-.07 4.85-.15 3.23-1.66 4.77-4.92 4.92-1.27.06-1.64.07-4.85.07-3.2 0-3.58-.01-4.85-.07-3.26-.15-4.77-1.7-4.92-4.92-.06-1.27-.07-1.64-.07-4.85 0-3.2.01-3.58.07-4.85.15-3.23 1.66-4.77 4.92-4.92C8.42 2.17 8.8 2.16 12 2.16zm0 1.62c-3.14 0-3.51.01-4.75.07-3.17.14-4.56 1.55-4.7 4.7-.06 1.24-.07 1.6-.07 4.75s.01 3.51.07 4.75c.14 3.15 1.53 4.56 4.7 4.7 1.24.06 1.61.07 4.75.07s3.51-.01 4.75-.07c3.17-.14 4.56-1.55 4.7-4.7.06-1.24.07-1.6.07-4.75s-.01-3.51-.07-4.75c-.14-3.15-1.53-4.56-4.7-4.7-1.24-.06-1.61-.07-4.75-.07zm0 4.32a5.74 5.74 0 100 11.48 5.74 5.74 0 000-11.48zm0 9.46a3.72 3.72 0 110-7.44 3.72 3.72 0 010 7.44zm5.96-9.65a1.34 1.34 0 11-2.68 0 1.34 1.34 0 012.68 0z" /></svg> },
];
export default function GetInTouch() {
const [formState, setFormState] = useState<FormState>('idle');
const [errorMessage, setErrorMessage] = useState('');
const handleSubmit = async (e: FormEvent<HTMLFormElement>) => {
e.preventDefault();
setFormState('sending');
setErrorMessage('');
const form = e.currentTarget;
const data = {
name: (form.elements.namedItem('name') as HTMLInputElement).value,
email: (form.elements.namedItem('email') as HTMLInputElement).value,
subject: (form.elements.namedItem('subject') as HTMLInputElement).value,
message: (form.elements.namedItem('message') as HTMLTextAreaElement).value,
};
try {
const res = await fetch('/api/contact', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data),
});
if (!res.ok) {
const body = await res.json().catch(() => ({}));
throw new Error(body.message || `Request failed: ${res.status}`);
}
setFormState('success');
form.reset();
} catch (err) {
setFormState('error');
setErrorMessage(err instanceof Error ? err.message : 'Something went wrong. Please try again.');
}
};
return (
<section
id="contact"
aria-label="Contact us"
style={{
position: 'relative',
margin: '0 20px 0',
borderRadius: '24px 24px 0 0',
overflow: 'hidden',
background: '#07070d',
}}
>
<div className="git-grid" style={{
position: 'relative', zIndex: 2,
maxWidth: 1200, margin: '0 auto',
padding: 'clamp(56px, 8vh, 100px) clamp(28px, 5vw, 72px)',
display: 'grid',
gridTemplateColumns: 'repeat(auto-fit, minmax(320px, 1fr))',
gap: 'clamp(40px, 5vw, 80px)',
alignItems: 'start',
}}>
{/* ── Left — info ── */}
<div>
<div style={{
fontSize: 11, fontWeight: 700, letterSpacing: '0.2em', textTransform: 'uppercase',
color: '#f4be28', fontFamily: 'Sora, sans-serif', marginBottom: 24,
display: 'flex', alignItems: 'center', gap: 12,
}}>
<span style={{ width: 28, height: 1, background: '#f4be28', display: 'block' }} />Get in Touch
</div>
<h2 style={{
margin: '0 0 24px',
fontSize: 'clamp(2rem, 4vw, 3.4rem)', fontWeight: 800,
lineHeight: 0.98, letterSpacing: '-0.04em',
color: '#fff', fontFamily: 'Sora, sans-serif',
}}>
Let's talk<br /><span style={{ color: '#f4be28' }}>strategy.</span>
</h2>
<p style={{
margin: '0 0 44px', fontSize: 16, lineHeight: 1.8,
color: 'rgba(255,255,255,0.42)', fontFamily: 'Sora, sans-serif', maxWidth: 380,
}}>
Whether you're exploring AI possibilities or ready to deploy, our team will help you find the right solution for your business.
</p>
{/* Detail cards */}
<div style={{ display: 'flex', flexDirection: 'column', gap: 14, marginBottom: 36 }}>
{DETAILS.map((d) => (
<div key={d.label} style={{
display: 'flex', gap: 16, alignItems: 'flex-start',
padding: '18px 20px', borderRadius: 16,
background: 'rgba(255,255,255,0.03)', border: '1px solid rgba(255,255,255,0.07)',
}}>
<div style={{
width: 40, height: 40, borderRadius: 11, flexShrink: 0,
display: 'flex', alignItems: 'center', justifyContent: 'center',
background: 'rgba(244,190,40,0.1)', border: '1px solid rgba(244,190,40,0.22)',
color: '#f4be28',
}}>{d.icon}</div>
<div>
<div style={{ fontSize: 11, fontWeight: 700, letterSpacing: '0.12em', textTransform: 'uppercase', color: 'rgba(255,255,255,0.4)', fontFamily: 'Sora, sans-serif', marginBottom: 6 }}>{d.label}</div>
<div style={{ fontSize: 15, lineHeight: 1.6, color: 'rgba(255,255,255,0.7)', fontFamily: 'Sora, sans-serif' }}>{d.value}</div>
</div>
</div>
))}
</div>
{/* Socials */}
<div style={{ display: 'flex', alignItems: 'center', gap: 12 }}>
<span style={{ fontSize: 11, fontWeight: 700, letterSpacing: '0.12em', textTransform: 'uppercase', color: 'rgba(255,255,255,0.35)', fontFamily: 'Sora, sans-serif' }}>Follow</span>
{SOCIALS.map((s) => (
<a key={s.label} href={s.href} target="_blank" rel="noopener noreferrer" aria-label={s.label} className="git-social">
{s.icon}
</a>
))}
</div>
</div>
{/* ── Right — form ── */}
<div style={{
background: 'rgba(255,255,255,0.03)',
border: '1px solid rgba(255,255,255,0.08)',
borderRadius: 24,
padding: 'clamp(28px, 3vw, 44px)',
backdropFilter: 'blur(20px)',
WebkitBackdropFilter: 'blur(20px)',
boxShadow: 'inset 0 1px 0 rgba(255,255,255,0.05)',
}}>
{formState === 'success' ? (
<div role="status" aria-live="polite" style={{ textAlign: 'center', padding: '40px 0' }}>
<div style={{
width: 64, height: 64, borderRadius: '50%', margin: '0 auto 24px',
display: 'flex', alignItems: 'center', justifyContent: 'center',
background: 'rgba(74,222,128,0.12)', border: '1px solid rgba(74,222,128,0.3)',
}}>
<svg width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="#4ade80" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round"><path d="M20 6 9 17l-5-5" /></svg>
</div>
<h3 style={{ margin: '0 0 10px', fontSize: 22, fontWeight: 800, color: '#fff', fontFamily: 'Sora, sans-serif', letterSpacing: '-0.02em' }}>Message sent!</h3>
<p style={{ margin: 0, color: 'rgba(255,255,255,0.45)', fontFamily: 'Sora, sans-serif' }}>We'll get back to you within one business day.</p>
<button onClick={() => setFormState('idle')} style={{
marginTop: 28, padding: '12px 28px', borderRadius: 100,
background: 'transparent', border: '1px solid rgba(255,255,255,0.2)',
color: '#fff', fontWeight: 600, fontSize: 14, cursor: 'pointer',
fontFamily: 'Sora, sans-serif',
}}>Send another message</button>
</div>
) : (
<form onSubmit={handleSubmit} noValidate>
<h3 style={{ margin: '0 0 28px', fontSize: 22, fontWeight: 800, color: '#fff', fontFamily: 'Sora, sans-serif', letterSpacing: '-0.02em' }}>
Send us a message
</h3>
{formState === 'error' && (
<div role="alert" style={{
marginBottom: 20, padding: '12px 16px',
background: 'rgba(248,113,113,0.1)', border: '1px solid rgba(248,113,113,0.3)',
color: '#fca5a5', borderRadius: 12, fontSize: 14, fontFamily: 'Sora, sans-serif',
}}>{errorMessage}</div>
)}
<div style={{ display: 'grid', gap: 18 }}>
<label style={labelStyle}>
Full Name
<input type="text" name="name" required placeholder="Jane Smith" className="git-input" style={inputStyle} />
</label>
<label style={labelStyle}>
Email Address <span aria-hidden="true" style={{ color: '#f4be28' }}>*</span>
<input type="email" name="email" required placeholder="jane@company.com" className="git-input" style={inputStyle} />
</label>
<label style={labelStyle}>
Subject
<input type="text" name="subject" placeholder="How can we help?" className="git-input" style={inputStyle} />
</label>
<label style={labelStyle}>
Message
<textarea name="message" rows={5} placeholder="Tell us about your business and what you're trying to achieve" className="git-input" style={{ ...inputStyle, resize: 'vertical', minHeight: 120 }} />
</label>
<button type="submit" disabled={formState === 'sending'} style={{
marginTop: 4, padding: '16px 28px', borderRadius: 100, border: 'none',
background: formState === 'sending' ? 'rgba(244,190,40,0.6)' : '#f4be28',
color: '#111', fontWeight: 800, fontSize: 15, fontFamily: 'Sora, sans-serif',
cursor: formState === 'sending' ? 'wait' : 'pointer',
display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 8,
boxShadow: '0 12px 36px rgba(244,190,40,0.25)',
transition: 'transform 0.25s ease',
}}>
{formState === 'sending' ? (
<>
<span style={{ display: 'inline-block', width: 16, height: 16, border: '2px solid #111', borderTopColor: 'transparent', borderRadius: '50%', animation: 'gitspin 0.7s linear infinite' }} aria-hidden="true" />
Sending…
</>
) : (
<>Send Message <svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round"><path d="M5 12h14M13 6l6 6-6 6" /></svg></>
)}
</button>
</div>
</form>
)}
</div>
</div>
<style>{`
@keyframes gitspin { to { transform: rotate(360deg); } }
.git-input::placeholder { color: rgba(255,255,255,0.3); }
.git-input:focus { border-color: rgba(244,190,40,0.6) !important; box-shadow: 0 0 0 3px rgba(244,190,40,0.12) !important; }
.git-social {
width: 38px; height: 38px; border-radius: 10px;
display: flex; align-items: center; justify-content: center;
background: rgba(255,255,255,0.05); border: 1px solid rgba(255,255,255,0.08);
color: rgba(255,255,255,0.5); transition: all 0.2s ease;
}
.git-social:hover { background: rgba(244,190,40,0.12); border-color: rgba(244,190,40,0.3); color: #f4be28; }
@media (max-width: 420px) {
.git-grid { grid-template-columns: 1fr !important; }
}
`}</style>
</section>
);
}
const labelStyle: React.CSSProperties = {
display: 'flex', flexDirection: 'column', gap: 8,
fontSize: 13, fontWeight: 600, color: 'rgba(255,255,255,0.55)',
fontFamily: 'Sora, sans-serif',
};
const inputStyle: React.CSSProperties = {
padding: '13px 16px',
border: '1px solid rgba(255,255,255,0.1)',
borderRadius: 12,
fontSize: 15,
background: 'rgba(255,255,255,0.04)',
color: '#fff',
outline: 'none',
width: '100%',
boxSizing: 'border-box',
fontFamily: 'Sora, sans-serif',
transition: 'border-color 0.2s ease, box-shadow 0.2s ease',
};