import React, { useEffect, useRef, useState } from "react"; import { css, cx } from "@emotion/css"; import { setupCursorTracking, Toggle } from "../util"; import { ReactComponent as CloseIcon } from "../assets/cross.svg"; import { Button } from "./ButtonOrAnchor"; const containerStyle = css` width: 100%; height: 100%; position: fixed; top: 0; left: 0; z-index: 1000; background-color: rgba(from var(--card-bg) r g b / 0.9); transition: opacity 300ms; & main { position: absolute; top: 0; right: 0; width: 50rem; max-width: 90vw; height: 100%; background-color: var(--bg-colour); translate: 100% 0; transition: translate 300ms, opacity 300ms; display: grid; place-items: center; overflow-y: auto; & > .contact-container { display: flex; flex-direction: column; padding: 2rem; gap: 2rem; width: 100%; max-width: 35rem; & > .contact-header { display: flex; justify-content: space-between; align-items: center; & h3 { font-size: 1.5rem; font-weight: 700; } & .close-button { background: none; border: none; cursor: pointer; color: var(--text-subdued); &:hover, &:focus { color: var(--text-colour); } } } & form { display: flex; flex-direction: column; gap: 1rem; } & article { color: var(--text-subdued); margin-bottom: 1rem; display: flex; flex-direction: column; gap: 1.2rem; font-size: 0.9rem; } & .success { color: var(--text-success); } & .error { color: var(--text-error); } } } `; const inputStyle = css` width: 100%; padding: 0.8rem; border: 1px solid var(--table-border); background-color: var(--card-bg); color: var(--text-colour); font-size: 1rem; border-radius: 0.5rem; textarea& { height: 100%; max-height: 20vh; } &:disabled { opacity: 0.8; cursor: not-allowed; } `; const ContactForm: React.FC<{ toggle: Toggle; }> = ({ toggle }) => { const nameRef = useRef(null); const [sending, setSending] = useState(false); const [sent, setSent] = useState(false); useEffect(() => { if (nameRef.current && toggle.on) nameRef.current.focus(); if (!toggle.on) { setSent(false); setSending(false); setError(null); } // remember current page const href = window.location.href; // hijack back button and close the form instead const handleBack = (e: PopStateEvent) => { if (toggle.on) { e.preventDefault(); toggle.set(false); // not sure of a cleaner way to do this // this resets the history to the current page window.history.replaceState(null, "", href); } }; const handleEscape = (e: KeyboardEvent) => { if (e.key === "Escape") toggle.set(false); }; window.addEventListener("keydown", handleEscape); window.addEventListener("popstate", handleBack); return () => { window.removeEventListener("keydown", handleEscape); window.removeEventListener("popstate", handleBack); }; }, [toggle.on]); const [name, setName] = useState(""); const [email, setEmail] = useState(""); const [message, setMessage] = useState(""); const [error, setError] = useState(null); const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); if (!name || !email || !message) { return setError("Please fill in all fields."); } setError(null); setSending(true); try { await fetch("https://api.feathers.studio/send", { method: "POST", body: JSON.stringify({ type: "website-contact", name, email, message, }), }); setName(""); setEmail(""); setMessage(""); setSending(false); setSent(true); // reset if the form was closed before the request was sent if (!toggle.on) setSent(false); } catch (e) { console.error(e); setError( "Failed to send message. Try again later, or notify me via email or Telegram, thanks!", ); setSending(false); setSent(false); } }; const disabled = sending || sent; return (
toggle.set(false)} className={cx( containerStyle, css` opacity: ${toggle.on ? 1 : 0}; pointer-events: ${toggle.on ? "auto" : "none"}; `, )}>
e.stopPropagation()} style={{ translate: toggle.on ? "0 0" : "100% 0", }}>

Let's talk!

setName(e.target.value)} className={inputStyle} /> setEmail(e.target.value)} className={inputStyle} />