Skip to content

Commit 0c82e6c

Browse files
committed
🎨 typescript strict checks
1 parent 90977ac commit 0c82e6c

25 files changed

+207
-211
lines changed

app/about/page.tsx

+15-12
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,11 @@ import SocialIcon from '@/components/social-icons'
99
import Image from 'next/image'
1010
import ProjectCard from '@/components/project-card'
1111
import { Mdx } from '@/components/mdx/mdx'
12+
import { notFound } from 'next/navigation'
1213

1314
export default function About() {
1415
const author = allAuthors.find((p) => p.slug === 'default')
16+
if (!author) notFound()
1517
const { name, avatar, occupation, company, email, twitter, linkedin, github } = author
1618

1719
return (
@@ -31,22 +33,23 @@ export default function About() {
3133
height={106}
3234
className="absolute top-[-6px]"
3335
/>
34-
35-
<Image
36-
src={avatar}
37-
alt="avatar"
38-
width={192}
39-
height={192}
40-
className="h-48 w-48 rounded-full"
41-
/>
36+
{avatar && (
37+
<Image
38+
src={avatar}
39+
alt="avatar"
40+
width={192}
41+
height={192}
42+
className="h-48 w-48 rounded-full"
43+
/>
44+
)}
4245
<h2 className="pb-2 pt-4 text-2xl font-bold leading-8 tracking-tight">{name}</h2>
4346
<div className="text-gray-700 dark:text-gray-400">{occupation}</div>
4447
<div className="text-gray-700 dark:text-gray-400">{company}</div>
4548
<div className="flex space-x-3 pt-6">
46-
<SocialIcon kind="mail" href={`mailto:${email}`} />
47-
<SocialIcon kind="github" href={github} />
48-
<SocialIcon kind="linkedin" href={linkedin} />
49-
<SocialIcon kind="twitter" href={twitter} />
49+
{email && <SocialIcon kind="mail" href={`mailto:${email}`} />}
50+
{github && <SocialIcon kind="github" href={github} />}
51+
{linkedin && <SocialIcon kind="linkedin" href={linkedin} />}
52+
{twitter && <SocialIcon kind="twitter" href={twitter} />}
5053
</div>
5154
</div>
5255
<div className="max-w-none pb-8 pt-8 text-gray-700 dark:text-gray-300 xl:col-span-2">

app/blog/[...slug]/page.tsx

+74-42
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1+
import '@/styles/mdx.css'
2+
13
import PageTitle from '@/components/page-title'
24
import { sortedBlogPost, coreContent } from '../../../lib/contentlayer'
3-
import '@/styles/mdx.css'
45
import { allBlogs, allAuthors } from 'contentlayer/generated'
56
import type { Blog } from 'contentlayer/generated'
67
import { Mdx } from '@/components/mdx/mdx'
@@ -11,32 +12,63 @@ import Tag from '@/components/tag'
1112
import { notFound } from 'next/navigation'
1213
import Comments from '@/components/comments'
1314
import ScrollTopAndComment from '@/components/floating-buttons'
15+
import { formatDate } from '@/lib/utils'
1416

15-
const editUrl = (path) => `${siteMetadata.siteRepo}/blob/master/data/${path}`
16-
const discussUrl = (path) =>
17+
const editUrl = (path: string) => `${siteMetadata.siteRepo}/blob/master/data/${path}`
18+
const discussUrl = (path: string) =>
1719
`https://twitter.com/intent/tweet?text=${encodeURIComponent(`${siteMetadata.siteUrl}/${path}`)}`
1820

19-
const postDateTemplate: Intl.DateTimeFormatOptions = {
20-
weekday: 'long',
21-
year: 'numeric',
22-
month: 'long',
23-
day: 'numeric',
21+
interface BlogPostPageProps {
22+
params: {
23+
slug: string[]
24+
}
2425
}
2526

26-
export default function BlogPostPage({ params }) {
27-
const slug = (params.slug as string[]).join('/')
27+
interface PostPageDetails {
28+
post: Blog
29+
prev: ReturnType<typeof coreContent> | null
30+
next: ReturnType<typeof coreContent> | null
31+
authorDetails: (ReturnType<typeof coreContent> | null)[]
32+
}
33+
34+
async function getPostFromParams(
35+
params: BlogPostPageProps['params']
36+
): Promise<PostPageDetails | null> {
37+
const slug = params.slug.join('/')
2838
const sortedPosts = sortedBlogPost(allBlogs) as Blog[]
2939
const postIndex = sortedPosts.findIndex((p) => p.slug === slug)
40+
if (postIndex === -1) return null
41+
const post = sortedPosts[postIndex]
3042
const prevContent = sortedPosts[postIndex + 1] || null
3143
const prev = (prevContent && (prevContent?.draft ? null : coreContent(prevContent))) || null
3244
const nextContent = sortedPosts[postIndex - 1] || null
3345
const next = (nextContent && (nextContent?.draft ? null : coreContent(nextContent))) || null
34-
const post = sortedPosts.find((p) => p.slug === slug)
46+
3547
const authorList = post.authors || ['default']
3648
const authorDetails = authorList.map((author) => {
3749
const authorResults = allAuthors.find((p) => p.slug === author)
50+
if (!authorResults) return null
3851
return coreContent(authorResults)
3952
})
53+
54+
return {
55+
post,
56+
prev,
57+
next,
58+
authorDetails,
59+
}
60+
}
61+
62+
export async function generateStaticParams(): Promise<BlogPostPageProps['params'][]> {
63+
return allBlogs.map((post) => ({
64+
slug: post.slugAsParams.split('/'),
65+
}))
66+
}
67+
68+
export default async function BlogPostPage({ params }: BlogPostPageProps) {
69+
const pageDetails = await getPostFromParams(params)
70+
if (!pageDetails) return notFound()
71+
const { post, prev, next, authorDetails } = pageDetails
4072
const { filePath, path, date, title, tags } = post
4173

4274
if (!post) {
@@ -53,9 +85,7 @@ export default function BlogPostPage({ params }) {
5385
<div>
5486
<dt className="sr-only">Published on</dt>
5587
<dd className="text-base font-medium leading-6 text-gray-500 dark:text-gray-400">
56-
<time dateTime={date}>
57-
{new Date(date).toLocaleDateString(siteMetadata.locale, postDateTemplate)}
58-
</time>
88+
<time dateTime={date}>{formatDate(date)}</time>
5989
</dd>
6090
</div>
6191
</dl>
@@ -69,34 +99,36 @@ export default function BlogPostPage({ params }) {
6999
<dt className="sr-only">Authors</dt>
70100
<dd>
71101
<ul className="flex flex-wrap justify-center gap-4 sm:space-x-12 xl:block xl:space-x-0 xl:space-y-8">
72-
{authorDetails.map((author) => (
73-
<li className="flex items-center space-x-2" key={author.name}>
74-
{author.avatar && (
75-
<Image
76-
src={author.avatar}
77-
width={38}
78-
height={38}
79-
alt="avatar"
80-
className="h-10 w-10 rounded-full"
81-
/>
82-
)}
83-
<dl className="whitespace-nowrap text-sm font-medium leading-5">
84-
<dt className="sr-only">Name</dt>
85-
<dd className="text-gray-900 dark:text-gray-100">{author.name}</dd>
86-
<dt className="sr-only">Twitter</dt>
87-
<dd>
88-
{author.twitter && (
89-
<Link
90-
href={author.twitter}
91-
className="text-primary-500 hover:text-primary-600 dark:hover:text-primary-400"
92-
>
93-
{author.twitter.replace('https://twitter.com/', '@')}
94-
</Link>
95-
)}
96-
</dd>
97-
</dl>
98-
</li>
99-
))}
102+
{authorDetails.map((author) =>
103+
!author ? null : (
104+
<li className="flex items-center space-x-2" key={author.name}>
105+
{author.avatar && (
106+
<Image
107+
src={author.avatar}
108+
width={38}
109+
height={38}
110+
alt="avatar"
111+
className="h-10 w-10 rounded-full"
112+
/>
113+
)}
114+
<dl className="whitespace-nowrap text-sm font-medium leading-5">
115+
<dt className="sr-only">Name</dt>
116+
<dd className="text-gray-900 dark:text-gray-100">{author.name}</dd>
117+
<dt className="sr-only">Twitter</dt>
118+
<dd>
119+
{author.twitter && (
120+
<Link
121+
href={author.twitter}
122+
className="text-primary-500 hover:text-primary-600 dark:hover:text-primary-400"
123+
>
124+
{author.twitter.replace('https://twitter.com/', '@')}
125+
</Link>
126+
)}
127+
</dd>
128+
</dl>
129+
</li>
130+
)
131+
)}
100132
</ul>
101133
</dd>
102134
</dl>

app/blog/page.tsx

+8-7
Original file line numberDiff line numberDiff line change
@@ -54,13 +54,14 @@ export default async function BlogPage() {
5454
</Link>
5555
</h2>
5656
<div className="flex flex-wrap gap-2 pt-2">
57-
{tags.map((tag) => (
58-
<Tag
59-
key={tag}
60-
text={tag}
61-
className="2xl rounded-lg bg-blue-600 px-2 py-1 text-sm text-white hover:scale-110 hover:bg-blue-600 dark:bg-blue-950 "
62-
/>
63-
))}
57+
{tags &&
58+
tags.map((tag) => (
59+
<Tag
60+
key={tag}
61+
text={tag}
62+
className="2xl rounded-lg bg-blue-600 px-2 py-1 text-sm text-white hover:scale-110 hover:bg-blue-600 dark:bg-blue-950 "
63+
/>
64+
))}
6465
</div>
6566
</div>
6667
<div className=" max-w-none text-gray-600 dark:text-gray-400">

app/tags/[slug]/page.tsx

+23-13
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,14 @@ import Tag from '@/components/tag'
88
import { kebabCase } from 'utils/kebabCase'
99
import { formatDate } from '@/lib/utils'
1010

11-
interface PageProps {
11+
interface TagPageProps {
1212
params: {
13-
slug: string[]
13+
slug: string
1414
}
1515
}
1616

17-
async function getPageFromParams(params) {
18-
const slug = params?.slug
17+
async function getPageFromParams(params: TagPageProps['params']) {
18+
const slug = params.slug
1919
const tags = await getAllTags(allBlogs)
2020
const page = Object.keys(tags).find((tag) => tag === slug)
2121

@@ -26,17 +26,26 @@ async function getPageFromParams(params) {
2626
return page
2727
}
2828

29-
export default async function PagePage({ params }: PageProps) {
29+
export async function generateStaticParams(): Promise<TagPageProps['params'][]> {
30+
const tags = await getAllTags(allBlogs)
31+
return Object.keys(tags).map((doc) => ({
32+
slug: doc,
33+
}))
34+
}
35+
36+
export default async function PagePage({ params }: TagPageProps) {
3037
const page = await getPageFromParams(params)
38+
3139
if (!page) {
3240
notFound()
3341
}
42+
3443
const title = page[0].toUpperCase() + page.split(' ').join('-').slice(1)
3544

3645
const tags = await getAllTags(allBlogs)
3746
const posts = allCoreContent(
3847
allBlogs.filter(
39-
(post) => post.draft !== true && post.tags.map((t) => kebabCase(t)).includes(page)
48+
(post) => post.draft !== true && post.tags?.map((t) => kebabCase(t)).includes(page)
4049
)
4150
)
4251

@@ -80,13 +89,14 @@ export default async function PagePage({ params }: PageProps) {
8089
</Link>
8190
</h2>
8291
<div className="flex flex-wrap gap-2 pt-2">
83-
{tags.map((tag) => (
84-
<Tag
85-
key={tag}
86-
text={tag}
87-
className="2xl rounded-lg bg-blue-600 px-2 py-1 text-sm text-white hover:scale-110 hover:bg-blue-600 dark:bg-blue-950 "
88-
/>
89-
))}
92+
{tags &&
93+
tags.map((tag) => (
94+
<Tag
95+
key={tag}
96+
text={tag}
97+
className="2xl rounded-lg bg-blue-600 px-2 py-1 text-sm text-white hover:scale-110 hover:bg-blue-600 dark:bg-blue-950 "
98+
/>
99+
))}
90100
</div>
91101
</div>
92102
<div className=" max-w-none text-gray-600 dark:text-gray-400">

components/comments/giscus.tsx

+10-10
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,15 @@ export interface GiscusConfig {
55
themeURL?: string
66
theme?: string
77
darkTheme?: string
8-
mapping?: string
9-
repo?: string
10-
repositoryId?: string
11-
category?: string
12-
categoryId?: string
13-
reactions?: string
14-
metadata?: string
8+
mapping: string
9+
repo: string
10+
repositoryId: string
11+
category: string
12+
categoryId: string
13+
reactions: string
14+
metadata: string
1515
inputPosition?: string
16-
lang?: string
16+
lang: string
1717
}
1818

1919
export type GiscusProps = GiscusConfig
@@ -52,9 +52,9 @@ export const Giscus = ({
5252
script.setAttribute('data-mapping', mapping)
5353
script.setAttribute('data-reactions-enabled', reactions)
5454
script.setAttribute('data-emit-metadata', metadata)
55-
script.setAttribute('data-input-position', inputPosition)
55+
script.setAttribute('data-input-position', inputPosition ?? 'bottom')
5656
script.setAttribute('data-lang', lang)
57-
script.setAttribute('data-theme', commentsTheme)
57+
script.setAttribute('data-theme', commentsTheme ?? 'dark')
5858
script.setAttribute('crossorigin', 'anonymous')
5959
script.async = true
6060

components/floating-buttons.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ const ScrollTopAndComment = () => {
1919
window.scrollTo({ top: 0 })
2020
}
2121
const handleScrollToComment = () => {
22-
document.getElementById('comment').scrollIntoView()
22+
document.querySelector('#comment')?.scrollIntoView()
2323
}
2424
return (
2525
<div

components/image-switcher.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ interface Props extends Omit<ImageProps, 'src'> {
1212
const ImageSwitcher = ({ light, dark, alt, ...rest }: Props) => {
1313
const { theme, resolvedTheme } = useTheme()
1414
const [clientLoaded, setClientLoaded] = useState(false)
15-
const [aiImage, setAiImage] = useState(null)
15+
const [aiImage, setAiImage] = useState<'light' | 'dark' | null>(null)
1616

1717
useEffect(() => {
1818
setClientLoaded(true)

components/latest-posts.tsx

+16-8
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,17 @@
11
import Link from '@/components/link'
22
import Tag from '@/components/tag'
33
import { formatDate } from '@/lib/utils'
4+
import type { Blog } from 'contentlayer/generated'
45

56
const MAX_DISPLAY = 3
67

7-
export function LatestPosts({ posts }) {
8+
type Posts = Omit<Blog, 'body' | '_raw' | '_id'>[]
9+
10+
interface LatestPostsProps {
11+
posts: Posts
12+
}
13+
14+
export function LatestPosts({ posts }: LatestPostsProps) {
815
return (
916
<>
1017
<div className="mx-auto max-w-3xl px-4 sm:px-6 xl:max-w-5xl ">
@@ -40,13 +47,14 @@ export function LatestPosts({ posts }) {
4047
</Link>
4148
</h2>
4249
<div className="flex flex-wrap gap-2 pt-2">
43-
{tags.map((tag) => (
44-
<Tag
45-
key={tag}
46-
text={tag}
47-
className="2xl rounded-lg bg-blue-600 px-2 py-1 text-sm text-white hover:scale-110 hover:bg-blue-600 dark:bg-blue-950 "
48-
/>
49-
))}
50+
{tags &&
51+
tags.map((tag) => (
52+
<Tag
53+
key={tag}
54+
text={tag}
55+
className="2xl rounded-lg bg-blue-600 px-2 py-1 text-sm text-white hover:scale-110 hover:bg-blue-600 dark:bg-blue-950 "
56+
/>
57+
))}
5058
</div>
5159
</div>
5260
<div className=" max-w-none text-gray-600 dark:text-gray-400">

0 commit comments

Comments
 (0)