cloudlfare

This commit is contained in:
will
2026-04-02 23:29:59 +01:00
parent 746e51370d
commit c76f66c0fd
6 changed files with 154 additions and 16 deletions

View File

@@ -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="Whats 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>