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

feat: Implemented form for reporting issues encountered while accessing the course #1712

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
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
4 changes: 4 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@ GITHUB_SECRET=
NEXT_PUBLIC_DISCORD_WEBHOOK_URL =
JOB_BOARD_AUTH_SECRET=

EMAIL_USER=
EMAIL_PASS=
EMAIL_RECEIVER=


COHORT3_DISCORD_ACCESS_KEY =
COHORT3_DISCORD_ACCESS_SECRET =
Expand Down
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
"@radix-ui/react-tooltip": "^1.1.2",
"@tabler/icons-react": "^3.14.0",
"@types/bcrypt": "^5.0.2",
"@types/nodemailer": "^6.4.17",
"@uiw/react-markdown-preview": "^5.1.3",
"@uiw/react-md-editor": "^4.0.4",
"autoprefixer": "^10.4.20",
Expand All @@ -65,6 +66,7 @@
"next-auth": "^4.24.5",
"next-themes": "^0.2.1",
"nextjs-toploader": "^1.6.11",
"nodemailer": "^6.9.16",
"notion-client": "^6.16.0",
"pdf-lib": "^1.17.1",
"prismjs": "^1.29.0",
Expand Down
25 changes: 23 additions & 2 deletions pnpm-lock.yaml

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

28 changes: 28 additions & 0 deletions src/app/api/send-email/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { NextResponse } from "next/server";
import { sendMail } from "@/lib/nodemailer";

export async function POST(req: Request) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Missing authentication check here

Copy link
Author

@ToseebNadaf ToseebNadaf Jan 15, 2025

Choose a reason for hiding this comment

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

Added now

const body = await req.json();
const { name, email, message } = body;

if (!name) {
return NextResponse.json({ error: "Name is required." }, { status: 400 });
}

if (!email) {
return NextResponse.json({ error: "Email is required." }, { status: 400 });
}

if (!message) {
return NextResponse.json({ error: "Message is required." }, { status: 400 });
}

try {
await sendMail(name, email, message);

return NextResponse.json({ message: "Email sent successfully." }, { status: 200 });
} catch (error) {
console.error("Error sending email:", error);
return NextResponse.json({ error: "Failed to send email." }, { status: 500 });
}
}
215 changes: 215 additions & 0 deletions src/app/contact/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,215 @@
"use client";

import { Input } from "@/components/ui/input";
import { Textarea } from "@/components/ui/textarea";
import { Button } from "@/components/ui/button";
import { zodResolver } from "@hookform/resolvers/zod";
import { useForm } from "react-hook-form";
import {
Form,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
} from "@/components/ui/form";
import { toast } from "sonner";
import { motion } from "framer-motion";
import { z } from "zod";
import { Toaster } from "sonner";
import { Loader2 } from "lucide-react";

const formSchema = z.object({
name: z
.string()
.min(1, "First name is required")
.max(50, "First name must be less than 50 characters"),
lastName: z
.string()
.min(1, "Last name is required")
.max(50, "Last name must be less than 50 characters"),
email: z.string().email("Invalid email address"),
message: z
.string()
.min(10, "Message must be at least 10 characters")
.max(500, "Message must be less than 500 characters"),
});

type FormValues = z.infer<typeof formSchema>;

export default function ContactForm() {
const containerVariants = {
hidden: { opacity: 0, y: 20 },
visible: {
opacity: 1,
y: 0,
transition: {
staggerChildren: 0.3,
when: "beforeChildren",
},
},
};

const boxVariants = {
hidden: { opacity: 0, y: 20 },
visible: {
opacity: 1,
y: 0,
transition: {
ease: "easeOut",
},
},
};

const form = useForm<FormValues>({
resolver: zodResolver(formSchema),
defaultValues: {
name: "",
lastName: "",
email: "",
message: "",
},
});

const onSubmit = async (data: FormValues) => {
try {
const res = await fetch("/api/send-email", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(data),
});

if (res.ok) {
toast("Your message has been sent successfully!");
form.reset();
} else {
throw new Error("Failed to send message");
}
} catch (err) {
toast("There was a problem sending your message. Please try again.");
}
};

return (
<>
<div className="container flex justify-center items-center h-screen md:pt-[90px] pb-[90px]">
<motion.div
className="bg-cta-gradient rounded-3xl py-[60px] px-4 w-full max-w-3xl shadow-lg"
variants={containerVariants}
initial="hidden"
whileInView="visible"
viewport={{ once: true, amount: 0.2 }}
layout="position"
>
<motion.h2
className="bg-gradient-to-br from-neutral-300 to-neutral-500 bg-clip-text text-left text-4xl font-bold tracking-tight text-transparent md:text-5xl mb-6"
variants={boxVariants}
>
Contact Us
</motion.h2>
<motion.p
className="mt-2 text-left text-[#7D7F78] max-w-[450px] mb-4"
variants={boxVariants}
>
Fill out the form below, and we'll get back to you as soon as possible.
</motion.p>
<Form {...form}>
<form
onSubmit={form.handleSubmit(onSubmit)}
className="space-y-6 md:space-y-8"
>
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2">
<FormField
control={form.control}
name="name"
render={({ field }) => (
<FormItem>
<FormLabel>First Name</FormLabel>
<FormControl>
<Input
{...field}
placeholder="Name"
aria-label="First Name"
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="lastName"
render={({ field }) => (
<FormItem>
<FormLabel>Last Name</FormLabel>
<FormControl>
<Input
{...field}
placeholder="Last Name"
aria-label="Last Name"
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</div>

<FormField
control={form.control}
name="email"
render={({ field }) => (
<FormItem>
<FormLabel>Email</FormLabel>
<FormControl>
<Input
{...field}
type="email"
placeholder="[email protected]"
aria-label="Email"
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>

<FormField
control={form.control}
name="message"
render={({ field }) => (
<FormItem>
<FormLabel>Message</FormLabel>
<FormControl>
<Textarea
{...field}
placeholder="Write your message here..."
className="min-h-[120px]"
aria-label="Message"
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>

<div className="flex justify-end">
<Button type="submit" className="w-full sm:w-auto">
{form.formState.isSubmitting ? (
<>
Submitting
<Loader2 className="ml-2 h-4 w-4 animate-spin" />
</>
) : (
"Submit"
)}
</Button>
</div>
</form>
</Form>
</motion.div>
</div>
<Toaster />
</>
);
}
10 changes: 4 additions & 6 deletions src/components/Courses.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import { Button } from './ui/button';
import { refreshDb } from '@/actions/refresh-db';
import { useSession } from 'next-auth/react';
import { toast } from 'sonner';
import Link from 'next/link';

export const Courses = ({ courses }: { courses: Course[] }) => {
const session = useSession();
Expand Down Expand Up @@ -50,13 +49,12 @@ export const Courses = ({ courses }: { courses: Course[] }) => {
</h3>
<p className="text-primary/80">
Try refreshing the database. If you are still facing issues?{' '}
<Link
href="mailto:[email protected]"
target="_blank"
className="text-primary underline underline-offset-4"
<span
onClick={() => router.push('/contact')}
className="text-primary underline underline-offset-4 cursor-pointer"
>
Contact us
</Link>
</span>
</p>
</div>
<Button size={'lg'} onClick={handleClick}>
Expand Down
Loading