Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
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 package-lock.json

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

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
"@prisma/client": "^6.5.0",
"@radix-ui/react-alert-dialog": "^1.1.13",
"@radix-ui/react-avatar": "^1.1.9",
"@radix-ui/react-dialog": "^1.1.13",
"@radix-ui/react-dropdown-menu": "^2.1.14",
"@radix-ui/react-label": "^2.1.6",
"@radix-ui/react-navigation-menu": "^1.2.11",
Expand Down
2 changes: 1 addition & 1 deletion prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ model Note {
Title String
createdAt DateTime @default(now())
url String
fileType String
//fileType String

createdBy User @relation(fields: [createdById], references: [id])
createdById String
Expand Down
Binary file added public/blurredNote.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file removed public/favicon.ico
Binary file not shown.
Binary file added public/logo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
14 changes: 3 additions & 11 deletions src/app/admin/ApprovedCourseTable.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,15 @@
"use client";

import { useState } from "react";
import EditButton from "~/components/EditButton";
import { api } from "~/trpc/react";

const ITEMS_PER_PAGE = 10;

export default function ApprovedCourseTable() {
const [approvedPage, setApprovedPage] = useState(1);

const {data: courses, isLoading, error } = api.admin.getAllApprovedCourses.useQuery();

const handleEdit = (id: string) => {
console.log("Edit:", id);
};
const {data: courses, isLoading, error, refetch } = api.admin.getAllApprovedCourses.useQuery();

const paginate = <T,>(data: T[], page: number) =>
data.slice((page - 1) * ITEMS_PER_PAGE, page * ITEMS_PER_PAGE);
Expand Down Expand Up @@ -76,12 +73,7 @@ export default function ApprovedCourseTable() {
{course.code.replace(/^([A-Z]{3})(\d{4})$/, "$1 $2")}
</td>
<td className="px-4 py-2">
<button
onClick={() => handleEdit(course.id)}
className="cursor-pointer rounded bg-yellow-500 px-3 py-1 text-white hover:bg-yellow-600"
>
Edit
</button>
<EditButton course={course} refetch={refetch} />
</td>
</tr>
))
Expand Down
14 changes: 3 additions & 11 deletions src/app/admin/PendingCourseTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,14 @@ import {
import { toast } from "sonner";
import { Loader2 } from "lucide-react";
import { Button } from "~/components/ui/button";
import ApproveButton from "~/components/ApproveButton";


const ITEMS_PER_PAGE = 10;

export default function PendingCourseTable() {
const [requestedPage, setRequestedPage] = useState(1);
const {data: courses, isLoading, error } = api.admin.getAllPendingCourses.useQuery();
const {data: courses, isLoading, error, refetch } = api.admin.getAllPendingCourses.useQuery();

const denyCourse = api.admin.denyCourse.useMutation({
onSuccess: (result) => {
Expand All @@ -35,10 +36,6 @@ export default function PendingCourseTable() {
},
});

const handleApprove = (id: string) => {
console.log("Approved:", id);
};

const handleDeny = (id: string) => {
denyCourse.mutate({courseId: id});
};
Expand Down Expand Up @@ -107,12 +104,7 @@ export default function PendingCourseTable() {
</td>
<td className="px-4 py-2">{course.count}</td>
<td className="space-x-2 px-4 py-2">
<button
onClick={() => handleApprove(course.id)}
className="cursor-pointer rounded bg-green-500 px-3 py-1 text-white hover:bg-green-600"
>
Approve
</button>
<ApproveButton course={course} refetch={refetch}/>
<AlertDialog>
<AlertDialogTrigger asChild>
<button className="cursor-pointer rounded bg-red-500 px-3 py-1 text-white hover:bg-red-600">
Expand Down
7 changes: 6 additions & 1 deletion src/app/admin/page.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import AdminCreateCourseForm from "~/components/AdminCreateCourseForm";
import ApprovedCourseTable from "./ApprovedCourseTable";
import PendingCourseTable from "./PendingCourseTable";

Expand All @@ -12,14 +13,18 @@ export default function AdminDashboard() {
<Tabs defaultValue="pending" className="w-full">
<TabsList>
<TabsTrigger className="cursor-pointer" value="pending">Pending Courses</TabsTrigger>
<TabsTrigger className="cursor-pointer" value="approved">ApprovedCourses</TabsTrigger>
<TabsTrigger className="cursor-pointer" value="approved">Approved Courses</TabsTrigger>
<TabsTrigger className="cursor-pointer" value="create">Create Course</TabsTrigger>
</TabsList>
<TabsContent value="pending">
<PendingCourseTable/>
</TabsContent>
<TabsContent value="approved">
<ApprovedCourseTable />
</TabsContent>
<TabsContent value="create">
<AdminCreateCourseForm />
</TabsContent>
</Tabs>

</main>
Expand Down
7 changes: 7 additions & 0 deletions src/app/courses/[courseCode]/loading.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@

export default function Loading() {

return (
<p className="pt-15">Loading...</p>
);
}
30 changes: 30 additions & 0 deletions src/app/courses/[courseCode]/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { notFound } from "next/navigation";
import NoteCard from "~/components/NoteCard";

type paramsType = Promise<{courseCode: string}>;

export default async function CoursePage(props: { params: paramsType}) {
const { courseCode } = await props.params;
const isValid = /^[A-Z]{3}\d{4}$/.test(courseCode);

if (!isValid) {
notFound();
}

// const notes = trpc
// if(!notes) notFound()
// if notes.length < 0

return (
<main className="mx-auto max-w-7xl px-4 py-8 pt-15">
<h1 className="mb-6 text-3xl font-bold">{courseCode}</h1>
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-6">
<NoteCard id="1" title="Lecture 1" createdAt=""/>
<NoteCard id="1" title="Lecture 1" createdAt=""/>
<NoteCard id="1" title="Lecture 1" createdAt=""/>
<NoteCard id="1" title="Lecture 1" createdAt=""/>

</div>
</main>
);
}
35 changes: 4 additions & 31 deletions src/app/courses/page.tsx
Original file line number Diff line number Diff line change
@@ -1,37 +1,10 @@
import Link from "next/link";
import CourseCard from "~/components/ui/CourseCard";
import { api } from "~/trpc/server";

const courses = [
{
name: "Calculus I",
code: "MATH101",
imageUrl: "/images/calculus.jpg",
},
{
name: "Introduction to Programming",
code: "CS100",
imageUrl: "/images/programming.jpg",
},
{
name: "Physics I",
code: "PHYS101",
imageUrl: "/images/physics.jpg",
},{
name: "Physics I",
code: "PHYS101",
imageUrl: "/images/physics.jpg",
},{
name: "Physics I",
code: "PHYS101",
imageUrl: "/images/physics.jpg",
},{
name: "Physics I",
code: "PHYS101",
imageUrl: "/images/physics.jpg",
},
];
export default async function CoursesPage() {
const courses = await api.course.fetchCourses();

export default function CoursesPage() {
return (
<main className="max-w-7xl mx-auto px-4 py-8 pt-15">
<h1 className="text-3xl font-bold mb-6">Courses</h1>
Expand All @@ -42,7 +15,7 @@ export default function CoursesPage() {
key={course.code}
name={course.name}
code={course.code}
imageUrl={course.imageUrl}
imageUrl={course.url}
/>
))}
</div>
Expand Down
2 changes: 1 addition & 1 deletion src/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { Toaster } from "~/components/ui/sonner";
export const metadata: Metadata = {
title: "Public Notes",
description: "Upload your Notes with Other Users!",
icons: [{ rel: "icon", url: "/favicon.ico" }],
icons: [{ rel: "icon", url: "/logo.png" }],
};

const geist = Geist({
Expand Down
159 changes: 159 additions & 0 deletions src/components/AdminCreateCourseForm.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
"use client";

import { toast } from "sonner";
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 { api } from "~/trpc/react";
import { Loader2 } from "lucide-react";

const formSchema = z.object({
courseName: z.string().min(1).min(10).max(60),
courseUrl: z.string().min(1),
coursePrefix: z.string().min(1).min(3).max(3),
courseCode: z.string().min(1).min(4).max(4),
});

export default function AdminCreateCourseForm() {
const form = useForm<z.infer<typeof formSchema>>({
resolver: zodResolver(formSchema),
});

const createCourse = api.admin.createCourse.useMutation({
onSuccess: (result) => {
if (result.success) {
toast.success(`${result.createdCourse?.code} successfully created!`);
} else {
toast.error(result.message);
}
},
onError: (error) => {
toast.error("Something went wrong");
console.error(error.message, error.data);
},
onSettled: () => {
form.reset({
courseName: "",
coursePrefix: "",
courseCode: "",
courseUrl: "",
});
},
});

function onSubmit(values: z.infer<typeof formSchema>) {
const capitalPrefix = values.coursePrefix.toUpperCase();
createCourse.mutate({
name: values.courseName,
prefix: capitalPrefix,
code: values.courseCode,
url: values.courseUrl,
});
}

return (
<Form {...form}>
<form
onSubmit={form.handleSubmit(onSubmit)}
className="mx-auto max-w-3xl space-y-8 py-10"
>
<FormField
control={form.control}
name="courseName"
render={({ field }) => (
<FormItem>
<FormLabel>Course Name</FormLabel>
<FormControl>
<Input
placeholder="Computer Science 1"
type="text"
{...field}
/>
</FormControl>

<FormMessage />
</FormItem>
)}
/>

<FormField
control={form.control}
name="courseUrl"
render={({ field }) => (
<FormItem>
<FormLabel>Image URL</FormLabel>
<FormControl>
<Input placeholder="" type="text" {...field} />
</FormControl>
<FormDescription>
An image url to display for the course
</FormDescription>
<FormMessage />
</FormItem>
)}
/>

<div className="grid grid-cols-12 gap-4">
<div className="col-span-6">
<FormField
control={form.control}
name="coursePrefix"
render={({ field }) => (
<FormItem>
<FormLabel>Course Prefix</FormLabel>
<FormControl>
<Input placeholder="COP" type="text" {...field} />
</FormControl>
<FormDescription>
This should be the three letters
</FormDescription>
<FormMessage />
</FormItem>
)}
/>
</div>

<div className="col-span-6">
<FormField
control={form.control}
name="courseCode"
render={({ field }) => (
<FormItem>
<FormLabel>Course Code</FormLabel>
<FormControl>
<Input placeholder="3502" type="text" {...field} />
</FormControl>
<FormDescription>
This should be the numbers following the course prefix
</FormDescription>
<FormMessage />
</FormItem>
)}
/>
</div>
</div>
{createCourse.isPending ? (
<Button disabled>
<Loader2 className="animate-spin" />
Loading
</Button>
) : (
<Button className="cursor-pointer" type="submit">
Submit
</Button>
)}
</form>
</Form>
);
}
Loading