Files
shadow-gunbuilder-ai-proto/components/ChangelogTabs.tsx
T

132 lines
4.2 KiB
TypeScript

'use client'
import { useEffect, useState } from 'react'
import { PortableTextRenderer } from '@/components/PortableTextRenderer'
import type { ChangelogEntry, RoadmapItemData, Tab } from '@/types/changelog'
const TABS: { id: Tab; label: string }[] = [
{ id: 'whats-new', label: "What's New" },
{ id: 'whats-next', label: "What's Next" },
{ id: 'give-feedback', label: 'Give Feedback' },
]
const TAG_STYLES: Record<string, string> = {
feature: 'bg-amber-500/10 text-amber-400 border border-amber-500/25',
fix: 'bg-green-500/10 text-green-400 border border-green-500/20',
improvement: 'bg-blue-400/10 text-blue-400 border border-blue-400/20',
}
function formatDate(iso: string) {
return new Date(iso).toLocaleDateString('en-US', {
year: 'numeric',
month: 'long',
day: 'numeric',
})
}
export function ChangelogTabs({
entries,
roadmapItems,
}: {
entries: ChangelogEntry[]
roadmapItems: RoadmapItemData[]
}) {
const [activeTab, setActiveTab] = useState<Tab>('whats-new')
useEffect(() => {
const hash = window.location.hash.replace('#', '') as Tab
if (TABS.some((t) => t.id === hash)) setActiveTab(hash)
}, [])
function handleTabChange(tab: Tab) {
setActiveTab(tab)
window.history.replaceState(null, '', `#${tab}`)
}
return (
<div>
{/* Tab bar */}
<div className="mb-10 flex border-b border-zinc-800">
{TABS.map(({ id, label }) => (
<button
key={id}
onClick={() => handleTabChange(id)}
className={[
'-mb-px border-b-2 px-5 py-2.5 text-sm font-medium transition-colors',
activeTab === id
? 'border-amber-400 text-amber-400'
: 'border-transparent text-zinc-500 hover:text-zinc-300',
].join(' ')}
>
{label}
</button>
))}
</div>
{/* ── What's New ── */}
{activeTab === 'whats-new' && (
<div>
{entries.length === 0 ? (
<p className="text-sm text-zinc-500">
No releases yet check back soon.
</p>
) : (
<div className="divide-y divide-zinc-900">
{entries.map((entry) => (
<div
key={entry._id}
className="grid grid-cols-[120px_1fr] gap-8 py-8"
>
<div className="pt-1">
<p className="font-mono text-xs font-bold text-amber-400">
{entry.version}
</p>
<p className="mt-1 text-[11px] text-zinc-600">
{formatDate(entry.publishedAt)}
</p>
</div>
<div>
{(entry.tags?.length ?? 0) > 0 && (
<div className="mb-2.5 flex flex-wrap gap-1.5">
{(entry.tags ?? []).map((tag) => (
<span
key={tag}
className={`rounded-full px-2 py-0.5 text-[10px] font-bold uppercase tracking-wide ${
TAG_STYLES[tag] ??
'border border-zinc-700 bg-zinc-800 text-zinc-400'
}`}
>
{tag}
</span>
))}
</div>
)}
<h3 className="mb-3 text-[17px] font-semibold text-white">
{entry.title}
</h3>
{entry.body && (
<div className="text-sm text-zinc-400">
<PortableTextRenderer value={entry.body} />
</div>
)}
</div>
</div>
))}
</div>
)}
</div>
)}
{/* ── What's Next — stub, filled in Task 3 ── */}
{activeTab === 'whats-next' && (
<p className="text-sm text-zinc-500">Roadmap coming soon.</p>
)}
{/* ── Give Feedback — stub, filled in Task 6 ── */}
{activeTab === 'give-feedback' && (
<p className="text-sm text-zinc-500">Feedback form coming soon.</p>
)}
</div>
)
}