Skip to content
This repository has been archived by the owner on Aug 27, 2024. It is now read-only.

Commit

Permalink
Add initial assignments page
Browse files Browse the repository at this point in the history
  • Loading branch information
anli5005 committed Dec 30, 2023
1 parent d08836a commit 328836d
Show file tree
Hide file tree
Showing 12 changed files with 253 additions and 10 deletions.
2 changes: 1 addition & 1 deletion app/[slug]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export default function Page({ params }: { params: { slug: string } }) {
return <Card title={<h1>{page.title}</h1>}>
<MenuItemActivator item={page.activeMenuItem ?? null} />
<Prose>
<MDXContent components={mdxComponents} />
{content}
</Prose>
</Card>
}
27 changes: 27 additions & 0 deletions app/assignments/assessment/[slug]/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { allAssessments, allHomework } from 'contentlayer/generated'
import { notFound } from 'next/navigation'
import { useMDXComponent } from 'next-contentlayer/hooks'
import { mdxComponents } from '@/components/mdx'
import { Card } from '@/components/Card'
import { Prose } from '@/components/Prose'
import { MenuItemActivator } from '@/app/menu'

export async function generateStaticParams() {
return allAssessments.map(page => ({
slug: page.slug,
})).filter(slug => slug)
}

export default function Assessment({ params }: { params: { slug: string } }) {
const page = allAssessments.find(page => page.slug === params.slug)
if (!page) notFound()

const MDXContent = useMDXComponent(page.body.code)
const content = <MDXContent components={mdxComponents} />

return <Card title={<h1>{page.title}</h1>}>
<Prose>
{content}
</Prose>
</Card>
}
27 changes: 27 additions & 0 deletions app/assignments/hw/[slug]/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { allHomework } from 'contentlayer/generated'
import { notFound } from 'next/navigation'
import { useMDXComponent } from 'next-contentlayer/hooks'
import { mdxComponents } from '@/components/mdx'
import { Card } from '@/components/Card'
import { Prose } from '@/components/Prose'
import { MenuItemActivator } from '@/app/menu'

export async function generateStaticParams() {
return allHomework.map(page => ({
slug: page.slug,
})).filter(slug => slug)
}

export default function Homework({ params }: { params: { slug: string } }) {
const page = allHomework.find(page => page.slug === params.slug)
if (!page) notFound()

const MDXContent = useMDXComponent(page.body.code)
const content = <MDXContent components={mdxComponents} />

return <Card title={<h1>{page.title}</h1>}>
<Prose>
{content}
</Prose>
</Card>
}
9 changes: 9 additions & 0 deletions app/assignments/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { ReactNode } from "react"
import { MenuItemActivator } from "../menu"

export default function RootLayout({ children }: { children: ReactNode }) {
return <>
<MenuItemActivator item="assignments" />
{children}
</>
}
115 changes: 115 additions & 0 deletions app/assignments/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
import { allHomework, allAssessments } from 'contentlayer/generated'
import { Card } from '@/components/Card'
import { MenuItemActivator } from '@/app/menu'
import Link from 'next/link'
import { format } from 'date-fns'

type Assignment = {
title: string
href: string
isReleased: boolean
releaseDate?: Date
dates: {
name: string
date: Date
specifyTime: boolean
}[]
sortDate: Date
}

export const formattedHomework: Assignment[] = allHomework.map(hw => {
const due = new Date(hw.dueDate)

return {
title: hw.title,
href: `/assignments/hw/${hw.slug}`,
isReleased: hw.isReleased,
releaseDate: hw.releaseDate && new Date(hw.releaseDate),
dates: [
...(hw.auxiliaryDates ?? []).map(aux => ({
name: aux.name,
date: new Date(aux.date),
specifyTime: true,
})),
{
name: "Due",
date: due,
specifyTime: true,
}
],
sortDate: new Date(hw.dueDate),
}
})

export const formattedAssessments = allAssessments.map(a => {
const date = new Date(a.assessmentDate)

return {
title: a.title,
href: `/assignments/assessment/${a.slug}`,
isReleased: a.isReleased,
releaseDate: a.releaseDate && new Date(a.releaseDate),
dates: [
{
name: "Scheduled",
date,
specifyTime: true,
}
],
sortDate: date,
}
})

function AssignmentRow({ title, href, isReleased, releaseDate, dates }: Assignment) {
let className = "border-b border-neutral-300 dark:border-neutral-600 py-2 last:pb-0 last:border-0 block lg:table-row w-full"
if (!isReleased) {
className += " opacity-50"
}

return <tr className={className}>
<td className="lg:py-2 w-full block lg:table-cell">
{isReleased ?
<Link href={href} className="link font-bold">{title}</Link> :
<span className="italic">{title}{releaseDate && <> (available <strong>{format(releaseDate, "M/d")}</strong>)</>}</span>}
</td>
<td className="lg:py-2 block lg:table-cell">
{dates.map((item, index) => <div key={index}>
{item.name} <strong>{format(item.date, item.specifyTime ? "E, M/d @ h:mmaa" : "E, M/d")}</strong>
</div>)}
</td>
</tr>
}

export function AssignmentTable({ assignments: raw }: { assignments: Assignment[] }) {
const data = raw.toSorted((a, b) => a.sortDate.getTime() - b.sortDate.getTime())

return <table className="block lg:table w-full">
<thead>
<tr className="border-b-4 border-neutral-300 dark:border-neutral-600 sr-only lg:not-sr-only">
<th className="text-left pb-2 w-full">Name</th>
<th className="text-left pb-2 lg:min-w-64">Date</th>
</tr>
</thead>
<tbody className="block lg:table-row-group">
{data.map(assignment => <AssignmentRow {...assignment} key={assignment.href} />)}
</tbody>
</table>
}

export default function Assignments() {
if (!allHomework.length && !allAssessments.length) {
return <Card>
There are currently no assignments for this class.
</Card>
}

return <div>
<div className="mb-4">Here you'll find all assignments for this class.</div>
{allHomework.length > 0 && <Card title={<h2>Homework</h2>} margin={allAssessments.length > 0}>
<AssignmentTable assignments={formattedHomework} />
</Card>}
{allAssessments.length > 0 && <Card title={<h2>Assessments</h2>}>
<AssignmentTable assignments={formattedAssessments} />
</Card>}
</div>
}
6 changes: 6 additions & 0 deletions app/menu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,12 @@ export const menuItems: MenuItemProps[] = [
icon: "📄",
href: "/syllabus",
},
{
id: "assignments",
title: "Assignments",
icon: "📋",
href: "/assignments",
},
{
id: "codestyle",
title: "Style Guide",
Expand Down
20 changes: 20 additions & 0 deletions components/UpcomingAssignments.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { formattedAssessments, formattedHomework, AssignmentTable } from "@/app/assignments/page"

const threshold = 5 * 7 * 24 * 60 * 60 * 1000 // 3 weeks
const thresholdText = "3 weeks"

const upcoming = [...formattedHomework, ...formattedAssessments].filter(({ sortDate }) => {
const remainingTime = sortDate.getTime() - new Date().getTime()
return remainingTime >= 0 && remainingTime <= threshold
})

export function UpcomingAssignments() {
if (!upcoming.length) return <div>
There are no upcoming assignments in the next {thresholdText}.
</div>

return <>
<div className="mb-4">There are {upcoming.length} {upcoming.length === 1 ? "assignment" : "assignments"} in the next {thresholdText}:</div>
<AssignmentTable assignments={upcoming} />
</>
}
2 changes: 2 additions & 0 deletions components/mdx.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import Link from "next/link";
import { Card } from "./Card";
import { Prose } from "./Prose";
import { StaffGrid } from "./StaffGrid";
import { UpcomingAssignments } from "./UpcomingAssignments";

export const mdxComponents: MDXComponents = {
a: ({ href, ...props }) => {
Expand All @@ -18,4 +19,5 @@ export const mdxComponents: MDXComponents = {
Card: Card,
Prose: Prose,
StaffGrid: StaffGrid,
UpcomingAssignments: UpcomingAssignments,
}
7 changes: 7 additions & 0 deletions content/homework/hw0.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
title: HW0 - Xcode Setup
isReleased: true
dueDate: 2024-01-22T23:59:00
---

Just send us a screenshot of Xcode working
6 changes: 6 additions & 0 deletions content/homework/hw1.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
title: HW1 - Basic Swift
isReleased: false
dueDate: 2024-01-23T23:59:00
releaseDate: 2024-01-24T23:59:00
---
4 changes: 4 additions & 0 deletions content/pages/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ customLayout: true
</Prose>
</Card>

<Card title={<h2>Upcoming Assignments</h2>} margin>
<UpcomingAssignments />
</Card>

<Card title={<h2>Staff</h2>} margin>
<h3 className="font-bold text-xl">Instructors</h3>
<StaffGrid members={[
Expand Down
38 changes: 29 additions & 9 deletions contentlayer.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,39 +28,59 @@ export const Homework = defineDocumentType(() => ({
contentType: 'mdx',
fields: {
title: { type: 'string', required: true },
isReleased: { type: 'boolean', required: true },
releaseDate: { type: 'date', required: false },
dueDate: { type: 'date', required: true },
auxiliaryDates: {
type: 'list',
of: {
type: 'nested',
def: () => ({
fields: {
name: { type: 'string', required: true },
date: { type: 'date', required: true },
},
}),
},
required: false,
},
},
computedFields: {
slug: { type: 'string', resolve: page => page._raw.flattenedPath.slice("homework/".length) },
},
}))

export const Lecture = defineDocumentType(() => ({
name: 'Lecture',
filePathPattern: `lectures/**/*.mdx`,
export const Assessment = defineDocumentType(() => ({
name: 'Assessment',
filePathPattern: `assessments/**/*.mdx`,
contentType: 'mdx',
fields: {
title: { type: 'string', required: true },
isReleased: { type: 'boolean', required: true },
releaseDate: { type: 'date', required: false },
assessmentDate: { type: 'date', required: true },
},
computedFields: {
slug: { type: 'string', resolve: page => page._raw.flattenedPath.slice("lectures/".length) },
slug: { type: 'string', resolve: page => page._raw.flattenedPath.slice("assessments/".length) },
},
}))

export const Exam = defineDocumentType(() => ({
name: 'Exam',
filePathPattern: `exams/**/*.mdx`,
export const Lecture = defineDocumentType(() => ({
name: 'Lecture',
filePathPattern: `lectures/**/*.mdx`,
contentType: 'mdx',
fields: {
title: { type: 'string', required: true },
date: { type: 'date', required: true },
},
computedFields: {
slug: { type: 'string', resolve: page => page._raw.flattenedPath.slice("exams/".length) },
slug: { type: 'string', resolve: page => page._raw.flattenedPath.slice("lectures/".length) },
},
}))

export default makeSource({
contentDirPath: 'content',
documentTypes: [Page, Homework, Lecture, Exam],
documentTypes: [Page, Homework, Assessment, Lecture],
mdx: {
rehypePlugins: [rehypePrism],
},
Expand Down

0 comments on commit 328836d

Please sign in to comment.