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.

+ )} +
+ ) +}