Skip to content

Commit

Permalink
feat: 1st draft of education page
Browse files Browse the repository at this point in the history
  • Loading branch information
mwskwong committed Dec 26, 2024
1 parent addbacc commit d6ac541
Show file tree
Hide file tree
Showing 4 changed files with 248 additions and 15 deletions.
8 changes: 0 additions & 8 deletions postcss.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,29 +36,21 @@ const config = {
'.rt-TextArea',
'.rt-TextField',
'.rt-ThemePanel',
"[data-accent-color='amber']",
"[data-accent-color='blue']",
"[data-accent-color='bronze']",
"[data-accent-color='brown']",
"[data-accent-color='crimson']",
"[data-accent-color='cyan']",
"[data-accent-color='gold']",
"[data-accent-color='grass']",
"[data-accent-color='green']",
"[data-accent-color='iris']",
"[data-accent-color='jade']",
"[data-accent-color='lime']",
"[data-accent-color='mint']",
"[data-accent-color='orange']",
"[data-accent-color='pink']",
"[data-accent-color='plum']",
"[data-accent-color='purple']",
"[data-accent-color='red']",
"[data-accent-color='ruby']",
"[data-accent-color='sky']",
"[data-accent-color='teal']",
"[data-accent-color='tomato']",
"[data-accent-color='violet']",
"[data-accent-color='yellow']",
"[data-accent-color='mauve']",
"[data-accent-color='olive']",
Expand Down
194 changes: 190 additions & 4 deletions src/app/education/page.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,195 @@
const EducationPage = () => {
import '@radix-ui/themes/tokens/colors/gold.css';
import '@radix-ui/themes/tokens/colors/amber.css';
import '@radix-ui/themes/tokens/colors/ruby.css';
import '@radix-ui/themes/tokens/colors/violet.css';
import '@radix-ui/themes/tokens/colors/blue.css';
import '@radix-ui/themes/tokens/colors/cyan.css';
import '@radix-ui/themes/tokens/colors/jade.css';
import '@radix-ui/themes/tokens/colors/lime.css';

import {
Badge,
Button,
Card,
Container,
Flex,
Grid,
Heading,
Section,
Text,
} from '@radix-ui/themes';
import { IconArrowLeft } from '@tabler/icons-react';
import { type Metadata } from 'next';
import Image from 'next/image';
import Link from 'next/link';
import { type BreadcrumbList, type Graph } from 'schema-dts';

import { Footer } from '@/components/footer';
import * as Timeline from '@/components/timeline';
import { firstName, lastName } from '@/constants/me';
import { routes, siteUrl } from '@/constants/site-config';
import { getCourseCategories, getCourses, getEducations } from '@/lib/queries';
import { dateFormatter } from '@/lib/utils';

const colors = [
'gold',
'amber',
'ruby',
'violet',
'blue',
'cyan',
'jade',
'lime',
'gray',
] as const;

const EducationPage = async () => {
const [educations, courseCategories = [], courses] = await Promise.all([
getEducations(),
getCourseCategories(),
getCourses(),
]);

const courseCategoryColors = courseCategories.reduce<
Record<string, (typeof colors)[number]>
>(
(acc, category, index) => ({
...acc,
[category]: colors[index] ?? 'gray',
}),
{},
);

return (
<div style={{ height: 3000 }}>
<h1>Education</h1>
</div>
<>
<Container>
<main>
<Section>
<Button asChild highContrast size="3" variant="ghost">
<Link href={routes.home.pathname}>
<IconArrowLeft size={20} />
{firstName} {lastName}
</Link>
</Button>
<Heading mt="2" size="9">
Education
</Heading>
<Timeline.Root mt="8">
{educations.map(
({
id,
from,
to,
program,
school,

supportingDocuments,
}) => (
<Timeline.Item
key={id}
from={new Date(from)}
media={supportingDocuments}
organization={school}
title={program}
to={to ? new Date(to) : undefined}
/>
),
)}
</Timeline.Root>
</Section>
<Section>
<Heading as="h2" size="8">
Self-studies
</Heading>
<Grid columns={{ sm: '2', md: '3' }} gap="5" mt="8">
{courses.map(
({
id,
name,
institution,
description,
completedOn,
certificate,
categories,
}) => (
<Card key={id} asChild>
<a href={certificate} rel="noopener" target="_blank">
{institution?.logo ? (
<Flex align="center" justify="between">
<Image
alt={institution.name}
className="object-scale-down"
height={24}
src={institution.logo}
width={24}
/>
<Text>{institution.name}</Text>
</Flex>
) : null}
<Heading as="h3" mt="4" size="4">
{name}
</Heading>
<Text as="p" color="gray" mt="2" size="2">
{dateFormatter.format(new Date(completedOn))}
</Text>
<Text as="p" className="line-clamp-3" mt="4">
{description}
</Text>
{categories ? (
<Flex gap="3" mt="4">
{categories.map((category) => (
<Badge
key={category}
color={courseCategoryColors[category]}
size="3"
>
{category}
</Badge>
))}
</Flex>
) : null}
</a>
</Card>
),
)}
</Grid>
</Section>
</main>
<Footer />
</Container>
<script
dangerouslySetInnerHTML={{
__html: JSON.stringify({
'@context': 'https://schema.org',
'@graph': [
{
'@type': 'BreadcrumbList',
itemListElement: [
{
'@type': 'ListItem',
name: routes.home.name,
item: siteUrl,
position: 1,
},
{
'@type': 'ListItem',
name: routes.education.name,
position: 2,
},
],
name: 'Breadcrumbs',
} satisfies BreadcrumbList,
],
} satisfies Graph),
}}
type="application/ld+json"
/>
</>
);
};

export const metadata = {
title: routes.education.name,
} satisfies Metadata;

export default EducationPage;
22 changes: 19 additions & 3 deletions src/lib/contentful-types.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -62,9 +62,7 @@ export interface OrganizationSkeleton {
fields: {
name: EntryFieldTypes.Symbol;
url?: EntryFieldTypes.Symbol;
logoUniversal?: EntryFieldTypes.AssetLink;
logoLight?: EntryFieldTypes.AssetLink;
logoDark?: EntryFieldTypes.AssetLink;
logo?: EntryFieldTypes.AssetLink;
};
}

Expand All @@ -79,3 +77,21 @@ export interface ArticleSkeleton {
content?: EntryFieldTypes.Text;
};
}
export type CourseCategory =
| 'Database'
| 'Desktop Virtualization'
| 'Development'
| 'DevOps'
| 'Marketing'
| 'Operating System'
| 'Project Management'
| 'Security';
export interface CourseSkeleton {
contentTypeId: 'course';
fields: {
name: EntryFieldTypes.Symbol;
institution: EntryFieldTypes.EntryLink<OrganizationSkeleton>;
certificate?: EntryFieldTypes.AssetLink;
categories?: EntryFieldTypes.Array<EntryFieldTypes.Symbol<CourseCategory>>;
};
}
39 changes: 39 additions & 0 deletions src/lib/queries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import { personalPortrait, resume } from '@/constants/contentful-ids';
import { contentful, prisma } from './clients';
import {
type ArticleSkeleton,
type CourseCategory,
type CourseSkeleton,
type EducationSkeleton,
type ExperienceSkeleton,
type ProjectSkeleton,
Expand Down Expand Up @@ -200,3 +202,40 @@ export const getContributedProjects = async () => {
`https:${item.fields.logo.fields.file.url}`,
}));
};

export const getCourseCategories = async () => {
'use cache';

const course = await contentful.getContentType('course');
return course.fields
.find(({ id }) => id === 'categories')
?.items?.validations[0]?.in?.toSorted() as CourseCategory[] | undefined;
};

export const getCourses = async () => {
'use cache';

const { items } = await contentful.getEntries<CourseSkeleton>({
content_type: 'course',
order: ['fields.name'],
});

return items.map((item) => ({
id: item.sys.id,
...item.fields,
institution: item.fields.institution && {
id: item.fields.institution.sys.id,
name: item.fields.institution.fields.name,
logo:
item.fields.institution.fields.logo?.fields.file &&
`https:${item.fields.institution.fields.logo.fields.file.url}`,
},
certificate:
item.fields.certificate?.fields.file &&
`https:${item.fields.certificate.fields.file.url}`,
categories: item.fields.categories,
description:
'This five-day instructor-led course provides students who administer and maintain SQL Server databases with the knowledge and skills to administer a SQL server database infrastructure. Additionally, it will be of use to individuals who develop applications that deliver content from SQL Server databases.', // TODO: real description
completedOn: new Date().toISOString(),
}));
};

0 comments on commit d6ac541

Please sign in to comment.