cloudlfare
This commit is contained in:
@@ -31,6 +31,7 @@ export default function Contact() {
|
||||
const [turnstileToken, setTurnstileToken] = useState('');
|
||||
const widgetIdRef = useRef<string | null>(null);
|
||||
const turnstileRef = useRef<HTMLDivElement | null>(null);
|
||||
const startedAtRef = useRef<number>(Date.now());
|
||||
|
||||
useEffect(() => {
|
||||
if (!TURNSTILE_SITE_KEY) {
|
||||
@@ -48,6 +49,7 @@ export default function Contact() {
|
||||
theme: 'dark',
|
||||
callback: token => {
|
||||
setTurnstileToken(token);
|
||||
setErrorMessage('');
|
||||
},
|
||||
'expired-callback': () => {
|
||||
setTurnstileToken('');
|
||||
@@ -85,7 +87,7 @@ export default function Contact() {
|
||||
name: String(fd.get('name') || '').trim(),
|
||||
email: String(fd.get('email') || '').trim(),
|
||||
message: String(fd.get('message') || '').trim(),
|
||||
company: String(fd.get('company') || '').trim(), // honeypot
|
||||
company: String(fd.get('company') || '').trim(),
|
||||
turnstileToken,
|
||||
formStartedAt: String(fd.get('formStartedAt') || ''),
|
||||
};
|
||||
@@ -117,7 +119,9 @@ export default function Contact() {
|
||||
|
||||
setStatus('sent');
|
||||
form.reset();
|
||||
startedAtRef.current = Date.now();
|
||||
setTurnstileToken('');
|
||||
|
||||
if (window.turnstile && widgetIdRef.current) {
|
||||
window.turnstile.reset(widgetIdRef.current);
|
||||
}
|
||||
@@ -126,10 +130,11 @@ export default function Contact() {
|
||||
setErrorMessage(
|
||||
err instanceof Error ? err.message : 'Something went wrong.',
|
||||
);
|
||||
} finally {
|
||||
if (status !== 'sent') {
|
||||
setStatus(prev => (prev === 'sent' ? 'sent' : 'idle'));
|
||||
|
||||
if (window.turnstile && widgetIdRef.current) {
|
||||
window.turnstile.reset(widgetIdRef.current);
|
||||
}
|
||||
setTurnstileToken('');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -141,6 +146,7 @@ export default function Contact() {
|
||||
>
|
||||
<div className="max-w-5xl mx-auto">
|
||||
<span className="label">Get in touch</span>
|
||||
|
||||
<h2
|
||||
id="contact-heading"
|
||||
className="text-[clamp(1.8rem,4vw,2.5rem)] font-black tracking-tight text-white mb-3"
|
||||
@@ -153,17 +159,84 @@ export default function Contact() {
|
||||
</p>
|
||||
|
||||
<div className="grid md:grid-cols-[280px_1fr] gap-6">
|
||||
{/* Left side contact cards */}
|
||||
<div className="flex flex-col gap-3">
|
||||
{/* email + gitea blocks unchanged */}
|
||||
{/* ... */}
|
||||
<a
|
||||
href={`mailto:${SITE.email}`}
|
||||
className="glass rounded-xl p-4 flex items-center gap-3 group hover:border-white/[0.14]
|
||||
hover:-translate-x-[-4px] transition-all duration-150"
|
||||
>
|
||||
<div
|
||||
className="w-8 h-8 rounded-lg bg-white/[0.05] flex items-center justify-center flex-shrink-0"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<svg
|
||||
width="14"
|
||||
height="14"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="rgba(255,255,255,0.5)"
|
||||
strokeWidth="1.8"
|
||||
>
|
||||
<rect x="2" y="4" width="20" height="16" rx="2" />
|
||||
<path d="m22 7-8.97 5.7a1.94 1.94 0 0 1-2.06 0L2 7" />
|
||||
</svg>
|
||||
</div>
|
||||
<div className="min-w-0">
|
||||
<p className="text-[10px] text-white/25 uppercase tracking-wider">
|
||||
Email
|
||||
</p>
|
||||
<p className="text-xs font-medium text-white/55 truncate group-hover:text-white/80 transition-colors">
|
||||
{SITE.email}
|
||||
</p>
|
||||
</div>
|
||||
</a>
|
||||
|
||||
<a
|
||||
href={`${GITEA_URL}/${GITEA_USERNAME}`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="glass rounded-xl p-4 flex items-center gap-3 group hover:border-white/[0.14]
|
||||
hover:translate-x-1 transition-all duration-150"
|
||||
>
|
||||
<div
|
||||
className="w-8 h-8 rounded-lg bg-white/[0.05] flex items-center justify-center flex-shrink-0"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<svg
|
||||
width="14"
|
||||
height="14"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="rgba(255,255,255,0.5)"
|
||||
strokeWidth="1.8"
|
||||
>
|
||||
<path d="M15 22v-4a4.8 4.8 0 0 0-1-3.2c3 0 6-2 6-5.5.08-1.25-.27-2.48-1-3.5.28-1.15.28-2.35 0-3.5 0 0-1 0-3 1.5-2.64-.5-5.36-.5-8 0C6 2 5 2 5 2c-.3 1.15-.3 2.35 0 3.5A5.403 5.403 0 0 0 4 9c0 3.5 3 5.5 6 5.5-.39.49-.68 1.05-.85 1.65-.17.6-.22 1.23-.15 1.85v4" />
|
||||
<path d="M9 18c-4.51 2-5-2-7-2" />
|
||||
</svg>
|
||||
</div>
|
||||
<div className="min-w-0">
|
||||
<p className="text-[10px] text-white/25 uppercase tracking-wider">
|
||||
Gitea
|
||||
</p>
|
||||
<p className="text-xs font-medium text-white/55 truncate group-hover:text-white/80 transition-colors">
|
||||
{GITEA_URL.replace(/^https?:\/\//, '')}/{GITEA_USERNAME}
|
||||
</p>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
{/* Form */}
|
||||
<form
|
||||
onSubmit={handleSubmit}
|
||||
className="glass rounded-xl p-6 flex flex-col gap-4"
|
||||
className="glass rounded-xl p-6 flex flex-col gap-4 relative"
|
||||
noValidate
|
||||
>
|
||||
<input type="hidden" name="formStartedAt" value={String(Date.now())} />
|
||||
<input
|
||||
type="hidden"
|
||||
name="formStartedAt"
|
||||
value={String(startedAtRef.current)}
|
||||
/>
|
||||
|
||||
{/* Honeypot */}
|
||||
<div
|
||||
@@ -180,8 +253,54 @@ export default function Contact() {
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* fields unchanged */}
|
||||
{/* ... */}
|
||||
<div className="grid sm:grid-cols-2 gap-4">
|
||||
<label className="flex flex-col gap-1.5">
|
||||
<span className="text-[10px] font-semibold uppercase tracking-wider text-white/30">
|
||||
Name
|
||||
</span>
|
||||
<input
|
||||
name="name"
|
||||
type="text"
|
||||
required
|
||||
placeholder="Your name"
|
||||
autoComplete="name"
|
||||
className="bg-white/[0.03] border border-white/[0.08] rounded-lg px-3 py-2.5 text-sm
|
||||
text-white placeholder-white/20 outline-none transition-colors
|
||||
focus:border-white/[0.2] focus:ring-1 focus:ring-white/[0.1]"
|
||||
/>
|
||||
</label>
|
||||
|
||||
<label className="flex flex-col gap-1.5">
|
||||
<span className="text-[10px] font-semibold uppercase tracking-wider text-white/30">
|
||||
Email
|
||||
</span>
|
||||
<input
|
||||
name="email"
|
||||
type="email"
|
||||
required
|
||||
placeholder="you@example.com"
|
||||
autoComplete="email"
|
||||
className="bg-white/[0.03] border border-white/[0.08] rounded-lg px-3 py-2.5 text-sm
|
||||
text-white placeholder-white/20 outline-none transition-colors
|
||||
focus:border-white/[0.2] focus:ring-1 focus:ring-white/[0.1]"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<label className="flex flex-col gap-1.5">
|
||||
<span className="text-[10px] font-semibold uppercase tracking-wider text-white/30">
|
||||
Message
|
||||
</span>
|
||||
<textarea
|
||||
name="message"
|
||||
required
|
||||
placeholder="What’s on your mind?"
|
||||
rows={5}
|
||||
className="bg-white/[0.03] border border-white/[0.08] rounded-lg px-3 py-2.5 text-sm
|
||||
text-white placeholder-white/20 outline-none resize-none transition-colors
|
||||
focus:border-white/[0.2] focus:ring-1 focus:ring-white/[0.1]"
|
||||
/>
|
||||
</label>
|
||||
|
||||
<div>
|
||||
{TURNSTILE_SITE_KEY ? (
|
||||
@@ -199,7 +318,11 @@ export default function Contact() {
|
||||
disabled={status === 'loading'}
|
||||
className="btn btn-primary disabled:opacity-60 disabled:cursor-not-allowed"
|
||||
>
|
||||
{status === 'loading' ? 'Sending…' : status === 'sent' ? 'Message sent' : 'Send message'}
|
||||
{status === 'loading'
|
||||
? 'Sending…'
|
||||
: status === 'sent'
|
||||
? 'Message sent'
|
||||
: 'Send message'}
|
||||
</button>
|
||||
|
||||
{status === 'sent' && (
|
||||
@@ -208,7 +331,7 @@ export default function Contact() {
|
||||
</p>
|
||||
)}
|
||||
|
||||
{errorMessage && (
|
||||
{status === 'error' && errorMessage && (
|
||||
<p className="text-xs text-red-300/80">{errorMessage}</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user