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

Prettify courses #26

Merged
merged 1 commit into from Mar 21, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions package.json
Expand Up @@ -33,6 +33,7 @@
"@next-auth/prisma-adapter": "^1.0.5",
"@prisma/client": "^4.9.0",
"@tailwindcss/forms": "^0.5.3",
"@tailwindcss/line-clamp": "^0.4.2",
"@tanstack/react-query": "4.23.0",
"@tanstack/react-query-devtools": "^4.23.0",
"@trpc/client": "^10.9.0",
Expand Down
184 changes: 130 additions & 54 deletions src/pages/app/index.tsx
@@ -1,7 +1,9 @@
import { QueueListIcon, WindowIcon } from '@heroicons/react/24/solid';
import { Course, User, UserCourse } from '@prisma/client';
import clsx from 'clsx';
import { getServerSession } from 'next-auth';
import Link from 'next/link';
import { FC } from 'react';
import { FC, useState } from 'react';
import { serialize } from 'superjson';

import { Button } from '~/components/Button';
Expand All @@ -17,66 +19,127 @@ type Props = {
recommendedCourses: (Course & { course: Course })[];
};

const App: FC<Props> = ({ user, userCourses, recommendedCourses }) => {
const courses = userCourses?.length ? userCourses : recommendedCourses;
const App: FC<Props> = ({ userCourses, recommendedCourses }) => {
const [layout, setLayout] = useState<'grid' | 'list'>('grid');
const [filter, setFilter] = useState('');
const courses: Array<{ course: Course }> = userCourses?.length
? userCourses
: recommendedCourses;

return (
<>
<Header />
<div className="grid w-full gap-8 px-4 py-8 mx-auto max-w-7xl">
<H1>Hey there, {user?.name?.split(' ')[0]}!</H1>
<p className="text-4xl">
{userCourses?.length > 0
? "Here are your courses that you've signed up for."
: "You haven't yet signed up for any courses. Here are some we recommend!"}
</p>
<div className="grid grid-cols-4 gap-4">
{courses?.map(
(
userCourse:
| Props['userCourses'][-1]
| Props['recommendedCourses'][-1],
index: number,
) => (
<div
key={userCourse.id}
className="border border-gray-200 rounded"
>
<div className="grid gap-2">
<H1>Your Courses</H1>
<p className="leading-relaxed">
Welcome to your courses page! This is where you can access all the
courses you&lsquo;ve enrolled in, track your progress, and complete
your learning objectives. Feel free to explore your courses and take
advantage of the learning resources available to you. To explore
more courses than you&lsquo;ve enrolled in, be sure to visit the{' '}
<Link className="underline" href="/courses">
course catalog
</Link>
.
</p>
</div>
<div className="flex items-center justify-between gap-4 leading-none">
<label className="grid w-full gap-1 text-sm">
Search courses...
<input
value={filter}
onChange={(e) => setFilter(e.target.value)}
className="rounded border-neutral-300"
type="text"
placeholder="tailWIN"
/>
</label>
<div className="flex items-center mt-6 leading-none h-[43px] border border-neutral-300 rounded">
<button
className={clsx(
layout === 'list' && 'shadow-inner bg-neutral-100',
'h-full p-1 text-neutral-500 shrink-0 aspect-square',
)}
onClick={() => {
setLayout('list');
}}
>
<QueueListIcon className="mx-auto" width={24} />
</button>
<button
className={clsx(
layout === 'grid' && 'shadow-inner bg-neutral-100',
'h-full p-1 border-l text-neutral-500 shrink-0 aspect-square',
)}
onClick={() => {
setLayout('grid');
}}
>
<WindowIcon className="mx-auto" width={24} />
</button>
</div>
</div>
<div
className={clsx(
layout === 'list' && 'grid gap-4',
layout === 'grid' && 'grid grid-cols-3',
)}
>
{courses
?.filter((c) => c.course.name.toLowerCase().includes(filter))
.map(
(
userCourse:
| Props['userCourses'][-1]
| Props['recommendedCourses'][-1],
index: number,
) => (
<div
className="bg-cover aspect-square w-[200px] border-b border-gray-200"
style={{ backgroundImage: `url(${userCourse.course.media})` }}
/>
<div className="grid gap-4 p-4">
<div className="grid gap-2">
<h2 className="text-2xl">{userCourse.course.name}</h2>
{'startedDate' in userCourse && (
<>
<span>
Started on {formatDate(userCourse.startedDate)}
</span>
<progress
className="w-full h-2 overflow-hidden rounded bg-neutral-200"
max={100}
value={userCourse.progress || 0}
/>
</>
key={userCourse.id}
className={clsx(
'gap-4 p-4 border border-gray-200 rounded',
layout === 'list' && 'flex items-center',
layout === 'grid' && 'grid',
)}
>
<div
className="min-w-[200px] bg-cover border-b border-gray-200 rounded shadow shrink-0 aspect-square"
style={{
backgroundImage: `url(${userCourse.course.media})`,
}}
/>
<div className="grid gap-4">
<div className="grid gap-2">
<h2 className="text-2xl font-semibold">
{userCourse.course.name}
</h2>
{'startedDate' in userCourse && (
<>
<span>
Started on {formatDate(userCourse.startedDate)}
</span>
</>
)}
<p className="text-sm line-clamp-3">
{userCourse.course.description}
</p>
</div>
{userCourses[index] ? (
<Link
href={`/app/courses/${userCourses[index]!.course.slug}`}
>
<Button>Dive in!</Button>
</Link>
) : (
<Link href={`/courses/${userCourse.course.slug}/buy`}>
<Button>Buy Course</Button>
</Link>
)}
</div>
{userCourses[index] ? (
<Link
href={`/app/courses/${userCourses[index]!.course.slug}`}
>
<Button>Dive in!</Button>
</Link>
) : (
<Link href={`/courses/${userCourse.course.slug}/buy`}>
<Button>Buy Course</Button>
</Link>
)}
</div>
</div>
),
)}
),
)}
</div>
</div>
</>
Expand All @@ -101,15 +164,28 @@ export const getServerSideProps = async (ctx) => {
startedDate: true,
id: true,
progress: true,
course: { select: { slug: true, id: true, name: true, media: true } },
course: {
select: {
description: true,
slug: true,
id: true,
name: true,
media: true,
},
},
},
});

const userCourses = serialize(rawUserCourses).json;

const recommendedCourses = await prisma.course.findMany({
where: { id: { notIn: rawUserCourses.map((c) => c.course.id) } },
select: { id: true, name: true, media: true, slug: true },
select: {
id: true,
name: true,
media: true,
slug: true,
},
take: 5,
});

Expand Down
2 changes: 1 addition & 1 deletion tailwind.config.js
Expand Up @@ -22,5 +22,5 @@ module.exports = {
},
},
},
plugins: [require('@tailwindcss/forms')],
plugins: [require('@tailwindcss/line-clamp'), require('@tailwindcss/forms')],
};
5 changes: 5 additions & 0 deletions yarn.lock
Expand Up @@ -265,6 +265,11 @@
dependencies:
mini-svg-data-uri "^1.2.3"

"@tailwindcss/line-clamp@^0.4.2":
version "0.4.2"
resolved "https://registry.yarnpkg.com/@tailwindcss/line-clamp/-/line-clamp-0.4.2.tgz#f353c5a8ab2c939c6267ac5b907f012e5ee130f9"
integrity sha512-HFzAQuqYCjyy/SX9sLGB1lroPzmcnWv1FHkIpmypte10hptf4oPUfucryMKovZh2u0uiS9U5Ty3GghWfEJGwVw==

"@tanstack/match-sorter-utils@^8.7.0":
version "8.7.6"
resolved "https://registry.npmjs.org/@tanstack/match-sorter-utils/-/match-sorter-utils-8.7.6.tgz"
Expand Down