Skip to content

Commit aea47f3

Browse files
committed
feat(frontend/v2): ✨ Add blog page and post details with styling
1 parent 5cfc3f2 commit aea47f3

5 files changed

Lines changed: 218 additions & 2 deletions

File tree

apps/frontend/next-env.d.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/// <reference types="next" />
22
/// <reference types="next/image-types/global" />
3-
import './.next/types/routes.d.ts';
3+
import './.next/dev/types/routes.d.ts';
44

55
// NOTE: This file should not be edited
66
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import Wrapper from '@/components/layout/Wrapper';
2+
import styles from '@/styles/Blog.module.css';
3+
import directus from '@/util/directus';
4+
import { readItem } from '@directus/sdk';
5+
import { Box, Group, Text, Tooltip } from '@mantine/core';
6+
import { IconCalendar } from '@tabler/icons-react';
7+
import { Metadata, ResolvingMetadata } from 'next';
8+
import { Locale } from 'next-intl';
9+
import { getFormatter, setRequestLocale } from 'next-intl/server';
10+
import { notFound } from 'next/navigation';
11+
export const dynamic = 'force-static';
12+
export const revalidate = 86400; // 24h minutes
13+
14+
async function getPost(slug: string): Promise<{
15+
slug: string;
16+
status: 'published';
17+
user_created: { display_name?: string };
18+
title: string;
19+
content: string;
20+
thumbnail: string;
21+
summary: string;
22+
hero_image: string;
23+
published_at: string;
24+
}> {
25+
try {
26+
const post = await directus.request(
27+
readItem('blog', slug, { fields: ['*', { slug, user_created: ['display_name'] }] }),
28+
);
29+
30+
return post as any;
31+
} catch (error) {
32+
notFound();
33+
}
34+
}
35+
36+
export async function generateMetadata(
37+
{ params }: { params: Promise<{ locale: Locale; slug: string }> },
38+
parent: ResolvingMetadata,
39+
): Promise<Metadata> {
40+
const { slug } = await params;
41+
42+
const post = await getPost(slug);
43+
44+
return {
45+
title: post.title,
46+
description: post.summary,
47+
authors: [{ name: post.user_created.display_name || 'BuildTheEarth' }],
48+
openGraph: { images: [`${directus.url}assets/${post.thumbnail}`] },
49+
};
50+
}
51+
52+
export default async function Page({ params }: { params: Promise<{ locale: Locale; slug: string }> }) {
53+
const locale = (await params).locale;
54+
setRequestLocale(locale);
55+
const formatter = await getFormatter();
56+
57+
const post = await getPost((await params).slug);
58+
59+
return (
60+
<Wrapper offsetHeader={false} head={{ title: post.title, src: `${directus.url}assets/${post.hero_image}` }}>
61+
<Box dangerouslySetInnerHTML={{ __html: post.content }} className={styles.blogContent} />
62+
63+
<Group wrap="nowrap" gap={10} mt="xs" mb="md" justify="flex-end">
64+
<Tooltip label={'Published on ' + formatter.dateTime(new Date(post.published_at), { dateStyle: 'medium' })}>
65+
<IconCalendar size={16} />
66+
</Tooltip>
67+
<Text size="sm" c="dimmed">
68+
{formatter.dateTime(new Date(post.published_at), { dateStyle: 'medium' })} /{' '}
69+
{post.user_created.display_name || 'BuildTheEarth'}
70+
</Text>
71+
</Group>
72+
</Wrapper>
73+
);
74+
}
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
import LinkButton from '@/components/core/LinkButton';
2+
import Wrapper from '@/components/layout/Wrapper';
3+
import directus from '@/util/directus';
4+
import { readItems } from '@directus/sdk';
5+
import { Card, CardSection, Group, Image, SimpleGrid, Text, Title, Tooltip } from '@mantine/core';
6+
import { IconCalendar, IconChevronRight } from '@tabler/icons-react';
7+
import { Metadata } from 'next';
8+
import { Locale } from 'next-intl';
9+
import { getFormatter, setRequestLocale } from 'next-intl/server';
10+
11+
export const dynamic = 'force-static';
12+
export const revalidate = 86400; // 24h
13+
14+
export const metadata: Metadata = {
15+
title: 'Blog',
16+
description:
17+
'Discover the latest news, updates, and stories from BuildTheEarth. Explore our blog to learn about our projects, events, and the passionate community behind our mission to recreate Earth in Minecraft.',
18+
};
19+
20+
export default async function Page({ params }: { params: Promise<{ locale: Locale }> }) {
21+
const locale = (await params).locale;
22+
setRequestLocale(locale);
23+
const formatter = await getFormatter();
24+
25+
const posts: {
26+
slug: string;
27+
title: string;
28+
thumbnail: string;
29+
summary: string;
30+
published_at: string;
31+
user_created: { display_name?: string };
32+
}[] = (await directus.request(
33+
readItems('blog', {
34+
limit: 99,
35+
sort: '-published_at',
36+
fields: ['slug', 'title', 'thumbnail', 'summary', 'published_at', { user_created: ['display_name'] }],
37+
}),
38+
)) as any[];
39+
40+
return (
41+
<Wrapper offsetHeader={false} head={{ title: 'Blog', src: '/placeholders/home.png' }}>
42+
<Text>
43+
Ever wonder how we build our projects, what goes on behind the scenes, or want to stay updated with our latest
44+
news? Then this is the place! Dive into a world of creativity, innovation, and community spirit as we share
45+
stories, updates, and insights from our journey to recreate Earth in Minecraft.
46+
</Text>
47+
<Title order={2} mb="md" mt="lg">
48+
Latest Posts
49+
</Title>
50+
<SimpleGrid cols={3}>
51+
{posts.map((post) => (
52+
<Card
53+
key={post.slug + '-lg'}
54+
withBorder
55+
maw={{ base: '60vw', sm: '40vw', md: '32vw', xl: '20vw' }}
56+
radius={0}
57+
className="anim"
58+
>
59+
<CardSection>
60+
<Image src={`${directus.url}assets/${post.thumbnail}?height=320`} height={160} alt="Thumbnail Image" />
61+
</CardSection>
62+
63+
<Text
64+
fw={700}
65+
fz="xl"
66+
mt="md"
67+
style={{
68+
display: '-webkit-box',
69+
WebkitLineClamp: 2,
70+
lineClamp: 2,
71+
WebkitBoxOrient: 'vertical',
72+
overflow: 'hidden',
73+
}}
74+
>
75+
{post.title}
76+
</Text>
77+
<Group wrap="nowrap" gap={10} mt="xs" mb="md">
78+
<Tooltip
79+
label={'Published on ' + formatter.dateTime(new Date(post.published_at), { dateStyle: 'medium' })}
80+
>
81+
<IconCalendar size={16} />
82+
</Tooltip>
83+
<Text size="xs" c="dimmed">
84+
{formatter.dateTime(new Date(post.published_at), { dateStyle: 'medium' })} /{' '}
85+
{post.user_created.display_name || 'BuildTheEarth'}
86+
</Text>
87+
</Group>
88+
<Text
89+
size="sm"
90+
c="dimmed"
91+
style={{
92+
minHeight: '5lh',
93+
maxHeight: '5lh',
94+
display: '-webkit-box',
95+
WebkitLineClamp: 5,
96+
lineClamp: 5,
97+
WebkitBoxOrient: 'vertical',
98+
overflow: 'hidden',
99+
}}
100+
>
101+
{post.summary}
102+
</Text>
103+
<LinkButton
104+
href={`/blog/${post.slug}`}
105+
variant="subtle"
106+
color="indigo"
107+
rightSection={<IconChevronRight size={12} />}
108+
mt="md"
109+
>
110+
Read more
111+
</LinkButton>
112+
</Card>
113+
))}
114+
</SimpleGrid>
115+
</Wrapper>
116+
);
117+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
.blogContent img {
2+
max-width: 100%;
3+
height: auto;
4+
display: block;
5+
border-radius: var(--mantine-radius-sm);
6+
}
7+
8+
.blogContent pre {
9+
background-color: light-dark(var(--mantine-color-gray-0), var(--mantine-color-dark-7));
10+
padding: var(--mantine-spacing-md);
11+
border-radius: var(--mantine-radius-sm);
12+
overflow-x: auto;
13+
font-family: 'Source Code Pro', monospace;
14+
font-size: 14px;
15+
}
16+
17+
.blogContent blockquote {
18+
padding-left: var(--mantine-spacing-md);
19+
margin-left: 0;
20+
margin-top: var(--mantine-spacing-lg);
21+
margin-bottom: var(--mantine-spacing-lg);
22+
color: light-dark(var(--mantine-color-dark-6), var(--mantine-color-dark-2));
23+
font-style: italic;
24+
border-left: 4px solid var(--mantine-color-dark-6);
25+
}

apps/frontend/src/util/directus.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { createDirectus, rest } from '@directus/sdk';
22

33
const directus = createDirectus(process.env.CMS_URL!).with(
4-
rest({ onRequest: (options) => ({ ...options, cache: 'force-cache', tag: 'directus' }) }),
4+
rest({ onRequest: (options) => ({ ...options, cache: 'no-cache', tag: 'directus' }) }),
55
);
66

77
export default directus;

0 commit comments

Comments
 (0)