Files
Portfolio/app/components/Contact.tsx
2026-01-22 20:55:07 +00:00

186 lines
5.0 KiB
TypeScript
Executable File

"use client";
import { useState, useEffect } from "react";
import styles from "./components.module.css";
import Script from "next/script";
export default function Contact() {
const [loading, setLoading] = useState(false);
const [status, setStatus] = useState<boolean | null>(null);
const [captchaToken, setCaptchaToken] = useState<string | null>(null);
const [pendingForm, setPendingForm] =
useState<HTMLFormElement | null>(null);
async function handleSubmit(e: React.FormEvent<HTMLFormElement>) {
e.preventDefault();
if (!captchaToken) {
setPendingForm(e.currentTarget);
// @ts-ignore
window.grecaptcha.execute();
return;
}
await sendForm(e.currentTarget);
}
async function sendForm(form: HTMLFormElement) {
setLoading(true);
setStatus(null);
const data = {
name: "Website Contact",
email: (form.elements.namedItem("from") as HTMLInputElement).value,
subject: (form.elements.namedItem("subject") as HTMLInputElement).value,
message: (form.elements.namedItem("message") as HTMLTextAreaElement).value,
token: captchaToken,
};
try {
const res = await fetch("/api/contact", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(data),
});
if (!res.ok) throw new Error();
setStatus(true);
form.reset();
} catch {
setStatus(false);
} finally {
setLoading(false);
setCaptchaToken(null);
// @ts-ignore
window.grecaptcha.reset();
}
}
useEffect(() => {
if (captchaToken && pendingForm) {
sendForm(pendingForm);
setPendingForm(null);
}
}, [captchaToken]);
useEffect(() => {
if (status === true) {
const timer = setTimeout(() => {
setStatus(null);
}, 3_000);
return () => clearTimeout(timer);
}
}, [status]);
useEffect(() => {
(window as any).onRecaptchaSuccess = (token: string) => {
setCaptchaToken(token);
};
}, []);
const icon =
status === null
? "/img/icons/send.png"
: status === true
? "/img/icons/check.png"
: "/img/icons/error.png";
return (
<div className={styles.cwindow}>
<div className={styles.content}>
<div className={styles.cbuttonContainer}>
<a href="/" style={{ backgroundColor: "#FE4A45" }} className={styles.ball} />
<div style={{ backgroundColor: "#FDBE05" }} className={styles.ball} />
<div style={{ backgroundColor: "#05D02C" }} className={styles.ball} />
<Script
src="https://www.google.com/recaptcha/api.js"
strategy="afterInteractive"
/>
<div
className="g-recaptcha"
data-sitekey={process.env.NEXT_PUBLIC_RECAPTCHA_SITE_KEY}
data-size="invisible"
data-callback="onRecaptchaSuccess"
/>
<button
type="submit"
form="contact-form"
disabled={loading || pendingForm !== null || status === true}
style={{
marginLeft: "2vh",
backgroundColor: "transparent",
border: "none",
cursor: "pointer",
height: "2vh",
opacity: loading ? 0.5 : 1,
}}
><img
src={icon}
style={{ height: "2.5vh", filter: "invert(1)" }}
alt="Send"
/>
</button>
</div>
<div className={styles.mContent}>
<div className={styles.form}>
<form
id="contact-form"
onSubmit={handleSubmit}
style={{ width: "100%", textAlign: "center" }}
>
<div style={{ display: "flex" }}>
<span>To:</span>
<input
type="email"
placeholder="contact@4l3ks.com"
disabled
/>
</div>
<div style={{ display: "flex" }}>
<span>Cc:</span>
<input type="email" disabled />
</div>
<div style={{ display: "flex" }}>
<span>Subject:</span>
<input
name="subject"
type="text"
required
/>
</div>
<div style={{ display: "flex" }}>
<span>From:</span>
<input
name="from"
type="email"
required
/>
</div>
<div style={{ display: "flex" }}>
<textarea
name="message"
required
placeholder=""
/>
</div>
</form>
</div>
</div>
</div>
</div>
);
}