From 9f9a597f795b224a606459910baac7fd8cedaa51 Mon Sep 17 00:00:00 2001 From: Fernando Date: Fri, 30 May 2025 17:40:34 -0400 Subject: [PATCH 1/4] add sizes prop for Image performance --- src/components/NoteCard.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/NoteCard.tsx b/src/components/NoteCard.tsx index 3d980fb..58d220d 100644 --- a/src/components/NoteCard.tsx +++ b/src/components/NoteCard.tsx @@ -18,6 +18,7 @@ export default function NoteCard({ id, title, createdAt }: NoteCardProps) { src={blurredNote} alt={`Preview of ${title}`} fill + sizes="(max-width: 768px) 100vw, 400px" className="object-cover group-hover:scale-105 transition-transform" /> From 823bfbcd2b59f9d204eb46d0c4c612eef849d94a Mon Sep 17 00:00:00 2001 From: Fernando Date: Fri, 30 May 2025 17:50:18 -0400 Subject: [PATCH 2/4] move request course form to separate component --- src/app/request-course/page.tsx | 138 +------------------------- src/components/RequestCourseForm.tsx | 141 +++++++++++++++++++++++++++ 2 files changed, 143 insertions(+), 136 deletions(-) create mode 100644 src/components/RequestCourseForm.tsx diff --git a/src/app/request-course/page.tsx b/src/app/request-course/page.tsx index a1e7bbf..45fed85 100644 --- a/src/app/request-course/page.tsx +++ b/src/app/request-course/page.tsx @@ -1,144 +1,10 @@ -"use client"; -import { useForm } from "react-hook-form"; -import { zodResolver } from "@hookform/resolvers/zod"; -import * as z from "zod"; -import { Button } from "~/components/ui/button"; -import { - Form, - FormControl, - FormDescription, - FormField, - FormItem, - FormLabel, - FormMessage, -} from "~/components/ui/form"; -import { Input } from "~/components/ui/input"; -import { Loader2 } from "lucide-react"; -import { toast } from "sonner" - -import { api } from "~/trpc/react"; -import { useState } from "react"; - -const formSchema = z.object({ - courseName: z.string().min(1).min(10).max(60), - coursePrefix: z.string().min(1).min(3).max(3), - courseCode: z.string().min(1).min(4).max(4), -}); +import RequestCourseForm from "~/components/RequestCourseForm"; export default function RequestCourses() { - const [message, setMessage] = useState(null); - - const form = useForm>({ - resolver: zodResolver(formSchema), - }); - const createCourse = api.course.requestCourse.useMutation({ - onSuccess: (result) => { - if (result.message) { - setMessage(result.message); - } else if (result.newCourse) { - toast.success(`${result.newCourse.code} successfully requested`) - } - }, - onError: (error) => { - toast.error("Something went wrong"); - console.error(error.message, error.data); - }, - onSettled: () => { - form.reset({ - courseName: "", - courseCode: "", - coursePrefix: "", - })} - }); - - function onSubmit(values: z.infer) { - const capitalPrefix = values.coursePrefix.toUpperCase(); - createCourse.mutate({ - name: values.courseName, - prefix: capitalPrefix, - code: values.courseCode, - }); - } - return (

Request to add a Course

-
- - ( - - Course Name - - - - - - - )} - /> - -
-
- ( - - Course Prefix - - - - - This should be the three letters - - - - )} - /> -
- -
- ( - - Course Code - - - - - This should be the numbers following the course prefix - - - - )} - /> -
-
- {createCourse.isPending ? ( - - ) : ( - - )} - {message &&

{message}

} - - +
); } diff --git a/src/components/RequestCourseForm.tsx b/src/components/RequestCourseForm.tsx new file mode 100644 index 0000000..9781d85 --- /dev/null +++ b/src/components/RequestCourseForm.tsx @@ -0,0 +1,141 @@ +"use client"; +import { useForm } from "react-hook-form"; +import { zodResolver } from "@hookform/resolvers/zod"; +import * as z from "zod"; +import { Button } from "~/components/ui/button"; +import { + Form, + FormControl, + FormDescription, + FormField, + FormItem, + FormLabel, + FormMessage, +} from "~/components/ui/form"; +import { Input } from "~/components/ui/input"; +import { Loader2 } from "lucide-react"; +import { toast } from "sonner" + +import { api } from "~/trpc/react"; +import { useState } from "react"; + +const formSchema = z.object({ + courseName: z.string().min(1).min(10).max(60), + coursePrefix: z.string().min(1).min(3).max(3), + courseCode: z.string().min(1).min(4).max(4), +}); + +export default function RequestCourseForm() { + const [message, setMessage] = useState(null); + + const form = useForm>({ + resolver: zodResolver(formSchema), + }); + const createCourse = api.course.requestCourse.useMutation({ + onSuccess: (result) => { + if (result.message) { + setMessage(result.message); + } else if (result.newCourse) { + toast.success(`${result.newCourse.code} successfully requested`) + } + }, + onError: (error) => { + toast.error("Something went wrong"); + console.error(error.message, error.data); + }, + onSettled: () => { + form.reset({ + courseName: "", + courseCode: "", + coursePrefix: "", + })} + }); + + function onSubmit(values: z.infer) { + const capitalPrefix = values.coursePrefix.toUpperCase(); + createCourse.mutate({ + name: values.courseName, + prefix: capitalPrefix, + code: values.courseCode, + }); + } + + return ( +
+ + ( + + Course Name + + + + + + + )} + /> + +
+
+ ( + + Course Prefix + + + + + This should be the three letters + + + + )} + /> +
+ +
+ ( + + Course Code + + + + + This should be the numbers following the course prefix + + + + )} + /> +
+
+ {createCourse.isPending ? ( + + ) : ( + + )} + {message &&

{message}

} + + + ); +} \ No newline at end of file From 7c266c893a65b526030ac72893f0a446db23a0f5 Mon Sep 17 00:00:00 2001 From: Fernando Date: Fri, 30 May 2025 17:55:49 -0400 Subject: [PATCH 3/4] authenticate routes --- src/app/admin/page.tsx | 31 ++++++++++++++++++++------- src/app/courses/[courseCode]/page.tsx | 8 ++++++- src/app/request-course/page.tsx | 10 ++++++++- src/app/upload/page.tsx | 7 ++++++ 4 files changed, 46 insertions(+), 10 deletions(-) diff --git a/src/app/admin/page.tsx b/src/app/admin/page.tsx index e67733f..4ce4633 100644 --- a/src/app/admin/page.tsx +++ b/src/app/admin/page.tsx @@ -2,22 +2,38 @@ import AdminCreateCourseForm from "~/components/AdminCreateCourseForm"; import ApprovedCourseTable from "./ApprovedCourseTable"; import PendingCourseTable from "./PendingCourseTable"; -import { Tabs, TabsContent, TabsList, TabsTrigger } from "~/components/ui/tabs" +import { Tabs, TabsContent, TabsList, TabsTrigger } from "~/components/ui/tabs"; +import { auth } from "~/server/auth"; +import { redirect } from "next/navigation"; +import { adminIds } from "~/consts/admins"; + +export default async function AdminDashboard() { + const session = await auth(); + if (!session) { + redirect("/api/auth/signin"); + } + if (!adminIds.has(session.user.id)) { + redirect("/"); + } -export default function AdminDashboard() { - return (

Admin Dashboard

- Pending Courses - Approved Courses - Create Course + + Pending Courses + + + Approved Courses + + + Create Course + - + @@ -26,7 +42,6 @@ export default function AdminDashboard() { -
); } \ No newline at end of file diff --git a/src/app/courses/[courseCode]/page.tsx b/src/app/courses/[courseCode]/page.tsx index a3696fd..42fd785 100644 --- a/src/app/courses/[courseCode]/page.tsx +++ b/src/app/courses/[courseCode]/page.tsx @@ -1,5 +1,6 @@ -import { notFound } from "next/navigation"; +import { notFound, redirect } from "next/navigation"; import NoteCard from "~/components/NoteCard"; +import { auth } from "~/server/auth"; type paramsType = Promise<{courseCode: string}>; @@ -11,6 +12,11 @@ export default async function CoursePage(props: { params: paramsType}) { notFound(); } + const session = await auth(); + if(!session) { + redirect("/api/auth/signin"); + } + // const notes = trpc // if(!notes) notFound() // if notes.length < 0 diff --git a/src/app/request-course/page.tsx b/src/app/request-course/page.tsx index 45fed85..787204e 100644 --- a/src/app/request-course/page.tsx +++ b/src/app/request-course/page.tsx @@ -1,6 +1,14 @@ +import { redirect } from "next/navigation"; import RequestCourseForm from "~/components/RequestCourseForm"; +import { auth } from "~/server/auth"; + +export default async function RequestCourses() { + const session = await auth(); + + if(!session) { + redirect("/api/auth/signin"); + } -export default function RequestCourses() { return (

Request to add a Course

diff --git a/src/app/upload/page.tsx b/src/app/upload/page.tsx index 319bdde..52ce66a 100644 --- a/src/app/upload/page.tsx +++ b/src/app/upload/page.tsx @@ -1,7 +1,14 @@ +import { redirect } from "next/navigation"; import UploadNoteForm from "~/components/UploadNoteForm"; +import { auth } from "~/server/auth"; import { api } from "~/trpc/server"; export default async function UploadPage() { + const session = await auth(); + if(!session) { + redirect("/api/auth/signin"); + } + const courses = await api.course.fetchCourses(); return ( From 73a445d42802a6acebf9c522af5344a38a285ed7 Mon Sep 17 00:00:00 2001 From: Fernando Date: Fri, 30 May 2025 17:58:34 -0400 Subject: [PATCH 4/4] disable admin button if not admin --- src/components/ui/header.tsx | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/components/ui/header.tsx b/src/components/ui/header.tsx index f28fd4d..8eebf9c 100644 --- a/src/components/ui/header.tsx +++ b/src/components/ui/header.tsx @@ -2,11 +2,9 @@ import { auth } from "~/server/auth"; import { Button } from "./button"; import { NavigationMenu, - NavigationMenuContent, NavigationMenuItem, NavigationMenuLink, NavigationMenuList, - NavigationMenuTrigger, } from "./navigation-menu"; import { Avatar, AvatarFallback, AvatarImage } from "~/components/ui/avatar"; import { @@ -18,6 +16,7 @@ import { DropdownMenuTrigger, } from "~/components/ui/dropdown-menu"; import Link from "next/link"; +import { adminIds } from "~/consts/admins"; export const Header = async () => { const session = await auth(); @@ -80,11 +79,13 @@ export const Header = async () => { My Account - - - Admin - - + {adminIds.has(session.user.id) && ( + + + Admin + + + )} Log Out