Skip to content

Commit

Permalink
Added back contact form
Browse files Browse the repository at this point in the history
  • Loading branch information
santosh898 committed Dec 6, 2024
1 parent a23b601 commit 72d9213
Show file tree
Hide file tree
Showing 5 changed files with 229 additions and 2 deletions.
2 changes: 0 additions & 2 deletions next.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,6 @@ const nextConfig: NextConfig = {

return config;
},
output: "export",
reactStrictMode: true,
};

export default nextConfig;
63 changes: 63 additions & 0 deletions src/app/contact/actions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
"use server";

import { z } from "zod";
import { redirect } from "next/navigation";
import PocketBase from "pocketbase";
import defaultsDeep from "lodash/defaultsDeep";

const pb = new PocketBase("https://direction-trade.pockethost.io");

const FormSchema = z
.object({
name: z.string().min(3, "Name should at least be 3 characters"),
message: z.string().min(2, "Message should at least be 2 characters"),
email: z.string().email("Invalid email address").or(z.literal("")),
phone: z
.string()
.min(10, "Phone number should at least be 10 characters")
.or(z.literal("")),
})
.superRefine((data, ctx) => {
if (!data.email && !data.phone) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: "At least one of email or phone must be present",
path: ["email"], // Mark the email field with the custom error
});
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: "At least one of email or phone must be present",
path: ["phone"], // Mark the phone field with the custom error
});
}
});

export async function submitContactForm(
_prevState: Record<string, unknown>,
formData: FormData
) {
const data = {
name: formData.get("name"),
email: formData.get("email"),
message: formData.get("message"),
phone: formData.get("phone"),
};

const validatedFields = FormSchema.safeParse(data);

if (!validatedFields.success) {
return {
errors: validatedFields.error.flatten().fieldErrors,
data: defaultsDeep(data, {
email: "",
phone: "",
name: "",
message: "",
}),
};
}

await pb.collection("contact_me_santosh").create(validatedFields.data);

redirect("/contact/thank-you");
}
15 changes: 15 additions & 0 deletions src/app/contact/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { Metadata } from "next";

export const metadata: Metadata = {
title: "Contact Sai Santosh Kottakota",
description:
"Get in touch with Sai Santosh Kottakota, Tech Lead and experienced developer",
};

export default function ContactLayout({
children,
}: {
children: React.ReactNode;
}) {
return <div className="min-h-screen bg-black">{children}</div>;
}
137 changes: 137 additions & 0 deletions src/app/contact/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
"use client";

import { useActionState } from "react";
import Link from "next/link";
import { ArrowLeft } from "lucide-react";

import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Textarea } from "@/components/ui/textarea";
import { submitContactForm } from "./actions";

const initialState = {
data: {
name: "",
email: "",
message: "",
phone: "",
},
errors: {
name: [],
email: [],
message: [],
phone: [],
},
};

export default function ContactPage() {
const [state, formAction] = useActionState(submitContactForm, initialState);

return (
<div className="min-h-screen bg-black text-white flex flex-col">
<main className="flex-grow container mx-auto px-6 py-12">
<Link href="/" className="inline-block mb-8">
<Button variant="ghost">
<ArrowLeft className="mr-2 h-4 w-4" /> Back to Portfolio
</Button>
</Link>

<h1 className="text-3xl font-bold mb-8">Contact Me</h1>

<form action={formAction} className="space-y-6 max-w-md">
<div>
<label htmlFor="name" className="block text-sm font-medium mb-2">
Name *
</label>
<Input
id="name"
name="name"
required
defaultValue={state.data.name}
className="bg-gray-900 border-gray-700 text-white"
/>
{state.errors?.name && (
<>
{state.errors.name.map((error, index) => (
<p className="mt-1 text-sm text-red-500" key={index}>
{error}
</p>
))}
</>
)}
</div>

<div>
<label htmlFor="phone" className="block text-sm font-medium mb-2">
Phone
</label>
<Input
id="phone"
name="phone"
defaultValue={state.data.phone}
className="bg-gray-900 border-gray-700 text-white"
/>
{state.errors?.phone && (
<>
{state.errors.phone.map((error, index) => (
<p className="mt-1 text-sm text-red-500" key={index}>
{error}
</p>
))}
</>
)}
</div>

<div>
<label htmlFor="email" className="block text-sm font-medium mb-2">
Email
</label>
<Input
id="email"
name="email"
type="email"
defaultValue={state.data.email}
className="bg-gray-900 border-gray-700 text-white"
/>
{state.errors?.email && (
<>
{state.errors.email.map((error, index) => (
<p className="mt-1 text-sm text-red-500" key={index}>
{error}
</p>
))}
</>
)}
</div>

<div>
<label htmlFor="message" className="block text-sm font-medium mb-2">
Message *
</label>
<Textarea
id="message"
name="message"
required
defaultValue={state.data.message}
className="bg-gray-900 border-gray-700 text-white"
rows={5}
/>
{state.errors?.message && (
<>
{state.errors.message.map((error, index) => (
<p className="mt-1 text-sm text-red-500" key={index}>
{error}
</p>
))}
</>
)}
</div>

<Button type="submit" variant="secondary">
Send Message
</Button>
</form>
</main>
</div>
);
}
14 changes: 14 additions & 0 deletions src/app/contact/thank-you/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import Link from "next/link";
import { Button } from "@/components/ui/button";

export default function ThankYouPage() {
return (
<div className="min-h-screen bg-black text-white flex flex-col items-center justify-center">
<h1 className="text-3xl font-bold mb-4">Thank You!</h1>
<p className="text-xl mb-8">Your message has been sent successfully.</p>
<Button asChild>
<Link href="/">Return to Portfolio</Link>
</Button>
</div>
);
}

0 comments on commit 72d9213

Please sign in to comment.