Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Contact us page implemented #160

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ To run the project locally:
2. Visit `http://localhost:3000` in your browser.

### Recommended Extensions

- Prettier
- Open your command palette, choose your default formatter to be Prettier, and enable format on save.
- ESLint
Expand Down
24 changes: 24 additions & 0 deletions app/(auth)/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,30 @@ export async function signup(formData: FormData) {
redirect("/verification?email=" + data.email);
}

// Contact form submission
export async function contactSubmit(formData: FormData) {
const supabase = createClient();

const { error } = await supabase.from("contact").insert([
{
first_name: formData.get("first-name") as string,
last_name: formData.get("last-name") as string,
email: formData.get("email") as string,
company_name: formData.get("company-name") as string,
organisation_size: formData.get("organisation-size") as string,
job_title: formData.get("job-title") as string,
phone_number: formData.get("phone-number") as string,
message: formData.get("message") as string,
},
]);

if (error) {
return { error: error.message };
}

revalidatePath("/", "layout");
}

// OAuth sign-in with Google or GitHub
export async function signinWithOAuth(
provider: Provider,
Expand Down
17 changes: 17 additions & 0 deletions app/contact/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import ContactUs from "@/components/contact";
import { constructMetadata } from "@/lib/utils";
import { Metadata } from "next/types";

export const metadata: Metadata = constructMetadata({
title: "Contact Us",
description: "Contact PearAI.",
canonical: "/contact",
});

export default function Pricing() {
return (
<>
<ContactUs />
</>
);
}
297 changes: 297 additions & 0 deletions components/contact.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,297 @@
"use client";
import Link from "next/link";
import { useState } from "react";
import { signup, signinWithOAuth, contactSubmit } from "@/app/(auth)/actions";
import { Provider } from "@supabase/supabase-js";
import { zodResolver } from "@hookform/resolvers/zod";
import { useForm } from "react-hook-form";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Select } from "@/components/ui/select";
import {
Form,
FormField,
FormItem,
FormLabel,
FormControl,
FormMessage,
} from "@/components/ui/form";
import { contactSchema, ContactFormData } from "@/utils/form-schema";
import { toast } from "sonner";
import { useRouter } from "next/navigation";

const organisationSizes = [
{ value: "1-10", label: "1-10 employees" },
{ value: "11-50", label: "11-50 employees" },
{ value: "51-200", label: "51-200 employees" },
{ value: "201-500", label: "201-500 employees" },
{ value: "501-1000", label: "501-1000 employees" },
{ value: "1001+", label: "1001+ employees" },
];

export default function ContactUs() {
const [isSubmitting, setIsSubmitting] = useState(false);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I recommend using the isSubmitting status provided in formState by react-hook-form to avoid manually setting the update status.

const [errorMessage, setErrorMessage] = useState<string | null>(null);
const form = useForm<ContactFormData>({
resolver: zodResolver(contactSchema),
defaultValues: {
first_name: "",
last_name: "",
email: "",
job_title: "",
company_name: "",
organisation_size: "",
phone_number: "",
message: "",
},
});
const router = useRouter();

const handleContactSubmit = async (data: ContactFormData) => {
if (isSubmitting) return;
setIsSubmitting(true);
setErrorMessage(null);

try {
const formData = new FormData();
formData.append("first-name", data.first_name);
formData.append("last-name", data.last_name);
formData.append("email", data.email);
formData.append("job-title", data.job_title);
formData.append("company-name", data.company_name || "");
formData.append("organisation-size", data.organisation_size);
formData.append("phone-number", data.phone_number || "");
formData.append("message", data.message);

const response = await contactSubmit(formData);
if (response?.error) {
setErrorMessage(response.error);
} else {
toast.success("Successfully submitted! We will get back to you soon.");
router.push("/about");
}
} catch (error) {
setErrorMessage("An unexpected error occurred. Please try again.");
} finally {
setIsSubmitting(false);
}
};

return (
<section className="relative">
<div className="mx-auto max-w-6xl px-4 sm:px-6">
<div className="md:pt-30 pb-12 pt-24 md:pb-16">
<div className="md:pb-15 mx-auto max-w-3xl pb-4 text-center text-xl sm:text-2xl md:text-3xl lg:text-4xl">
<h1 className="text-3xl font-bold leading-tight sm:text-4xl md:text-5xl lg:text-6xl">
Looking for a <span className="text-primary-700">custom</span>{" "}
pricing option? Get in touch!
</h1>
</div>

<div className="mx-auto max-w-2xl">
<div className="my-6 flex items-center">
<div
className="mr-3 grow border-t border-solid border-gray-400"
aria-hidden="true"
/>
<div className="text-gray-600">
Please contact us using the form below
</div>
<div
className="ml-3 grow border-t border-solid border-gray-400"
aria-hidden="true"
/>
</div>

<Form {...form}>
<form
onSubmit={form.handleSubmit(handleContactSubmit)}
className="space-y-4"
>
<FormField
name="first_name"
control={form.control}
render={({ field }) => (
<FormItem>
<FormLabel htmlFor="first_name">
First Name <span className="text-red-600"> *</span>
</FormLabel>
<FormControl>
<Input
id="first_name"
placeholder="First name"
{...field}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>

<FormField
name="last_name"
control={form.control}
render={({ field }) => (
<FormItem>
<FormLabel htmlFor="last_name">
Last Name <span className="text-red-600"> *</span>
</FormLabel>
<FormControl>
<Input
id="last_name"
placeholder="Last name"
{...field}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>

<FormField
name="email"
control={form.control}
render={({ field }) => (
<FormItem>
<FormLabel htmlFor="email">
Work Email <span className="text-red-600"> *</span>
</FormLabel>
<FormControl>
<Input
id="email"
type="email"
placeholder="[email protected]"
{...field}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>

<FormField
name="job_title"
control={form.control}
render={({ field }) => (
<FormItem>
<FormLabel htmlFor="job_title">
Job Title <span className="text-red-600"> *</span>
</FormLabel>
<FormControl>
<Input
id="job_title"
placeholder="Your job title"
{...field}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>

<FormField
name="company_name"
control={form.control}
render={({ field }) => (
<FormItem>
<FormLabel htmlFor="company_name">
Company Name <span className="text-red-600"> *</span>
</FormLabel>
<FormControl>
<Input
id="company_name"
placeholder="Your company or app name"
{...field}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>

<FormField
name="organisation_size"
control={form.control}
render={({ field }) => (
<FormItem>
<FormLabel htmlFor="organisation_size">
Organisation Size{" "}
<span className="text-red-600"> *</span>
</FormLabel>
<FormControl>
<Select id="organisation_size" {...field}>
<option value="" disabled>
Select your organisation size
</option>
{organisationSizes.map((size) => (
<option key={size.value} value={size.value}>
{size.label}
</option>
))}
</Select>
</FormControl>
<FormMessage />
</FormItem>
)}
/>

<FormField
name="phone_number"
control={form.control}
render={({ field }) => (
<FormItem>
<FormLabel htmlFor="phone_number">Phone Number</FormLabel>
<FormControl>
<Input
id="phone_number"
placeholder="Your phone number"
{...field}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>

<FormField
name="message"
control={form.control}
render={({ field }) => (
<FormItem>
<FormLabel htmlFor="message">
Message <span className="text-red-600"> *</span>
</FormLabel>
<FormControl>
<textarea
id="message"
placeholder="Describe your needs here"
className="h-36 w-full resize-none rounded-md border border-gray-200 p-3 text-sm"
{...field}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>

<Button
type="submit"
size="lg"
className="w-full rounded-md"
disabled={isSubmitting}
isLoading={isSubmitting}
>
{isSubmitting ? "Submitting..." : "Submit"}
</Button>

{errorMessage && (
<p className="text-center text-red-500">{errorMessage}</p>
)}
</form>
</Form>
</div>
</div>
</div>
</section>
);
}
26 changes: 26 additions & 0 deletions components/ui/select.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import * as React from "react";
import { cn } from "@/lib/utils";

export interface SelectProps
extends React.SelectHTMLAttributes<HTMLSelectElement> {}

const Select = React.forwardRef<HTMLSelectElement, SelectProps>(
({ className, ...props }, ref) => {
return (
<select
className={cn(
"flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
className,
)}
ref={ref}
{...props}
>
{props.children}
</select>
);
},
);

Select.displayName = "Select";

export { Select };
Loading