diff --git a/apps/www/lib/helpers.tsx b/apps/www/lib/helpers.tsx index eb8495ca62..c94a49722f 100644 --- a/apps/www/lib/helpers.tsx +++ b/apps/www/lib/helpers.tsx @@ -7,6 +7,10 @@ export const generateReadingTime = (text: string) => { const readTime = Math.ceil(minutes) return `${readTime} minute read` } +// Helps with the TypeScript issue where filtering doesn't narrows undefined nor null types, check https://github.com/microsoft/TypeScript/issues/16069 +export function isNotNullOrUndefined(value: T | null | undefined): value is T { + return value !== null && value !== undefined +} export function capitalize(word: string) { return word[0].toUpperCase() + word.substring(1).toLowerCase() diff --git a/apps/www/pages/blog/[slug].tsx b/apps/www/pages/blog/[slug].tsx index 1b1fcbd5b8..05de8c920b 100644 --- a/apps/www/pages/blog/[slug].tsx +++ b/apps/www/pages/blog/[slug].tsx @@ -1,4 +1,5 @@ import { NextSeo } from 'next-seo' +import type { GetStaticProps, InferGetStaticPropsType } from 'next' import Image from 'next/image' import Link from 'next/link' import { useRouter } from 'next/router' @@ -10,7 +11,7 @@ import { Badge, Divider, IconChevronLeft } from 'ui' import CTABanner from '~/components/CTABanner' import DefaultLayout from '~/components/Layouts/Default' import BlogLinks from '~/components/LaunchWeek/7/BlogLinks' -import { generateReadingTime } from '~/lib/helpers' +import { generateReadingTime, isNotNullOrUndefined } from '~/lib/helpers' import ShareArticleActions from '~/components/Blog/ShareArticleActions' import useActiveAnchors from '~/hooks/useActiveAnchors' import mdxComponents from '~/lib/mdx/mdxComponents' @@ -18,6 +19,48 @@ import { mdxSerialize } from '~/lib/mdx/mdxSerialize' import { getAllPostSlugs, getPostdata, getSortedPosts } from '~/lib/posts' import { ReactMarkdown } from 'react-markdown/lib/react-markdown' +type Post = ReturnType[number] + +type BlogData = { + title: string + description: string + tags?: string[] + date: string + toc_depth?: number + author: string + image?: string + thumb?: string + youtubeHero?: string + author_url?: string + launchweek?: number + meta_title?: string + meat_description?: string + video?: string +} + +type MatterReturn = { + data: BlogData + content: string +} + +type Blog = { + slug: string + source: string + content: any + toc: any +} + +type BlogPostPageProps = { + prevPost: Post | null + nextPost: Post | null + relatedPosts: (Post & BlogData)[] + blog: Blog & BlogData +} + +type Params = { + slug: string +} + // table of contents extractor const toc = require('markdown-toc') @@ -29,14 +72,18 @@ export async function getStaticPaths() { } } -export async function getStaticProps({ params }: any) { - const filePath = `${params.slug}` +export const getStaticProps: GetStaticProps = async ({ params }) => { + const filePath = `${params?.slug}` const postContent = await getPostdata(filePath, '_blog') - const { data, content } = matter(postContent) + const { data, content } = matter(postContent) as unknown as MatterReturn const mdxSource: any = await mdxSerialize(content) - const relatedPosts = getSortedPosts('_blog', 5, mdxSource.scope.tags) + const relatedPosts = getSortedPosts('_blog', 5, mdxSource.scope.tags).filter( + (post) => post.slug !== filePath + ) as unknown as (BlogData & Post)[] + + console.log({ relatedPosts }) const allPosts = getSortedPosts('_blog') @@ -55,7 +102,7 @@ export async function getStaticProps({ params }: any) { nextPost: currentIndex === allPosts.length ? null : nextPost ? nextPost : null, relatedPosts, blog: { - slug: `${params.slug}`, + slug: `${params?.slug}`, source: content, ...data, content: mdxSource, @@ -65,22 +112,19 @@ export async function getStaticProps({ params }: any) { } } -function BlogPostPage(props: any) { +function BlogPostPage(props: InferGetStaticPropsType) { const content = props.blog.content const authorArray = props.blog.author.split(',') useActiveAnchors('h2, h3, h4', '.prose-toc a') const isLaunchWeek7 = props.blog.launchweek === 7 - const author = [] - for (let i = 0; i < authorArray.length; i++) { - author.push( - // @ts-ignore - authors.find((authors: string) => { - // @ts-ignore - return authors.author_id === authorArray[i] - }) - ) - } + const author = authorArray + .map((authorId) => { + return authors.find((author) => author.author_id === authorId) + }) + .filter(isNotNullOrUndefined) + + const authorUrls = author.map((author) => author?.author_url).filter(isNotNullOrUndefined) const { basePath } = useRouter() @@ -146,15 +190,17 @@ function BlogPostPage(props: any) { description: meta.description, url: meta.url, type: 'article', - videos: props.blog.video && [ - { - // youtube based video meta - url: props.blog.video, - type: 'application/x-shockwave-flash', - width: 640, - height: 385, - }, - ], + videos: props.blog.video + ? [ + { + // youtube based video meta + url: props.blog.video, + type: 'application/x-shockwave-flash', + width: 640, + height: 385, + }, + ] + : undefined, article: { // // to do: add expiration and modified dates @@ -163,7 +209,7 @@ function BlogPostPage(props: any) { // // to do: author urls should be internal in future // currently we have external links to github profiles - authors: [props.blog.author_url], + authors: authorUrls, tags: props.blog.tags?.map((cat: string) => { return cat }), @@ -305,8 +351,8 @@ function BlogPostPage(props: any) {

Related articles

- {props.relatedPosts.map((post: any, i: number) => ( - + {props.relatedPosts.map((post) => ( +