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

Move to app-folder routing and React Server Components #470

Merged
merged 14 commits into from
Feb 20, 2023
Merged
4 changes: 4 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"typescript.tsdk": "node_modules/typescript/lib",
"typescript.enablePromptUseWorkspaceTsdk": true
}
125 changes: 125 additions & 0 deletions app/[...path]/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
import PostCard from "shared/post-card";
import PressReleaseListing from "shared/press-releases";
import { Author } from "shared/author";
import { BlogPost, PostMetadata, stripBlogPostBody } from "shared/post";
import {
getAllAuthors,
getAllBlogPosts,
getAllPressReleases,
} from "shared/site-data";
import { markdownToHTML } from "shared/utils";
import { Metadata } from "next";
import { notFound } from "next/navigation";

type Params = {
path: string[];
};

type Props = {
params: Params;
};

const Post = ({ params }: Props) => {
const post = postForPath(params.path) || notFound();
const authors = getAllAuthors();
const author = authors.find((a) => a.id === post.authorId)!;
const otherPosts = getAllBlogPosts()
.filter((p) => p.path !== post.path)
.map(stripBlogPostBody)
.slice(0, 3);
const authorOf = (post: PostMetadata) =>
authors.find((a) => a.id === post.authorId)!;

return (
<div className="post-listing-row">
<div className="main-post">
<PostBody post={post} author={author} />
</div>

<div className="press-release-box">
<PressReleaseListing />
</div>

{otherPosts.map((post) => (
<div className="post-listing-post" key={post.path}>
<PostCard post={post} author={authorOf(post)} />
</div>
))}
</div>
);
};

const PostBody = ({ post, author }: { post: BlogPost; author: Author }) => {
const formatDate = (stamp: string) =>
new Date(stamp).toLocaleDateString("cs-CZ", { dateStyle: "medium" });
return (
<div className="post-container" lang={post.lang}>
<img
alt=""
loading="lazy"
width="100%"
style={{ borderTopLeftRadius: 10, borderTopRightRadius: 10 }}
src={post.coverImageUrl}
/>

<div className="post-wrapper">
<div className="post-metadata">
{formatDate(post.date)}
{" • "}
<a className="post-author" href={`mailto:${author.email}`}>
{author.name}
</a>
{/* TODO: Language versions */}
</div>

<h1 className="post-title">{post.title}</h1>
<p className="post-perex">{post.description}</p>

<div
className="post-content"
dangerouslySetInnerHTML={{ __html: markdownToHTML(post.body) }}
/>

<a className="post-button" href={"/"}>
<img src="/arrow-light.svg" alt="" />
Zpět na všechny články
</a>
</div>
</div>
);
};

//
// Support
//

const postForPath = (path: string[]) => {
const mergedPath = "/" + path.join("/");
const allPosts = [...getAllBlogPosts(), ...getAllPressReleases()];
return allPosts.find((post) => post.path === mergedPath);
};

export async function generateStaticParams(): Promise<Params[]> {
const allPosts = [...getAllBlogPosts(), ...getAllPressReleases()];
return allPosts.map((post) => ({
path: post.path.split("/").slice(1),
}));
}

export async function generateMetadata({ params }: Props): Promise<Metadata> {
const post = postForPath(params.path) || notFound();
return {
title: post.title,
openGraph: {
title: post.title,
description: post.description,
images: [
{
url: post.coverImageUrl,
},
],
},
};
}

export default Post;
File renamed without changes.
35 changes: 35 additions & 0 deletions app/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import Footer from "app/footer";
import NavigationBar from "app/navigation-bar";
import "../global.css";

type Props = {
children: React.ReactNode;
};

export default function RootLayout({ children }: Props) {
return (
<html>
<head>
<link rel="shortcut icon" type="image/png" href="/favicon.png" />
<link
rel="alternate"
type="application/rss+xml"
title="RSS"
href="https://blog.cesko.digital/rss.xml"
></link>
<script
data-domain="blog.cesko.digital"
src="https://plausible.io/js/plausible.js"
defer
/>
</head>
<body>
<div className="main-wrapper">
<NavigationBar />
<div className="content-wrapper">{children}</div>
<Footer />
</div>
</body>
</html>
);
}
28 changes: 28 additions & 0 deletions app/navigation-bar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
"use client";

import { usePathname } from "next/navigation";
import Link from "next/link";

const NavigationBar = () => {
const pathName = usePathname();
return (
<div className="header-wrapper">
<div className="navigation-bar">
<div>
<a href={"https://cesko.digital"} className="logo" />
</div>
<div>
<div className="toolbar-links">
{pathName !== "/" && (
<Link href="/" className="toolbar-link">
← Zpět na všechny články
</Link>
)}
</div>
</div>
</div>
</div>
);
};

export default NavigationBar;
50 changes: 50 additions & 0 deletions app/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { PostMetadata } from "shared/post";
import { getAllAuthors, getAllBlogPosts } from "shared/site-data";
import PostCard from "shared/post-card";
import PressReleaseListing from "shared/press-releases";
import { Metadata } from "next";

const Home = () => {
const authors = getAllAuthors();
const [firstPost, ...otherPosts] = getAllBlogPosts();
const authorOf = (post: PostMetadata) =>
authors.find((a) => a.id === post.authorId)!;
return (
<div className="post-listing-row">
<div className="main-post">
<PostCard
post={firstPost}
author={authorOf(firstPost)}
showCover={true}
/>
</div>

<div className="press-release-box">
<PressReleaseListing />
</div>

{otherPosts.map((post) => (
<div className="post-listing-post" key={post.path}>
<PostCard post={post} author={authorOf(post)} />
</div>
))}
</div>
);
};

export const metadata: Metadata = {
title: "Blog Česko.Digital",
openGraph: {
title: "Blog Česko.Digital",
description: "Skrz jedničky a nuly měníme Česko k lepšímu",
locale: "cs-CZ",
type: "website",
images: [
{
url: "https://data.cesko.digital/img/172a1526.png",
},
],
},
};

export default Home;
21 changes: 0 additions & 21 deletions components/client-render.tsx

This file was deleted.

50 changes: 0 additions & 50 deletions components/layout.tsx

This file was deleted.

27 changes: 0 additions & 27 deletions components/navigation-bar.tsx

This file was deleted.

40 changes: 0 additions & 40 deletions components/post-listing.tsx

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ title: Volební kalkulačka pomůže s výběrem prezidenta
author: zoul
cover: https://data.cesko.digital/web/sections/gallery/Prezidentskavolebnikalkulacka2023_.png
date: 2023-01-04-11-55
slug: volebni-kalkulacka-pro prezidentske-volby-2023
slug: volebni-kalkulacka-pro-prezidentske-volby-2023
description: Praha, 2.ledna 2023 - V lednu 2023 čeká Českou republiku volba
prezidenta. Volební kalkulačka jako již tradičně nabídne možnost porovnat
postoje kandidátů, tentokrát pro hlavu státu. Nezávislý přehledný online test
Expand Down
Loading