Skip to content

Commit

Permalink
Refactor contact form handling: remove CSRF token dependency, impleme…
Browse files Browse the repository at this point in the history
…nt server-side submission, and enhance error handling
  • Loading branch information
IgorKowalczyk committed Dec 13, 2024
1 parent 799e531 commit 7afa044
Show file tree
Hide file tree
Showing 7 changed files with 28 additions and 107 deletions.
49 changes: 18 additions & 31 deletions app/api/contact/route.ts → app/actions/contact.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,21 @@
import { contactFormSchema } from "@/lib/validator";
"use server";

import "server-only";
import { createHash } from "crypto";
import { contactFormSchema } from "@/lib/validator";

export async function POST(request: Request) {
const body = await request.json();
const result = contactFormSchema.safeParse(body);
export async function submitContactForm(data: FormData) {
const formData = {
email: data.get("email"),
name: data.get("name"),
message: data.get("message"),
};

const result = contactFormSchema.safeParse(formData);

if (!result.success) {
const errors = result.error.flatten().fieldErrors;
return new Response(JSON.stringify({ error: true, message: Object.values(errors).flat().join(" ") }), {
status: 400,
headers: {
"Content-Type": "application/json",
},
});
return { error: Object.values(errors).flat().join(" ") };
}

const { name, email, message } = result.data;
Expand Down Expand Up @@ -56,27 +59,11 @@ export async function POST(request: Request) {
const data = await response.json();

if (!data.id) {
return new Response(JSON.stringify({ error: true, message: "Unable to send message" }), {
status: 500,
headers: {
"Content-Type": "application/json",
},
});
return { error: "Unable to send message" };
}

return new Response(JSON.stringify({ error: false, message: "Message sent successfully! Thank you for contacting me!" }), {
status: 200,
headers: {
"Content-Type": "application/json",
},
});
} catch (error) {
console.error(error);
return new Response(JSON.stringify({ error: true, message: "Unable to send message" }), {
status: 500,
headers: {
"Content-Type": "application/json",
},
});
} catch (_error) {
return { error: "Unable to send message" };
}

return { message: "Your message has been sent successfully!" };
}
8 changes: 2 additions & 6 deletions app/contact/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,21 @@ import { Button } from "@/components/Button";
import { ContactForm } from "@/components/client/ContactForm";
import { Description, Header2 } from "@/components/Headers";
import { contact } from "@/config";
import { headers } from "next/headers";

export const metadata = {
title: "Contact",
description: "If you have a project in mind, or just want to say hi, feel free to send me a message.",
};

export default async function Page() {
const h = await headers();
const csrfToken = h.get("X-CSRF-Token") || "missing";

export default function Page() {
return (
<div className="mb-16 mt-20">
<section className="mb-12">
<Header2 id="contact">Contact me</Header2>
<Description>I’m always eager to explore new opportunities and take on exciting projects. If you have a project in mind, or just want to say hi, feel free to send me a message.</Description>

<div className="my-6 flex w-full rounded-md border border-black/15 bg-white p-5 dark:border-neutral-800 dark:bg-[#161617]">
<ContactForm csrfToken={csrfToken} />
<ContactForm />
</div>
<Description>Or contact me with...</Description>
<div className="mt-4 flex flex-wrap gap-4">
Expand Down
5 changes: 1 addition & 4 deletions app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import { projects } from "@/config";
import { header, contact, meta, technologies } from "@/config";
import { GetUserData, getTotalContributionsForYears } from "@/lib/graphql";
import { ConvertNumber } from "@/lib/utils";
import { headers } from "next/headers";

export const metadata = {
title: header.title,
Expand All @@ -19,8 +18,6 @@ export const metadata = {
export default async function HomePage() {
const userData = await GetUserData();
const contributions = await getTotalContributionsForYears();
const h = await headers();
const csrfToken = h.get("X-CSRF-Token") || "missing";

return (
<>
Expand Down Expand Up @@ -106,7 +103,7 @@ export default async function HomePage() {
<Description>I’m always eager to explore new opportunities and take on exciting projects. If you have a project in mind, or just want to say hi, feel free to send me a message.</Description>

<div className="my-6 flex w-full rounded-md border border-black/15 bg-white p-5 dark:border-neutral-800 dark:bg-[#161617]">
<ContactForm csrfToken={csrfToken} />
<ContactForm />
</div>
<Description>Or contact me with...</Description>
<div className="mt-4 flex flex-wrap gap-4">
Expand Down
38 changes: 7 additions & 31 deletions components/client/ContactForm.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
"use client";

import { useState, ChangeEvent, FormEvent, useEffect } from "react";
import { submitContactForm } from "@/app/actions/contact";
import { Button } from "@/components/Button";
import { Icons } from "@/components/Icons";
import { useDebounce } from "@/lib/hooks";
import { cn } from "@/lib/utils";
import { contactFormSchema, ContactFormSchema } from "@/lib/validator";

export function ContactForm({ csrfToken }: { csrfToken: string }) {
export function ContactForm() {
const [formData, setFormData] = useState<ContactFormSchema>({
email: "",
name: "",
Expand Down Expand Up @@ -68,48 +69,23 @@ export function ContactForm({ csrfToken }: { csrfToken: string }) {
setSuccess("");
setError("");

const result = contactFormSchema.safeParse(formData);

if (!result.success) {
const errors = result.error.flatten().fieldErrors;
setInvalid({
email: !!errors.email && formData.email !== "",
name: !!errors.name && formData.name !== "",
message: !!errors.message && formData.message !== "",
});
return setError(Object.values(errors).flat().join(" "));
}

const { data } = result;

setLoading(true);

const request = await fetch("/api/contact", {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-CSRF-Token": csrfToken,
},
body: JSON.stringify(data),
});

const response = await request.json();
const formData = new FormData(e.currentTarget);
const response = await submitContactForm(formData);
console.log(response);

setLoading(false);
if (response.error) {
setInvalid({
...invalid,
[response.error.field]: true,
});
setError(response.error.message);
setError(response.error);
} else {
setFormData({ email: "", name: "", message: "" });
setInvalid({
email: false,
name: false,
message: false,
});
setSuccess(response.message);
setSuccess(response.message || "Message sent successfully!");
}
};

Expand Down
22 changes: 0 additions & 22 deletions middleware.ts

This file was deleted.

1 change: 0 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@
"lint:fix": "next lint --fix"
},
"dependencies": {
"@edge-csrf/nextjs": "2.5.3-cloudflare-rc1",
"@headlessui/react": "2.2.0",
"@headlessui/tailwindcss": "0.2.1",
"@vercel/analytics": "1.4.1",
Expand Down
12 changes: 0 additions & 12 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 7afa044

Please sign in to comment.