1
mirror of https://github.com/jakejarvis/jarv.is.git synced 2025-06-27 17:45:41 -04:00
Files
jarv.is/app/contact/form.tsx
2025-05-02 22:04:26 -04:00

146 lines
5.0 KiB
TypeScript

"use client";
import { env } from "@/lib/env";
import { useActionState, useState } from "react";
import Turnstile from "react-turnstile";
import { SendIcon, Loader2Icon, CheckIcon, XIcon } from "lucide-react";
import Link from "@/components/link";
import Input from "@/components/input";
import Textarea from "@/components/textarea";
import Button from "@/components/button";
import { cn } from "@/lib/utils";
import { send, type ContactState, type ContactInput } from "./action";
const ContactForm = () => {
const [formState, formAction, pending] = useActionState<ContactState, FormData>(send, {
success: false,
message: "",
});
// keep track of input so we can repopulate the fields if the form fails
const [formFields, setFormFields] = useState<Partial<ContactInput>>({
name: "",
email: "",
message: "",
});
return (
<form action={formAction} className="my-6 space-y-4">
<div>
<Input
type="text"
name="name"
placeholder="Name"
value={formFields.name}
onChange={(e) => {
setFormFields({ ...formFields, name: e.target.value });
}}
disabled={pending || formState.success}
className={cn(!pending && formState.errors?.name && "border-destructive")}
/>
{!pending && formState.errors?.name && (
<span className="text-destructive text-[0.8rem] font-semibold">{formState.errors.name[0]}</span>
)}
</div>
<div>
<Input
type="email"
name="email"
placeholder="Email"
inputMode="email"
value={formFields.email}
onChange={(e) => {
setFormFields({ ...formFields, email: e.target.value });
}}
disabled={pending || formState.success}
className={cn(!pending && formState.errors?.email && "border-destructive")}
/>
{!pending && formState.errors?.email && (
<span className="text-destructive text-[0.8rem] font-semibold">{formState.errors.email[0]}</span>
)}
</div>
<div>
<Textarea
name="message"
placeholder="Write something..."
value={formFields.message}
onChange={(e) => {
setFormFields({ ...formFields, message: e.target.value });
}}
disabled={pending || formState.success}
className={cn("min-h-24", !pending && formState.errors?.message && "border-destructive")}
/>
{!pending && formState.errors?.message && (
<span className="text-destructive text-[0.8rem] font-semibold">{formState.errors.message[0]}</span>
)}
<div className="text-muted-foreground mt-2 text-[0.8rem] leading-relaxed">
<svg
xmlns="http://www.w3.org/2000/svg"
fill="currentColor"
stroke="currentColor"
strokeWidth="0"
viewBox="0 0 24 24"
className="mr-1 inline-block size-[16px] align-text-top"
>
<path d="M22.27 19.385H1.73A1.73 1.73 0 010 17.655V6.345a1.73 1.73 0 011.73-1.73h20.54A1.73 1.73 0 0124 6.345v11.308a1.73 1.73 0 01-1.73 1.731zM5.769 15.923v-4.5l2.308 2.885 2.307-2.885v4.5h2.308V8.078h-2.308l-2.307 2.885-2.308-2.885H3.46v7.847zM21.232 12h-2.309V8.077h-2.307V12h-2.308l3.461 4.039z" />
</svg>{" "}
Basic{" "}
<Link href="https://commonmark.org/help/" title="Markdown reference sheet" className="font-semibold">
Markdown syntax
</Link>{" "}
is allowed here, e.g.: <strong>**bold**</strong>, <em>_italics_</em>, [
<Link href="https://jarv.is" className="hover:no-underline">
links
</Link>
](https://jarv.is), and <code>`code`</code>.
</div>
</div>
<div>
<div className="my-4">
<Turnstile sitekey={env.NEXT_PUBLIC_TURNSTILE_SITE_KEY} fixedSize />
</div>
{!pending && formState.errors?.["cf-turnstile-response"] && (
<span className="text-destructive text-[0.8rem] font-semibold">
{formState.errors["cf-turnstile-response"][0]}
</span>
)}
</div>
<div className="mt-[0.6em] flex min-h-[3.75em] items-center">
{!formState.success && (
<Button type="submit" disabled={pending}>
{pending ? (
<>
<Loader2Icon className="animate-spin" /> Sending...
</>
) : (
<>
<SendIcon /> Send
</>
)}
</Button>
)}
{!pending && formState.message && (
<div
className={cn("ml-4 text-[0.9rem] font-semibold", formState.success ? "text-success" : "text-destructive")}
>
{formState.success ? (
<CheckIcon className="inline size-[16px]" />
) : (
<XIcon className="inline size-[16px]" />
)}{" "}
<span className="ml-[2px]">{formState.message}</span>
</div>
)}
</div>
</form>
);
};
export default ContactForm;