From d3874848724d6321b82c606d78c690da1a15ef0a Mon Sep 17 00:00:00 2001 From: Sean Date: Mon, 30 Mar 2026 16:37:41 -0400 Subject: [PATCH] feat: add /changelog page with What's New tab RSC page fetches changelog entries and roadmap items from Sanity with graceful error fallback. ChangelogTabs client component renders tabbed UI with What's New fully implemented; What's Next and Give Feedback are stubs for Tasks 3 and 6. Co-Authored-By: Claude Sonnet 4.6 --- app/(app)/changelog/page.tsx | 45 ++++++++++++ components/ChangelogTabs.tsx | 131 +++++++++++++++++++++++++++++++++++ 2 files changed, 176 insertions(+) create mode 100644 app/(app)/changelog/page.tsx create mode 100644 components/ChangelogTabs.tsx diff --git a/app/(app)/changelog/page.tsx b/app/(app)/changelog/page.tsx new file mode 100644 index 0000000..8c349a6 --- /dev/null +++ b/app/(app)/changelog/page.tsx @@ -0,0 +1,45 @@ +import type { Metadata } from 'next' +import { changelogEntriesQuery, roadmapItemsQuery } from '@/lib/sanity' +import { sanityFetch } from '@/lib/sanityFetch' +import { ChangelogTabs } from '@/components/ChangelogTabs' +import type { ChangelogEntry, RoadmapItemData } from '@/types/changelog' + +export const revalidate = 60 + +export const metadata: Metadata = { + title: "What's New — Battl Builders", + description: + 'Release notes, upcoming features, and product updates from Battl Builders.', +} + +export default async function ChangelogPage() { + let entries: ChangelogEntry[] = [] + let roadmapItems: RoadmapItemData[] = [] + + try { + entries = await sanityFetch(changelogEntriesQuery) + } catch (err) { + console.error('[Changelog] entries fetch failed:', err) + } + + try { + roadmapItems = await sanityFetch(roadmapItemsQuery) + } catch (err) { + console.error('[Changelog] roadmap fetch failed:', err) + } + + return ( +
+
+

+ Battl Builders +

+

Updates

+

+ What we've shipped and what's coming next. +

+
+ +
+ ) +} diff --git a/components/ChangelogTabs.tsx b/components/ChangelogTabs.tsx new file mode 100644 index 0000000..a3e02f7 --- /dev/null +++ b/components/ChangelogTabs.tsx @@ -0,0 +1,131 @@ +'use client' + +import { useEffect, useState } from 'react' +import { PortableTextRenderer } from '@/components/PortableTextRenderer' +import type { ChangelogEntry, RoadmapItemData, Tab, VoteState } 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 = { + 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('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 ( +
+ {/* Tab bar */} +
+ {TABS.map(({ id, label }) => ( + + ))} +
+ + {/* ── What's New ── */} + {activeTab === 'whats-new' && ( +
+ {entries.length === 0 ? ( +

+ No releases yet — check back soon. +

+ ) : ( +
+ {entries.map((entry) => ( +
+
+

+ {entry.version} +

+

+ {formatDate(entry.publishedAt)} +

+
+
+ {entry.tags?.length > 0 && ( +
+ {entry.tags.map((tag) => ( + + {tag} + + ))} +
+ )} +

+ {entry.title} +

+ {entry.body && ( +
+ +
+ )} +
+
+ ))} +
+ )} +
+ )} + + {/* ── What's Next — stub, filled in Task 3 ── */} + {activeTab === 'whats-next' && ( +

Roadmap coming soon.

+ )} + + {/* ── Give Feedback — stub, filled in Task 6 ── */} + {activeTab === 'give-feedback' && ( +

Feedback form coming soon.

+ )} +
+ ) +}