feat: added drizzle for contact management

This commit is contained in:
DESKTOP-RTLN3BA\$punk 2025-09-30 21:36:07 -07:00
parent 1a3faf03d5
commit ba62489715
8 changed files with 762 additions and 18 deletions

View file

@ -1,13 +1,72 @@
"use client";
import React from "react";
import React, { useState } from "react";
import { IconMailFilled } from "@tabler/icons-react";
import { useId } from "react";
import { cn } from "@/lib/utils";
import Image from "next/image";
import { motion } from "motion/react";
import Link from "next/link";
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { z } from "zod";
import { toast } from "sonner";
// Define validation schema matching the database schema
const contactFormSchema = z.object({
name: z.string().min(1, 'Name is required').max(255, 'Name is too long'),
email: z.string().email('Invalid email address').max(255, 'Email is too long'),
company: z.string().min(1, 'Company is required').max(255, 'Company name is too long'),
message: z.string().optional().default(''),
});
type ContactFormData = z.infer<typeof contactFormSchema>;
export function ContactFormGridWithDetails() {
const [isSubmitting, setIsSubmitting] = useState(false);
const {
register,
handleSubmit,
formState: { errors },
reset,
} = useForm<ContactFormData>({
resolver: zodResolver(contactFormSchema),
});
const onSubmit = async (data: ContactFormData) => {
setIsSubmitting(true);
try {
const response = await fetch('/api/contact', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(data),
});
const result = await response.json();
if (response.ok) {
toast.success('Message sent successfully!', {
description: 'We will get back to you as soon as possible.',
});
reset();
} else {
toast.error('Failed to send message', {
description: result.message || 'Please try again later.',
});
}
} catch (error) {
console.error('Error submitting form:', error);
toast.error('Something went wrong', {
description: 'Please try again later.',
});
} finally {
setIsSubmitting(false);
}
};
return (
<div className="mx-auto grid w-full max-w-7xl grid-cols-1 gap-10 px-4 py-10 md:px-6 md:py-20 lg:grid-cols-2">
<div className="relative flex flex-col items-center overflow-hidden lg:items-start">
@ -26,17 +85,12 @@ export function ContactFormGridWithDetails() {
<div className="mt-10 hidden flex-col items-center gap-4 md:flex-row lg:flex">
<Link href="mailto:rohan@surfsense.com" className="text-sm text-neutral-500 dark:text-neutral-400">
rohan@surfsense.com
rohan@surfsense.com
</Link>
{/* <div className="h-1 w-1 rounded-full bg-neutral-500 dark:bg-neutral-400" />
<p className="text-sm text-neutral-500 dark:text-neutral-400">
+1 (800) 123 XX21
</p> */}
<div className="h-1 w-1 rounded-full bg-neutral-500 dark:bg-neutral-400" />
<Link href="https://cal.com/mod-surfsense" className="text-sm text-neutral-500 dark:text-neutral-400">
https://cal.com/mod-surfsense
https://cal.com/mod-surfsense
</Link>
</div>
<div className="div relative mt-20 flex w-[600px] flex-shrink-0 -translate-x-10 items-center justify-center [perspective:800px] [transform-style:preserve-3d] sm:-translate-x-0 lg:-translate-x-32">
@ -51,7 +105,10 @@ export function ContactFormGridWithDetails() {
/>
</div>
</div>
<div className="relative mx-auto flex w-full max-w-2xl flex-col items-start gap-4 overflow-hidden rounded-3xl bg-gradient-to-b from-gray-100 to-gray-200 p-4 sm:p-10 dark:from-neutral-900 dark:to-neutral-950">
<form
onSubmit={handleSubmit(onSubmit)}
className="relative mx-auto flex w-full max-w-2xl flex-col items-start gap-4 overflow-hidden rounded-3xl bg-gradient-to-b from-gray-100 to-gray-200 p-4 sm:p-10 dark:from-neutral-900 dark:to-neutral-950"
>
<Grid size={20} />
<div className="relative z-20 mb-4 w-full">
<label
@ -64,8 +121,15 @@ export function ContactFormGridWithDetails() {
id="name"
type="text"
placeholder="John Doe"
className="shadow-input h-10 w-full rounded-md border border-transparent bg-white pl-4 text-sm text-neutral-700 placeholder-neutral-500 outline-none focus:ring-2 focus:ring-neutral-800 focus:outline-none active:outline-none dark:border-neutral-800 dark:bg-neutral-800 dark:text-white"
{...register("name")}
className={cn(
"shadow-input h-10 w-full rounded-md border bg-white pl-4 text-sm text-neutral-700 placeholder-neutral-500 outline-none focus:ring-2 focus:ring-neutral-800 focus:outline-none active:outline-none dark:border-neutral-800 dark:bg-neutral-800 dark:text-white",
errors.name ? "border-red-500" : "border-transparent"
)}
/>
{errors.name && (
<p className="mt-1 text-xs text-red-500">{errors.name.message}</p>
)}
</div>
<div className="relative z-20 mb-4 w-full">
<label
@ -78,8 +142,15 @@ export function ContactFormGridWithDetails() {
id="email"
type="email"
placeholder="john.doe@example.com"
className="shadow-input h-10 w-full rounded-md border border-transparent bg-white pl-4 text-sm text-neutral-700 placeholder-neutral-500 outline-none focus:ring-2 focus:ring-neutral-800 focus:outline-none active:outline-none dark:border-neutral-800 dark:bg-neutral-800 dark:text-white"
{...register("email")}
className={cn(
"shadow-input h-10 w-full rounded-md border bg-white pl-4 text-sm text-neutral-700 placeholder-neutral-500 outline-none focus:ring-2 focus:ring-neutral-800 focus:outline-none active:outline-none dark:border-neutral-800 dark:bg-neutral-800 dark:text-white",
errors.email ? "border-red-500" : "border-transparent"
)}
/>
{errors.email && (
<p className="mt-1 text-xs text-red-500">{errors.email.message}</p>
)}
</div>
<div className="relative z-20 mb-4 w-full">
<label
@ -92,8 +163,15 @@ export function ContactFormGridWithDetails() {
id="company"
type="text"
placeholder="Example Inc."
className="shadow-input h-10 w-full rounded-md border border-transparent bg-white pl-4 text-sm text-neutral-700 placeholder-neutral-500 outline-none focus:ring-2 focus:ring-neutral-800 focus:outline-none active:outline-none dark:border-neutral-800 dark:bg-neutral-800 dark:text-white"
{...register("company")}
className={cn(
"shadow-input h-10 w-full rounded-md border bg-white pl-4 text-sm text-neutral-700 placeholder-neutral-500 outline-none focus:ring-2 focus:ring-neutral-800 focus:outline-none active:outline-none dark:border-neutral-800 dark:bg-neutral-800 dark:text-white",
errors.company ? "border-red-500" : "border-transparent"
)}
/>
{errors.company && (
<p className="mt-1 text-xs text-red-500">{errors.company.message}</p>
)}
</div>
<div className="relative z-20 mb-4 w-full">
<label
@ -106,13 +184,24 @@ export function ContactFormGridWithDetails() {
id="message"
rows={5}
placeholder="Type your message here"
className="shadow-input w-full rounded-md border border-transparent bg-white pt-4 pl-4 text-sm text-neutral-700 placeholder-neutral-500 outline-none focus:ring-2 focus:ring-neutral-800 focus:outline-none active:outline-none dark:border-neutral-800 dark:bg-neutral-800 dark:text-white"
{...register("message")}
className={cn(
"shadow-input w-full rounded-md border bg-white pt-4 pl-4 text-sm text-neutral-700 placeholder-neutral-500 outline-none focus:ring-2 focus:ring-neutral-800 focus:outline-none active:outline-none dark:border-neutral-800 dark:bg-neutral-800 dark:text-white",
errors.message ? "border-red-500" : "border-transparent"
)}
/>
{errors.message && (
<p className="mt-1 text-xs text-red-500">{errors.message.message}</p>
)}
</div>
<button className="relative z-10 flex items-center justify-center rounded-md border border-transparent bg-neutral-800 px-4 py-2 text-sm font-medium text-white shadow-[0px_1px_0px_0px_#FFFFFF20_inset] transition duration-200 hover:bg-neutral-900 md:text-sm">
Submit
<button
type="submit"
disabled={isSubmitting}
className="relative z-10 flex items-center justify-center rounded-md border border-transparent bg-neutral-800 px-4 py-2 text-sm font-medium text-white shadow-[0px_1px_0px_0px_#FFFFFF20_inset] transition duration-200 hover:bg-neutral-900 disabled:cursor-not-allowed disabled:opacity-50 md:text-sm"
>
{isSubmitting ? 'Submitting...' : 'Submit'}
</button>
</div>
</form>
</div>
);
}