Skip to content

Commit 101a955

Browse files
chore: updated for comments
1 parent fa19d84 commit 101a955

File tree

6 files changed

+136
-68
lines changed

6 files changed

+136
-68
lines changed

apps/sim/app/(landing)/blog/[slug]/page.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ export default async function Page({ params }: { params: Promise<{ slug: string
5353
/>
5454

5555
<div className='mx-auto flex w-full max-w-[1500px] flex-col items-start gap-8 px-4 pt-10 pb-24 sm:px-6 sm:pt-12 lg:px-8 lg:pt-16 xl:flex-row xl:gap-12'>
56-
<div data-blog-main-content className='mx-auto w-full min-w-0 max-w-5xl flex-grow'>
56+
<div data-blog-main-content className='mx-auto w-full min-w-0 max-w-5xl flex-grow lg:px-4'>
5757
<Link
5858
href='/blog'
5959
className='group mb-8 inline-flex items-center gap-2 border border-[#2A2A2A] bg-[#232323] px-4 py-2 font-season text-[#999] text-[11px] uppercase tracking-widest transition-colors hover:text-[#ECECEC]'

apps/sim/app/(landing)/blog/[slug]/prose-studio.css

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
font-weight: 500;
55
font-size: 30px;
66
color: #ececec;
7-
margin-top: 3rem !important;
7+
margin-top: 1.5rem !important;
88
margin-bottom: 1.5rem;
99
letter-spacing: -0.025em;
1010
}
Lines changed: 79 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,48 +1,112 @@
11
'use client'
22

3-
import { useCallback, useMemo, useState } from 'react'
4-
import { useRouter } from 'next/navigation'
3+
import { useCallback, useEffect, useMemo, useState } from 'react'
54
import { BlogStudioSidebar } from '@/app/(landing)/blog/studio-sidebar-client'
5+
import { PostGrid } from '@/app/(landing)/blog/post-grid'
6+
import { CATEGORIES, getPrimaryCategory } from '@/app/(landing)/blog/tag-colors'
7+
import type { BlogMeta } from '@/lib/blog/schema'
68

79
interface AuthorWithSidebarProps {
8-
allPosts: { tags: string[] }[]
10+
allPosts: BlogMeta[]
11+
authorPosts: BlogMeta[]
912
activeTag: string | null
13+
initialQuery: string
1014
children: React.ReactNode
1115
}
1216

13-
export function AuthorWithSidebar({ allPosts, activeTag, children }: AuthorWithSidebarProps) {
14-
const router = useRouter()
15-
const [query, setQuery] = useState('')
17+
export function AuthorWithSidebar({ allPosts, authorPosts, activeTag, initialQuery, children }: AuthorWithSidebarProps) {
18+
const [selectedTag, setSelectedTag] = useState<string | null>(activeTag)
19+
const [query, setQuery] = useState(initialQuery)
20+
21+
const syncUrl = useCallback((tag: string | null, q: string) => {
22+
const params = new URLSearchParams()
23+
if (tag) params.set('tag', tag)
24+
if (q) params.set('q', q)
25+
const search = params.toString()
26+
const basePath = window.location.pathname
27+
window.history.replaceState(null, '', search ? `${basePath}?${search}` : basePath)
28+
}, [])
29+
30+
useEffect(() => {
31+
const onPopState = () => {
32+
const params = new URLSearchParams(window.location.search)
33+
setSelectedTag(params.get('tag'))
34+
setQuery(params.get('q') ?? '')
35+
}
36+
window.addEventListener('popstate', onPopState)
37+
return () => window.removeEventListener('popstate', onPopState)
38+
}, [])
39+
40+
const lowerQ = query.trim().toLowerCase()
41+
42+
const filteredAuthorPosts = useMemo(() => {
43+
const validTag = selectedTag && CATEGORIES.some((c) => c.id === selectedTag) ? selectedTag : null
44+
45+
let filtered = authorPosts
46+
47+
if (validTag) filtered = authorPosts.filter((p) => getPrimaryCategory(p.tags).id === validTag)
48+
49+
if (lowerQ) {
50+
filtered = filtered.filter((p) => {
51+
const haystack = [
52+
p.title,
53+
p.description,
54+
...p.tags,
55+
p.author.name,
56+
...(p.authors?.map((a) => a.name) ?? []),
57+
]
58+
.join(' ')
59+
.toLowerCase()
60+
return haystack.includes(lowerQ)
61+
})
62+
}
63+
64+
return filtered
65+
}, [authorPosts, lowerQ, selectedTag])
66+
67+
const sidebarPosts = useMemo(() => allPosts.map((p) => ({ tags: p.tags })), [allPosts])
1668

1769
const handleChangeQuery = useCallback(
1870
(value: string) => {
1971
setQuery(value)
20-
const trimmed = value.trim()
21-
router.push(trimmed ? `/blog?q=${encodeURIComponent(trimmed)}` : '/blog')
72+
setSelectedTag(null)
73+
syncUrl(null, value.trim())
2274
},
23-
[router]
75+
[syncUrl]
2476
)
2577

2678
const handleSelectTag = useCallback(
2779
(id: string | null) => {
2880
setQuery('')
29-
router.push(id ? `/blog?tag=${encodeURIComponent(id)}` : '/blog')
81+
setSelectedTag(id)
82+
syncUrl(id, '')
3083
},
31-
[router]
84+
[syncUrl]
3285
)
3386

34-
const sidebarPosts = useMemo(() => allPosts.map((p) => ({ tags: p.tags })), [allPosts])
35-
3687
return (
3788
<div className='flex min-h-0 flex-1 flex-col overflow-x-clip px-4 sm:px-6 lg:flex-row lg:px-12'>
3889
<BlogStudioSidebar
3990
posts={sidebarPosts}
40-
activeTag={activeTag}
91+
activeTag={selectedTag}
4192
query={query}
4293
onChangeQuery={handleChangeQuery}
4394
onSelectTag={handleSelectTag}
4495
/>
45-
<main className='relative min-w-0 flex-1'>{children}</main>
96+
<main className='relative min-w-0 flex-1'>
97+
<div className='mx-auto w-full max-w-5xl px-4 py-16 sm:px-0 lg:mr-8 lg:px-0 lg:py-16'>
98+
{children}
99+
{filteredAuthorPosts.length === 0 ? (
100+
<div className='py-20 text-center'>
101+
<p className='text-[#666] text-[14px]'>
102+
{lowerQ ? `No posts matching "${query.trim()}".` : 'No posts found.'}
103+
</p>
104+
</div>
105+
) : (
106+
<PostGrid posts={filteredAuthorPosts} />
107+
)}
108+
</div>
109+
</main>
46110
</div>
47111
)
48112
}

apps/sim/app/(landing)/blog/authors/[id]/page.tsx

Lines changed: 46 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ import Image from 'next/image'
33
import Link from 'next/link'
44
import { getAllPostMeta } from '@/lib/blog/registry'
55
import { AuthorWithSidebar } from '@/app/(landing)/blog/authors/[id]/author-with-sidebar'
6-
import { PostGrid } from '@/app/(landing)/blog/post-grid'
76

87
function findAuthorById(posts: Awaited<ReturnType<typeof getAllPostMeta>>, id: string) {
98
for (const p of posts) {
@@ -33,7 +32,7 @@ export default async function AuthorPage({
3332
searchParams: Promise<{ tag?: string; q?: string }>
3433
}) {
3534
const { id } = await params
36-
const { tag } = await searchParams
35+
const { tag, q } = await searchParams
3736
const allPosts = await getAllPostMeta()
3837
const posts = allPosts.filter((p) => p.author.id === id || p.authors?.some((a) => a.id === id))
3938
const author = findAuthorById(allPosts, id)
@@ -62,54 +61,55 @@ export default async function AuthorPage({
6261
}
6362

6463
return (
65-
<AuthorWithSidebar allPosts={allPosts} activeTag={tag ?? null}>
66-
<div className='mx-auto w-full max-w-5xl px-4 py-16 sm:px-0 lg:mr-8 lg:px-0 lg:py-16'>
67-
<script
68-
type='application/ld+json'
69-
dangerouslySetInnerHTML={{ __html: JSON.stringify(personJsonLd) }}
70-
/>
71-
<div className='mb-12 flex items-center gap-4'>
72-
{author.avatarUrl && (
73-
<div
74-
className='h-16 w-16 shrink-0 overflow-hidden border border-[#2A2A2A] bg-[#232323]'
75-
style={{ borderRadius: '5px' }}
64+
<AuthorWithSidebar
65+
allPosts={allPosts}
66+
authorPosts={posts}
67+
activeTag={tag ?? null}
68+
initialQuery={q ?? ''}
69+
>
70+
<script
71+
type='application/ld+json'
72+
dangerouslySetInnerHTML={{ __html: JSON.stringify(personJsonLd) }}
73+
/>
74+
<div className='mb-12 flex items-center gap-4'>
75+
{author.avatarUrl && (
76+
<div
77+
className='h-16 w-16 shrink-0 overflow-hidden border border-[#2A2A2A] bg-[#232323]'
78+
style={{ borderRadius: '5px' }}
79+
>
80+
<Image
81+
src={author.avatarUrl}
82+
alt={author.name}
83+
width={64}
84+
height={64}
85+
className='h-full w-full object-cover'
86+
unoptimized
87+
/>
88+
</div>
89+
)}
90+
<div>
91+
<div className='mb-1 font-season text-[#FA4EDF] text-[10px] uppercase tracking-widest'>
92+
Author
93+
</div>
94+
<h1 className='font-[500] text-[#ECECEC] text-[32px] leading-tight tracking-[-0.02em]'>
95+
{author.name}
96+
</h1>
97+
{author.url && (
98+
<Link
99+
href={author.url}
100+
target='_blank'
101+
rel='noopener noreferrer'
102+
className='font-season text-[#999] text-[11px] transition-colors hover:text-[#ECECEC]'
76103
>
77-
<Image
78-
src={author.avatarUrl}
79-
alt={author.name}
80-
width={64}
81-
height={64}
82-
className='h-full w-full object-cover'
83-
unoptimized
84-
/>
85-
</div>
104+
{author.xHandle ? `@${author.xHandle}` : 'Profile'}
105+
</Link>
86106
)}
87-
<div>
88-
<div className='mb-1 font-season text-[#FA4EDF] text-[10px] uppercase tracking-widest'>
89-
Author
90-
</div>
91-
<h1 className='font-[500] text-[#ECECEC] text-[32px] leading-tight tracking-[-0.02em]'>
92-
{author.name}
93-
</h1>
94-
{author.url && (
95-
<Link
96-
href={author.url}
97-
target='_blank'
98-
rel='noopener noreferrer'
99-
className='font-season text-[#999] text-[11px] transition-colors hover:text-[#ECECEC]'
100-
>
101-
{author.xHandle ? `@${author.xHandle}` : 'Profile'}
102-
</Link>
103-
)}
104-
</div>
105107
</div>
106-
<h2 className='mb-8 flex items-center gap-2 font-season text-[#666] text-[11px] uppercase tracking-widest'>
107-
<span className='inline-block h-2 w-2 bg-[#00F701]' aria-hidden='true' />
108-
Posts by {author.name}
109-
</h2>
110-
111-
<PostGrid posts={posts} />
112108
</div>
109+
<h2 className='mb-8 flex items-center gap-2 font-season text-[#666] text-[11px] uppercase tracking-widest'>
110+
<span className='inline-block h-2 w-2 bg-[#00F701]' aria-hidden='true' />
111+
Posts by {author.name}
112+
</h2>
113113
</AuthorWithSidebar>
114114
)
115115
}

apps/sim/app/(landing)/blog/og/route.tsx

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import fs from 'fs/promises'
22
import path from 'path'
33
import { ImageResponse } from 'next/og'
44
import type { NextRequest } from 'next/server'
5-
import { getPostBySlug } from '@/lib/blog/registry'
5+
import { getPostMetaBySlug } from '@/lib/blog/registry'
66
import { formatDate } from '@/lib/core/utils/formatting'
77
import { getPrimaryCategory } from '@/app/(landing)/blog/tag-colors'
88

@@ -20,10 +20,9 @@ export async function GET(request: NextRequest) {
2020
return new Response('Missing slug parameter', { status: 400 })
2121
}
2222

23-
let post
24-
try {
25-
post = await getPostBySlug(slug)
26-
} catch {
23+
const post = await getPostMetaBySlug(slug)
24+
25+
if (!post) {
2726
return new Response('Post not found', { status: 404 })
2827
}
2928

apps/sim/lib/blog/registry.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,11 @@ export async function getAllPostMeta(): Promise<BlogMeta[]> {
8383
return (await scanFrontmatters()).filter((p) => !p.draft)
8484
}
8585

86+
export async function getPostMetaBySlug(slug: string): Promise<BlogMeta | null> {
87+
const meta = await scanFrontmatters()
88+
return meta.find((m) => m.slug === slug) ?? null
89+
}
90+
8691
export async function getAllTags(): Promise<TagWithCount[]> {
8792
const posts = await getAllPostMeta()
8893
const counts: Record<string, number> = {}

0 commit comments

Comments
 (0)