Skip to content

Commit e4d2645

Browse files
chore: fix filtering
1 parent 8bc0f91 commit e4d2645

File tree

4 files changed

+282
-22
lines changed

4 files changed

+282
-22
lines changed

apps/sim/app/(landing)/studio/page.tsx

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,6 @@ export default async function StudioIndex({
5050
const start = (pageNum - 1) * perPage
5151
const pagePosts = sorted.slice(start, start + perPage)
5252

53-
// Split featured from regular posts on page 1
5453
const featured = pageNum === 1 && !tag ? pagePosts.filter((p) => p.featured) : []
5554
const feed = pageNum === 1 && !tag ? pagePosts.filter((p) => !p.featured) : pagePosts
5655

@@ -73,11 +72,11 @@ export default async function StudioIndex({
7372
<div className='mx-auto w-full max-w-5xl py-12'>
7473
{activeCategory && (
7574
<div className='mb-8 flex items-center gap-3'>
76-
<span className='font-season text-[10px] uppercase tracking-widest text-[#666]'>
75+
<span className='font-mono text-[10px] uppercase tracking-widest text-[#666]'>
7776
Filtered by:
7877
</span>
7978
<span
80-
className='px-2 py-0.5 font-season text-[10px] uppercase tracking-wider'
79+
className='px-2 py-0.5 font-mono text-[10px] uppercase tracking-wider'
8180
style={{
8281
border: `1px solid ${activeCategory.color}`,
8382
color: activeCategory.color,
@@ -87,15 +86,15 @@ export default async function StudioIndex({
8786
</span>
8887
<Link
8988
href='/studio'
90-
className='font-season text-[10px] uppercase tracking-wider text-[#999] transition-colors hover:text-[#ECECEC]'
89+
className='font-mono text-[10px] uppercase tracking-wider text-[#999] transition-colors hover:text-[#ECECEC]'
9190
>
9291
Clear
9392
</Link>
9493
</div>
9594
)}
9695
{featured.length > 0 && (
9796
<section className='mb-10'>
98-
<h2 className='mb-8 flex items-center gap-2 font-season text-[11px] uppercase tracking-widest text-[#666]'>
97+
<h2 className='mb-8 flex items-center gap-2 font-mono text-[11px] uppercase tracking-widest text-[#666]'>
9998
<span className='inline-block h-2 w-2 bg-[#FA4EDF]' aria-hidden='true' />
10099
Featured Content
101100
</h2>
@@ -104,7 +103,7 @@ export default async function StudioIndex({
104103
)}
105104
{feed.length > 0 && (
106105
<section>
107-
<h2 className='mb-8 flex items-center gap-2 font-season text-[11px] uppercase tracking-widest text-[#666]'>
106+
<h2 className='mb-8 flex items-center gap-2 font-mono text-[11px] uppercase tracking-widest text-[#666]'>
108107
<span className='inline-block h-2 w-2 bg-[#00F701]' aria-hidden='true' />
109108
{activeCategory ? activeCategory.label : 'All Posts'}
110109
</h2>
@@ -113,10 +112,10 @@ export default async function StudioIndex({
113112
)}
114113
{pagePosts.length === 0 && (
115114
<div className='py-20 text-center'>
116-
<p className='font-season text-[14px] text-[#666]'>No posts found.</p>
115+
<p className='text-[14px] text-[#666]'>No posts found.</p>
117116
<Link
118117
href='/studio'
119-
className='mt-4 inline-block font-season text-[12px] uppercase tracking-wider text-[#999] transition-colors hover:text-[#ECECEC]'
118+
className='mt-4 inline-block font-mono text-[12px] uppercase tracking-wider text-[#999] transition-colors hover:text-[#ECECEC]'
120119
>
121120
View all posts
122121
</Link>
@@ -127,19 +126,19 @@ export default async function StudioIndex({
127126
{pageNum > 1 && (
128127
<Link
129128
href={`/studio?page=${pageNum - 1}${tag ? `&tag=${encodeURIComponent(tag)}` : ''}`}
130-
className='border border-[#3d3d3d] bg-[#232323] px-6 py-2.5 font-season text-[11px] uppercase tracking-wider text-[#999] transition-colors hover:border-[#666] hover:text-[#ECECEC]'
129+
className='border border-[#3d3d3d] bg-[#232323] px-6 py-2.5 font-mono text-[11px] uppercase tracking-wider text-[#999] transition-colors hover:border-[#666] hover:text-[#ECECEC]'
131130
style={{ borderRadius: '5px' }}
132131
>
133132
Previous
134133
</Link>
135134
)}
136-
<span className='font-season text-[10px] uppercase tracking-wider text-[#666]'>
135+
<span className='font-mono text-[10px] uppercase tracking-wider text-[#666]'>
137136
Page {pageNum} of {totalPages}
138137
</span>
139138
{pageNum < totalPages && (
140139
<Link
141140
href={`/studio?page=${pageNum + 1}${tag ? `&tag=${encodeURIComponent(tag)}` : ''}`}
142-
className='border border-[#3d3d3d] bg-[#232323] px-6 py-2.5 font-season text-[11px] uppercase tracking-wider text-[#999] transition-colors hover:border-[#666] hover:text-[#ECECEC]'
141+
className='border border-[#3d3d3d] bg-[#232323] px-6 py-2.5 font-mono text-[11px] uppercase tracking-wider text-[#999] transition-colors hover:border-[#666] hover:text-[#ECECEC]'
143142
style={{ borderRadius: '5px' }}
144143
>
145144
Load more articles

apps/sim/app/(landing)/studio/sidebar.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ export async function StudioSidebar({ activeTag }: StudioSidebarProps) {
4343
<div className='flex h-full flex-col'>
4444
<div className='mb-6'>
4545
<h2 className='mb-4 font-season text-[10px] uppercase tracking-widest text-[#666]'>
46-
Find Insight
46+
Find Insights
4747
</h2>
4848
<div className='relative'>
4949
<input
Lines changed: 268 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,268 @@
1+
'use client'
2+
3+
import { useCallback, useMemo, useState } from 'react'
4+
import { StudioHero } from '@/app/(landing)/studio/hero'
5+
import { FeaturedGrid, PostGrid } from '@/app/(landing)/studio/post-grid'
6+
import { CATEGORIES, getCategoryById, getPrimaryCategory } from '@/app/(landing)/studio/tag-colors'
7+
8+
interface SerializedPost {
9+
slug: string
10+
title: string
11+
description: string
12+
date: string
13+
ogImage: string
14+
readingTime?: number
15+
tags: string[]
16+
author: { id: string; name: string; avatarUrl?: string; url?: string }
17+
authors?: { id: string; name: string; avatarUrl?: string; url?: string }[]
18+
featured?: boolean
19+
}
20+
21+
interface StudioFeedProps {
22+
posts: SerializedPost[]
23+
initialTag?: string | null
24+
}
25+
26+
export function StudioFeed({ posts, initialTag }: StudioFeedProps) {
27+
const [activeTag, setActiveTag] = useState<string | null>(initialTag ?? null)
28+
29+
const categoryCounts = useMemo(() => {
30+
const counts: Record<string, number> = {}
31+
for (const cat of CATEGORIES) counts[cat.id] = 0
32+
for (const post of posts) {
33+
const cat = getPrimaryCategory(post.tags)
34+
counts[cat.id] = (counts[cat.id] ?? 0) + 1
35+
}
36+
return counts
37+
}, [posts])
38+
39+
const filtered = useMemo(() => {
40+
if (!activeTag) return posts
41+
return posts.filter((p) => getPrimaryCategory(p.tags).id === activeTag)
42+
}, [posts, activeTag])
43+
44+
const sorted = useMemo(() => {
45+
return [...filtered].sort((a, b) => {
46+
if (a.featured && !b.featured) return -1
47+
if (!a.featured && b.featured) return 1
48+
return new Date(b.date).getTime() - new Date(a.date).getTime()
49+
})
50+
}, [filtered])
51+
52+
const featured = !activeTag ? sorted.filter((p) => p.featured) : []
53+
const feed = !activeTag ? sorted.filter((p) => !p.featured) : sorted
54+
const activeCategory = activeTag ? getCategoryById(activeTag) : null
55+
56+
const handleCategoryClick = useCallback((catId: string | null) => {
57+
setActiveTag(catId)
58+
const url = catId ? `/studio?tag=${encodeURIComponent(catId)}` : '/studio'
59+
window.history.replaceState(null, '', url)
60+
}, [])
61+
62+
const sidebarItems = useMemo(
63+
() => [
64+
{ id: null, label: 'All Posts', count: posts.length, color: '#00F701' },
65+
...CATEGORIES.map((cat) => ({
66+
id: cat.id as string | null,
67+
label: cat.label,
68+
count: categoryCounts[cat.id] ?? 0,
69+
color: cat.color,
70+
})),
71+
],
72+
[posts.length, categoryCounts]
73+
)
74+
75+
return (
76+
<div className='flex flex-1 flex-col lg:flex-row'>
77+
<Sidebar items={sidebarItems} activeId={activeTag} onSelect={handleCategoryClick} />
78+
<main className='relative flex-1'>
79+
<div className='flex flex-col'>
80+
{!activeTag && <StudioHero />}
81+
<div className='mx-auto w-full max-w-5xl py-12'>
82+
{activeCategory && (
83+
<div className='mb-8 flex items-center gap-3'>
84+
<span className='font-mono text-[10px] uppercase tracking-widest text-[#666]'>
85+
Filtered by:
86+
</span>
87+
<span
88+
className='px-2 py-0.5 font-mono text-[10px] uppercase tracking-wider'
89+
style={{
90+
border: `1px solid ${activeCategory.color}`,
91+
color: activeCategory.color,
92+
}}
93+
>
94+
{activeCategory.label}
95+
</span>
96+
<button
97+
type='button'
98+
onClick={() => handleCategoryClick(null)}
99+
className='font-mono text-[10px] uppercase tracking-wider text-[#999] transition-colors hover:text-[#ECECEC]'
100+
>
101+
Clear
102+
</button>
103+
</div>
104+
)}
105+
{featured.length > 0 && (
106+
<section className='mb-10'>
107+
<h2 className='mb-8 flex items-center gap-2 font-mono text-[11px] uppercase tracking-widest text-[#666]'>
108+
<span className='inline-block h-2 w-2 bg-[#FA4EDF]' aria-hidden='true' />
109+
Featured Content
110+
</h2>
111+
<FeaturedGrid posts={featured} />
112+
</section>
113+
)}
114+
{feed.length > 0 && (
115+
<section>
116+
<h2 className='mb-8 flex items-center gap-2 font-mono text-[11px] uppercase tracking-widest text-[#666]'>
117+
<span className='inline-block h-2 w-2 bg-[#00F701]' aria-hidden='true' />
118+
{activeCategory ? activeCategory.label : 'All Posts'}
119+
</h2>
120+
<PostGrid posts={feed} />
121+
</section>
122+
)}
123+
{sorted.length === 0 && (
124+
<div className='py-20 text-center'>
125+
<p className='text-[14px] text-[#666]'>No posts found.</p>
126+
<button
127+
type='button'
128+
onClick={() => handleCategoryClick(null)}
129+
className='mt-4 inline-block font-mono text-[12px] uppercase tracking-wider text-[#999] transition-colors hover:text-[#ECECEC]'
130+
>
131+
View all posts
132+
</button>
133+
</div>
134+
)}
135+
</div>
136+
</div>
137+
</main>
138+
</div>
139+
)
140+
}
141+
142+
import { useEffect, useRef } from 'react'
143+
import { motion, useReducedMotion } from 'framer-motion'
144+
import { Search } from 'lucide-react'
145+
146+
interface SidebarItem {
147+
id: string | null
148+
label: string
149+
count: number
150+
color: string
151+
}
152+
153+
interface SidebarProps {
154+
items: SidebarItem[]
155+
activeId: string | null
156+
onSelect: (id: string | null) => void
157+
}
158+
159+
function Sidebar({ items, activeId, onSelect }: SidebarProps) {
160+
const shouldReduceMotion = useReducedMotion()
161+
const listRef = useRef<HTMLUListElement>(null)
162+
const itemRefs = useRef<Map<string, HTMLLIElement>>(new Map())
163+
const [highlight, setHighlight] = useState<{ top: number; height: number } | null>(null)
164+
165+
const activeItem = items.find((item) => item.id === activeId) ?? items[0]
166+
167+
useEffect(() => {
168+
const key = activeId ?? 'all'
169+
const el = itemRefs.current.get(key)
170+
const list = listRef.current
171+
if (!el || !list) {
172+
setHighlight(null)
173+
return
174+
}
175+
const listRect = list.getBoundingClientRect()
176+
const elRect = el.getBoundingClientRect()
177+
setHighlight({
178+
top: elRect.top - listRect.top,
179+
height: elRect.height,
180+
})
181+
}, [activeId])
182+
183+
return (
184+
<aside className='flex w-full shrink-0 flex-col border-r border-[#2A2A2A] bg-[#1C1C1C] p-8 lg:sticky lg:top-[52px] lg:h-[calc(100vh-52px)] lg:w-72 lg:overflow-y-auto'>
185+
<div className='flex h-full flex-col'>
186+
<div className='mb-10'>
187+
<h2 className='mb-4 font-mono text-[10px] uppercase tracking-widest text-[#666]'>
188+
Find Insights
189+
</h2>
190+
<div className='relative'>
191+
<input
192+
type='text'
193+
placeholder='SEARCH COMING SOON...'
194+
disabled
195+
className='w-full cursor-not-allowed border border-[#2A2A2A] bg-[#232323] px-4 py-2 font-mono text-[11px] text-[#ECECEC] opacity-50 placeholder:text-[#666]'
196+
style={{ borderRadius: '5px' }}
197+
aria-label='Search blog posts (coming soon)'
198+
/>
199+
<Search
200+
className='absolute right-3 top-2.5 h-3.5 w-3.5 text-[#666]'
201+
aria-hidden='true'
202+
/>
203+
</div>
204+
</div>
205+
<div className='flex flex-col'>
206+
<h2 className='mb-3 font-mono text-[10px] uppercase tracking-widest text-[#ECECEC]'>
207+
Categories
208+
</h2>
209+
<ul ref={listRef} className='relative flex flex-col'>
210+
{activeItem && highlight && (
211+
<motion.div
212+
className='absolute left-0 right-0 rounded-sm'
213+
style={{
214+
backgroundColor: `${activeItem.color}0D`,
215+
border: `1px solid ${activeItem.color}`,
216+
height: highlight.height,
217+
}}
218+
animate={{ y: highlight.top }}
219+
transition={
220+
shouldReduceMotion
221+
? { duration: 0 }
222+
: { type: 'spring', duration: 0.3, bounce: 0 }
223+
}
224+
/>
225+
)}
226+
{items.map((item) => {
227+
const isActive = item.id === activeId
228+
const key = item.id ?? 'all'
229+
230+
return (
231+
<li
232+
key={key}
233+
ref={(el) => {
234+
if (el) itemRefs.current.set(key, el)
235+
}}
236+
>
237+
<button
238+
type='button'
239+
onClick={() => onSelect(item.id)}
240+
className={`relative flex w-full items-center justify-between rounded-sm px-3 py-2 text-left text-[13px] transition-colors duration-150 ease ${
241+
isActive
242+
? ''
243+
: '[@media(hover:hover)]:hover:bg-[#232323] [@media(hover:hover)]:hover:text-[#ECECEC]'
244+
}`}
245+
style={{ color: isActive ? item.color : '#999' }}
246+
>
247+
<span className='relative z-10'>{item.label}</span>
248+
<span
249+
className='relative z-10 font-mono text-[10px]'
250+
style={{
251+
padding: '2px 6px',
252+
borderRadius: '2px',
253+
border: isActive ? `1px solid ${item.color}` : '1px solid #2A2A2A',
254+
color: isActive ? item.color : '#666',
255+
}}
256+
>
257+
{String(item.count).padStart(2, '0')}
258+
</span>
259+
</button>
260+
</li>
261+
)
262+
})}
263+
</ul>
264+
</div>
265+
</div>
266+
</aside>
267+
)
268+
}

apps/sim/app/(landing)/studio/tag-colors.ts

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -73,16 +73,9 @@ export function getCategoryById(id: string): Category {
7373
}
7474

7575
export function getPrimaryCategory(tags: string[]): Category {
76-
const matchedIds = new Set<string>()
77-
for (const tag of tags) {
78-
matchedIds.add(getTagCategory(tag))
79-
}
80-
81-
for (const cat of CATEGORIES) {
82-
if (matchedIds.has(cat.id)) return cat
83-
}
84-
85-
return CATEGORIES[4]
76+
if (tags.length === 0) return CATEGORIES[4]
77+
const firstCatId = getTagCategory(tags[0])
78+
return getCategoryById(firstCatId)
8679
}
8780

8881
export function getTagColor(tag: string): string {

0 commit comments

Comments
 (0)