186 lines
5.0 KiB
TypeScript
Executable File
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>
|
|
);
|
|
}
|