convert-next.js
This commit is contained in:
268
src/components/GetInTouch.tsx
Normal file
268
src/components/GetInTouch.tsx
Normal file
@@ -0,0 +1,268 @@
|
||||
'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 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: 'Manrope, 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: 900,
|
||||
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: 'Manrope, 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: 'Manrope, sans-serif', marginBottom: 6 }}>{d.label}</div>
|
||||
<div style={{ fontSize: 15, lineHeight: 1.6, color: 'rgba(255,255,255,0.7)', fontFamily: 'Manrope, 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: 'Manrope, 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: 'Manrope, 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: 'Manrope, 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: 'Manrope, 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: 'Manrope, 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; }
|
||||
`}</style>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
const labelStyle: React.CSSProperties = {
|
||||
display: 'flex', flexDirection: 'column', gap: 8,
|
||||
fontSize: 13, fontWeight: 600, color: 'rgba(255,255,255,0.55)',
|
||||
fontFamily: 'Manrope, 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: 'Manrope, sans-serif',
|
||||
transition: 'border-color 0.2s ease, box-shadow 0.2s ease',
|
||||
};
|
||||
Reference in New Issue
Block a user