Skip to content

Commit

Permalink
feat: events/calendar page and a few minor fixes on components relate…
Browse files Browse the repository at this point in the history
…d to time, calendars, etc (#6266)

* chore: refactor a few utils

* chore: add calendar redirect

* chore: updated constants and i18n

* chore: add failsafe for blog data parsing

* chore: added gcal types

* feat: updated components for Time and new Calendar/Event components

* feat: updated layouts

* chore: home page changes requested by TSC

* chore: explain about api key

* fix: fixed events page

* chore: updated array type usage

* chore: minor fixes

* chore: updated text

* chore: storybook is pain

* chore: make utc small

* Apply suggestions from code review

Co-authored-by: Brian Muenzenmeyer <[email protected]>
Signed-off-by: Claudio W <[email protected]>

* chore: code-review changes

* chore: center content on extra large screens

---------

Signed-off-by: Claudio W <[email protected]>
Co-authored-by: Brian Muenzenmeyer <[email protected]>
  • Loading branch information
ovflowd and bmuenzenmeyer authored Jan 27, 2024
1 parent c6cb417 commit 69753c8
Show file tree
Hide file tree
Showing 45 changed files with 443 additions and 89 deletions.
14 changes: 13 additions & 1 deletion .storybook/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,20 @@ const config: StorybookConfig = {
// `nodevu` is a Node.js-specific package that requires Node.js modules
// this is incompatible with Storybook. So we just mock the module
resolve: { ...config.resolve, alias: { '@nodevu/core': false } },
// We need to configure `node:` APIs as Externals to WebPack
// since essentially they're not supported on the browser
externals: {
'node:fs': 'commonjs fs',
'node:url': 'commonjs url',
'node:path': 'commonjs path',
'node:readline': 'commonjs readline',
},
// Removes Pesky Critical Dependency Warnings due to `next/font`
ignoreWarnings: [e => e.message.includes('Critical dep')],
ignoreWarnings: [
e =>
e.message.includes('Critical dep') ||
e.message.includes('was not found in'),
],
}),
};

Expand Down
6 changes: 5 additions & 1 deletion .storybook/preview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,11 @@ const preview: Preview = {
},
decorators: [
Story => (
<NextIntlClientProvider locale="en" messages={englishLocale}>
<NextIntlClientProvider
locale="en"
timeZone="Etc/UTC"
messages={englishLocale}
>
<NotificationProvider viewportClassName="absolute top-0 left-0 list-none">
<Story />
</NotificationProvider>
Expand Down
3 changes: 2 additions & 1 deletion components/Common/BlogPostCard/index.module.css
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
.container {
@apply max-w-full;
@apply max-w-full
flex-1;
}

.subtitle {
Expand Down
19 changes: 8 additions & 11 deletions components/Common/BlogPostCard/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ import { useTranslations } from 'next-intl';
import type { FC } from 'react';

import AvatarGroup from '@/components/Common/AvatarGroup';
import FormattedTime from '@/components/Common/FormattedTime';
import Preview from '@/components/Common/Preview';
import { Time } from '@/components/Common/Time';
import Link from '@/components/Link';
import { mapBlogCategoryToPreviewType } from '@/util/blogUtils';

Expand All @@ -16,17 +16,17 @@ type BlogPostCardProps = {
title: string;
category: string;
description?: string;
authors: Array<Author>;
date: Date;
slug: string;
authors?: Array<Author>;
date?: Date;
slug?: string;
};

const BlogPostCard: FC<BlogPostCardProps> = ({
title,
slug,
category,
description,
authors,
authors = [],
date,
}) => {
const t = useTranslations();
Expand All @@ -52,15 +52,12 @@ const BlogPostCard: FC<BlogPostCardProps> = ({
{description && <p className={styles.description}>{description}</p>}

<footer className={styles.footer}>
<AvatarGroup avatars={avatars} />
<AvatarGroup avatars={avatars ?? []} />

<div className={styles.author}>
<p>{avatars.map(avatar => avatar.alt).join(', ')}</p>
{avatars && <p>{avatars.map(({ alt }) => alt).join(', ')}</p>}

<Time
date={date}
format={{ day: 'numeric', month: 'short', year: 'numeric' }}
/>
{date && <FormattedTime date={date} />}
</div>
</footer>
</article>
Expand Down
24 changes: 24 additions & 0 deletions components/Common/FormattedTime.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import type { DateTimeFormatOptions } from 'next-intl';
import { useFormatter } from 'next-intl';
import type { FC } from 'react';

import { DEFAULT_DATE_FORMAT } from '@/next.calendar.constants.mjs';

type FormattedTimeProps = {
date: string | Date;
format?: DateTimeFormatOptions;
};

const FormattedTime: FC<FormattedTimeProps> = ({ date, format }) => {
const formatter = useFormatter();

const dateObject = new Date(date);

return (
<time dateTime={dateObject.toISOString()}>
{formatter.dateTime(dateObject, format ?? DEFAULT_DATE_FORMAT)}
</time>
);
};

export default FormattedTime;
File renamed without changes.
18 changes: 0 additions & 18 deletions components/Common/Time/index.tsx

This file was deleted.

21 changes: 21 additions & 0 deletions components/MDX/Calendar/Event/index.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
.event {
@apply flex
w-fit
flex-col
gap-1;

.title {
@apply flex
flex-row
gap-2;

span {
@apply text-sm
font-bold;
}
}

a {
@apply text-sm;
}
}
44 changes: 44 additions & 0 deletions components/MDX/Calendar/Event/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import type { FC } from 'react';

import FormattedTime from '@/components/Common/FormattedTime';
import Link from '@/components/Link';
import { getZoomLink, isZoned } from '@/components/MDX/Calendar/utils';
import type { CalendarEvent } from '@/types';

import styles from './index.module.css';

type EventProps = Pick<
CalendarEvent,
'start' | 'end' | 'summary' | 'location' | 'description'
>;

const Event: FC<EventProps> = ({
start,
end,
description,
summary,
location,
}) => (
<div className={styles.event}>
<div className={styles.title}>
<span>
<FormattedTime
date={isZoned(start) ? start.dateTime : start.date}
format={{ hour: 'numeric', minute: 'numeric' }}
/>
</span>
<span>-</span>
<span>
<FormattedTime
date={isZoned(end) ? end.dateTime : end.date}
format={{ hour: 'numeric', minute: 'numeric' }}
/>
</span>
<small>(UTC)</small>
</div>

<Link href={getZoomLink({ description, location })}>{summary}</Link>
</div>
);

export default Event;
54 changes: 54 additions & 0 deletions components/MDX/Calendar/UpcomingEvents.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import type { FC } from 'react';

import FormattedTime from '@/components/Common/FormattedTime';
import Event from '@/components/MDX/Calendar/Event';
import { getZoomLink, isZoned } from '@/components/MDX/Calendar/utils';
import { CALENDAR_NODEJS_ID } from '@/next.calendar.constants.mjs';
import { getCalendarEvents } from '@/next.calendar.mjs';
import type { CalendarEvent } from '@/types';

import styles from './calendar.module.css';

type GrouppedEntries = Record<string, Array<CalendarEvent>>;

const UpcomingEvents: FC = async () => {
const events = await getCalendarEvents(CALENDAR_NODEJS_ID);

const groupedEntries = events.filter(getZoomLink).reduce((acc, event) => {
const startDate = new Date(
isZoned(event.start) ? event.start.dateTime : event.start.date
);

const datePerDay = startDate.toDateString();

acc[datePerDay] = acc[datePerDay] || [];
acc[datePerDay].push(event);

return acc;
}, {} as GrouppedEntries);

const sortedGroupedEntries = Object.entries(groupedEntries).sort(
([dateA], [dateB]) => new Date(dateA).getTime() - new Date(dateB).getTime()
);

return sortedGroupedEntries.map(([date, entries]) => (
<div key={date} className={styles.events}>
<h4>
<FormattedTime date={date} format={{ day: 'numeric', month: 'long' }} />
</h4>

{entries.map(({ id, start, end, summary, location, description }) => (
<Event
key={id}
start={start}
end={end}
summary={summary}
location={location}
description={description}
/>
))}
</div>
));
};

export default UpcomingEvents;
38 changes: 38 additions & 0 deletions components/MDX/Calendar/UpcomingSummits.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { getTranslations } from 'next-intl/server';
import type { FC } from 'react';

import BlogPostCard from '@/components/Common/BlogPostCard';
import getBlogData from '@/next-data/blogData';

import styles from './calendar.module.css';

const UpcomingSummits: FC = async () => {
const t = await getTranslations();
const { posts } = await getBlogData('events', 0);

const currentDate = new Date();
const filteredPosts = posts.filter(post => post.date >= currentDate);

const fallbackPosts = Array(2).fill({
title: t('components.mdx.upcomingEvents.defaultTitle'),
categories: ['events'],
});

const mappedPosts = fallbackPosts.map((post, key) => {
const actualPost = filteredPosts[key] || post;

return (
<BlogPostCard
key={actualPost.slug || key}
title={actualPost.title}
category={actualPost.categories[0]}
date={actualPost.date}
slug={actualPost.slug}
/>
);
});

return <div className={styles.summits}>{mappedPosts}</div>;
};

export default UpcomingSummits;
17 changes: 17 additions & 0 deletions components/MDX/Calendar/calendar.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
.events {
@apply flex
flex-col
gap-2;

h4 {
@apply text-xl
font-bold;
}
}

.summits {
@apply flex
flex-col
gap-3
md:flex-row;
}
10 changes: 10 additions & 0 deletions components/MDX/Calendar/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import type { CalendarEvent, ZonedCalendarTime } from '@/types';

export const isZoned = (d: object): d is ZonedCalendarTime =>
'dateTime' in d && 'timeZone' in d;

export const getZoomLink = (
event: Pick<CalendarEvent, 'description' | 'location'>
) =>
event.description?.match(/https:\/\/zoom.us\/j\/\d+/)?.[0] ||
event.location?.match(/https:\/\/zoom.us\/j\/\d+/)?.[0];
2 changes: 1 addition & 1 deletion components/withBadge.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import type { FC } from 'react';

import Badge from '@/components/Common/Badge';
import { siteConfig } from '@/next.json.mjs';
import { dateIsBetween } from '@/util/dateIsBetween';
import { dateIsBetween } from '@/util/dateUtils';

const WithBadge: FC<{ section: string }> = ({ section }) => {
const badge = siteConfig.websiteBadges[section];
Expand Down
2 changes: 1 addition & 1 deletion components/withBanner.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import type { FC } from 'react';

import Banner from '@/components/Common/Banner';
import { siteConfig } from '@/next.json.mjs';
import { dateIsBetween } from '@/util/dateIsBetween';
import { dateIsBetween } from '@/util/dateUtils';

const WithBanner: FC<{ section: string }> = ({ section }) => {
const banner = siteConfig.websiteBanners[section];
Expand Down
9 changes: 2 additions & 7 deletions components/withMetaBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,15 @@ import MetaBar from '@/components/Containers/MetaBar';
import GitHub from '@/components/Icons/Social/GitHub';
import Link from '@/components/Link';
import { useClientContext } from '@/hooks/server';
import { DEFAULT_DATE_FORMAT } from '@/next.calendar.constants.mjs';
import { getGitHubBlobUrl } from '@/util/gitHubUtils';

const DATE_FORMAT = {
month: 'short',
day: '2-digit',
year: 'numeric',
} as const;

const WithMetaBar: FC = () => {
const { headings, readingTime, frontmatter, filename } = useClientContext();
const formatter = useFormatter();

const lastUpdated = frontmatter.date
? formatter.dateTime(new Date(frontmatter.date), DATE_FORMAT)
? formatter.dateTime(new Date(frontmatter.date), DEFAULT_DATE_FORMAT)
: undefined;

return (
Expand Down
Loading

0 comments on commit 69753c8

Please sign in to comment.