feat: add roadmap tab with static RoadmapItem component

This commit is contained in:
2026-03-30 16:42:15 -04:00
parent 2d55af4de2
commit 26bc1a8c21
2 changed files with 151 additions and 3 deletions
+93 -3
View File
@@ -2,7 +2,8 @@
import { useEffect, useState } from 'react'
import { PortableTextRenderer } from '@/components/PortableTextRenderer'
import type { ChangelogEntry, RoadmapItemData, Tab } from '@/types/changelog'
import { RoadmapItem } from '@/components/RoadmapItem'
import type { ChangelogEntry, RoadmapItemData, Tab, VoteState } from '@/types/changelog'
const TABS: { id: Tab; label: string }[] = [
{ id: 'whats-new', label: "What's New" },
@@ -33,6 +34,19 @@ export function ChangelogTabs({
}) {
const [activeTab, setActiveTab] = useState<Tab>('whats-new')
const [voteState, setVoteState] = useState<Record<string, VoteState>>(
() =>
Object.fromEntries(
roadmapItems.map((item) => [
item._id,
{ count: 0, voted: false, loading: false },
])
)
)
// Filled in Task 5 — no-op for now
function handleVote(_id: string) {}
useEffect(() => {
const hash = window.location.hash.replace('#', '') as Tab
if (TABS.some((t) => t.id === hash)) setActiveTab(hash)
@@ -117,9 +131,85 @@ export function ChangelogTabs({
</div>
)}
{/* ── What's Next — stub, filled in Task 3 ── */}
{/* ── What's Next ── */}
{activeTab === 'whats-next' && (
<p className="text-sm text-zinc-500">Roadmap coming soon.</p>
<div>
{roadmapItems.length === 0 ? (
<p className="text-sm text-zinc-500">
No roadmap items yet check back soon.
</p>
) : (
<>
{roadmapItems.filter((i) => i.status === 'in-progress').length > 0 && (
<div className="mb-8">
<p className="mb-3 text-[10px] font-bold uppercase tracking-[0.18em] text-zinc-600">
In Progress
</p>
<div className="flex flex-col gap-2.5">
{roadmapItems
.filter((i) => i.status === 'in-progress')
.map((item) => (
<RoadmapItem
key={item._id}
id={item._id}
title={item.title}
description={item.description}
status={item.status}
vote={
voteState[item._id] ?? {
count: 0,
voted: false,
loading: false,
}
}
onVote={handleVote}
/>
))}
</div>
</div>
)}
{roadmapItems.filter((i) => i.status === 'planned').length > 0 && (
<div>
<p className="mb-3 text-[10px] font-bold uppercase tracking-[0.18em] text-zinc-600">
Planned
</p>
<div className="flex flex-col gap-2.5">
{roadmapItems
.filter((i) => i.status === 'planned')
.map((item) => (
<RoadmapItem
key={item._id}
id={item._id}
title={item.title}
description={item.description}
status={item.status}
vote={
voteState[item._id] ?? {
count: 0,
voted: false,
loading: false,
}
}
onVote={handleVote}
/>
))}
</div>
</div>
)}
<p className="mt-8 text-xs text-zinc-600">
Don&apos;t see what you need?{' '}
<button
onClick={() => handleTabChange('give-feedback')}
className="underline underline-offset-2 hover:text-zinc-300"
>
Give us feedback
</button>
</p>
</>
)}
</div>
)}
{/* ── Give Feedback — stub, filled in Task 6 ── */}