first commit
This commit is contained in:
185
app/components/Contact.tsx
Executable file
185
app/components/Contact.tsx
Executable file
@@ -0,0 +1,185 @@
|
||||
"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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user