blog post page

This commit is contained in:
m5r
2021-08-28 00:08:38 +08:00
parent 9248e5fbbe
commit c75ed023a1
12 changed files with 455 additions and 153 deletions

View File

@ -0,0 +1,12 @@
import Image from "next/image";
export default function Avatar({ name, picture }: any) {
return (
<div className="flex items-center">
<div className="w-12 h-12 relative mr-4">
<Image src={picture.url} layout="fill" className="rounded-full" alt={name} />
</div>
<div className="text-xl font-bold">{name}</div>
</div>
);
}

View File

@ -0,0 +1,28 @@
import { Image } from "react-datocms";
import clsx from "clsx";
import Link from "next/link";
export default function CoverImage({ title, responsiveImage, slug }: any) {
const image = (
<Image
data={{
...responsiveImage,
alt: `Cover Image for ${title}`,
}}
className={clsx("shadow-small", {
"hover:shadow-medium transition-shadow duration-200": slug,
})}
/>
);
return (
<div className="sm:mx-0">
{slug ? (
<Link href={`/posts/${slug}`}>
<a aria-label={title}>{image}</a>
</Link>
) : (
image
)}
</div>
);
}

View File

@ -0,0 +1,10 @@
const formatter = Intl.DateTimeFormat("en-US", {
day: "2-digit",
month: "short",
year: "numeric",
});
export default function DateComponent({ dateString }: any) {
const date = new Date(dateString);
return <time dateTime={dateString}>{formatter.format(date)}</time>;
}

View File

@ -0,0 +1,82 @@
import { Link, Routes } from "blitz";
import PostPreview from "./post-preview";
import type { Post } from "../../../integrations/datocms";
type Props = {
posts: Post[];
};
const formatter = Intl.DateTimeFormat("en-US", {
day: "2-digit",
month: "short",
year: "numeric",
});
export default function MoreStories({ posts }: Props) {
return (
<aside>
<div className="relative max-w-6xl mx-auto px-4 sm:px-6">
<div className="pb-12 md:pb-20">
<div className="max-w-3xl mx-auto">
<h4 className="h4 font-mackinac mb-8">Related articles</h4>
{/* Articles container */}
<div className="grid gap-4 sm:gap-6 sm:grid-cols-2">
{posts.map((post) => (
<article key={post.slug} className="relative group p-6 text-white">
<figure>
<img
className="absolute inset-0 w-full h-full object-cover opacity-50 group-hover:opacity-75 transition duration-700 ease-out"
src={post.coverImage.responsiveImage.src}
width="372"
height="182"
alt="Related post"
/>
<div
className="absolute inset-0 bg-primary-500 opacity-75 group-hover:opacity-50 transition duration-700 ease-out"
aria-hidden="true"
/>
</figure>
<div className="relative flex flex-col h-full">
<header className="flex-grow">
<Link href={Routes.PostPage({ slug: post.slug })}>
<a className="hover:underline">
<h3 className="text-lg font-mackinac font-bold tracking-tight mb-2">
{post.title}
</h3>
</a>
</Link>
<div className="text-sm opacity-80">
{formatter.format(new Date(post.date))}
</div>
</header>
<footer>
{/* Author meta */}
<div className="flex items-center text-sm mt-5">
<a href="#0">
<img
className="rounded-full flex-shrink-0 mr-3"
src={post.author.picture.url}
width="32"
height="32"
alt={post.author.name}
/>
</a>
<div>
<span className="opacity-75">By </span>
<span className="font-medium hover:underline">
{post.author.name}
</span>
</div>
</div>
</footer>
</div>
</article>
))}
</div>
</div>
</div>
</div>
</aside>
);
}

View File

@ -0,0 +1,16 @@
import styles from "../styles/post-body.module.css";
type Props = {
content: string;
};
export default function PostBody({ content }: Props) {
return (
<div className="max-w-2xl mx-auto">
<div
className={`prose prose-lg prose-blue ${styles.markdown}`}
dangerouslySetInnerHTML={{ __html: content }}
/>
</div>
);
}

View File

@ -0,0 +1,25 @@
import Link from "next/link";
import Avatar from "./avatar";
import Date from "./date";
import CoverImage from "./cover-image";
export default function PostPreview({ title, coverImage, date, excerpt, author, slug }: any) {
return (
<div>
<div className="mb-5">
<CoverImage slug={slug} title={title} responsiveImage={coverImage.responsiveImage} />
</div>
<h3 className="text-3xl mb-3 leading-snug">
<Link href={`/posts/${slug}`}>
<a className="hover:underline">{title}</a>
</Link>
</h3>
<div className="text-lg mb-4">
<Date dateString={date} />
</div>
<p className="text-lg leading-relaxed mb-4">{excerpt}</p>
<Avatar name={author.name} picture={author.picture} />
</div>
);
}

View File

@ -0,0 +1,3 @@
export default function SectionSeparator() {
return <hr className="border-accent-2 mt-28 mb-24" />;
}

View File

@ -1,8 +1,13 @@
import { BlitzPage, GetStaticPaths, GetStaticProps, Head, useRouter } from "blitz";
import type { BlitzPage, GetStaticPaths, GetStaticProps } from "blitz";
import { Head, useRouter } from "blitz";
import ErrorPage from "next/error";
import type { Post } from "integrations/datocms";
import { getAllPostsWithSlug, getPostAndMorePosts, markdownToHtml } from "integrations/datocms";
import Header from "../../../landing-page/components/header";
import PostBody from "../../components/post-body";
import SectionSeparator from "../../components/section-separator";
import MoreStories from "../../components/more-stories";
type Props = {
post: Post;
@ -10,15 +15,96 @@ type Props = {
preview: boolean;
};
const formatter = Intl.DateTimeFormat("en-US", {
day: "2-digit",
month: "short",
year: "numeric",
});
const PostPage: BlitzPage<Props> = ({ post, morePosts, preview }) => {
const router = useRouter();
if (!router.isFallback && !post?.slug) {
return <ErrorPage statusCode={404} />;
}
console.log("post", post);
// TODO
return (
<div className="flex flex-col min-h-screen overflow-hidden">
<Header />
<main className="flex-grow">
<section className="relative">
{/* Background image */}
<div className="absolute inset-0 h-128 pt-16 box-content">
<img
className="absolute inset-0 w-full h-full object-cover opacity-25"
src={post.coverImage.responsiveImage.src}
width={post.coverImage.responsiveImage.width}
height={post.coverImage.responsiveImage.height}
alt={post.coverImage.responsiveImage.alt ?? `${post.title} cover image`}
/>
<div
className="absolute inset-0 bg-gradient-to-t from-white dark:from-gray-900"
aria-hidden="true"
/>
</div>
<div className="relative max-w-6xl mx-auto px-4 sm:px-6">
<div className="pt-32 pb-12 md:pt-40 md:pb-20">
<div className="max-w-3xl mx-auto">
<article>
{/* Article header */}
<header className="mb-8">
{/* Title and excerpt */}
<div className="text-center md:text-left">
<h1 className="h1 font-mackinac mb-4">{post.title}</h1>
<p className="text-xl text-gray-600 dark:text-gray-400">{post.excerpt}</p>
</div>
{/* Article meta */}
<div className="md:flex md:items-center md:justify-between mt-5">
{/* Author meta */}
<div className="flex items-center justify-center">
<img
className="rounded-full flex-shrink-0 mr-3"
src={post.author.picture.url}
width="32"
height="32"
alt="Author 04"
/>
<div>
<span className="text-gray-600 dark:text-gray-400">By </span>
<a
className="font-medium text-gray-800 dark:text-gray-300 hover:underline"
href="#0"
>
{post.author.name}
</a>
<span className="text-gray-600 dark:text-gray-400">
{" "}
· {formatter.format(new Date(post.date))}
</span>
</div>
</div>
</div>
</header>
<hr className="w-5 h-px pt-px bg-gray-400 dark:bg-gray-500 border-0 mb-8" />
{/* Article content */}
<div className="text-lg text-gray-600 dark:text-gray-400">
<PostBody content={post.content} />
</div>
</article>
<SectionSeparator />
{morePosts.length > 0 && <MoreStories posts={morePosts} />}
</div>
</div>
</div>
</section>
</main>
</div>
);
/*return (
<Layout preview={preview}>
<Container>
@ -49,8 +135,6 @@ const PostPage: BlitzPage<Props> = ({ post, morePosts, preview }) => {
</Container>
</Layout>
);*/
return null;
};
export default PostPage;

View File

@ -0,0 +1,18 @@
.markdown {
@apply text-lg leading-relaxed;
}
.markdown p,
.markdown ul,
.markdown ol,
.markdown blockquote {
@apply my-6;
}
.markdown h2 {
@apply text-3xl mt-12 mb-4 leading-snug;
}
.markdown h3 {
@apply text-2xl mt-8 mb-4 leading-snug;
}