Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
198 changes: 172 additions & 26 deletions src/app/changelog/[slug]/page.tsx

Large diffs are not rendered by default.

847 changes: 499 additions & 348 deletions src/app/changelog/page.tsx

Large diffs are not rendered by default.

156 changes: 156 additions & 0 deletions src/components/changelog/CurrentReleaseHero.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
import Link from "next/link";
import {
Download,
ArrowRight,
ExternalLink,
Calendar,
CheckCircle2,
BookOpen,
} from "lucide-react";
import type { ChangelogEntry } from "@/lib/changelog";
import { formatDate } from "@passcodes/passalgo";
import { USER_GUIDE_URL } from "@/lib/constants";

interface CurrentReleaseHeroProps {
release: ChangelogEntry;
}

export function CurrentReleaseHero({ release }: CurrentReleaseHeroProps) {
const highlights = release.highlights?.slice(0, 4) || [];

return (
<section
aria-labelledby="current-production-release-heading"
className="relative overflow-hidden rounded-2xl border border-[var(--accent-border)] bg-[var(--card-bg-solid)] p-6 sm:p-8 shadow-sm transition-all"
>
{/* Ambient subtle glow */}
<div
aria-hidden="true"
className="pointer-events-none absolute -right-24 -top-24 h-72 w-72 rounded-full bg-[var(--accent-subtle)] blur-3xl"
/>

<div className="relative z-10">
{/* Header & Badges */}
<div className="flex flex-wrap items-center justify-between gap-3 border-b border-[var(--border-light)] pb-5">
<div className="flex flex-wrap items-center gap-2.5">
<span className="inline-flex items-center gap-1.5 rounded-full border border-emerald-500/30 bg-emerald-500/10 px-3 py-1 text-xs font-semibold text-emerald-600 dark:text-emerald-400">
<span className="h-2 w-2 rounded-full bg-emerald-500 animate-pulse" />
Current Production Release
</span>
<span className="tag stable shrink-0">
{release.releaseType}
</span>
<span className="font-mono text-sm font-bold text-[var(--accent-light)]">
{release.version}
</span>
</div>

<div className="flex items-center gap-1.5 text-xs text-[var(--text-muted)]">
<Calendar className="h-3.5 w-3.5 text-[var(--text-dim)]" aria-hidden="true" />
<time dateTime={release.date}>
{formatDate(release.date)}
</time>
</div>
</div>

{/* Title & Editorial Summary */}
<div className="mt-5">
<h2
id="current-production-release-heading"
className="text-xl font-bold tracking-tight text-[var(--text)] sm:text-2xl"
>
{release.title}
</h2>
<p className="mt-2.5 text-sm leading-relaxed text-[var(--text-muted)] sm:text-base">
{release.summary}
</p>
</div>

{/* Important Highlights */}
{highlights.length > 0 && (
<div className="mt-6 rounded-xl border border-[var(--border-light)] bg-[var(--card-bg)] p-4 sm:p-5">
<div className="mb-3 flex items-center gap-2 text-xs font-semibold uppercase tracking-wider text-[var(--text-dim)]">
<CheckCircle2 className="h-3.5 w-3.5 text-[var(--accent-light)]" aria-hidden="true" />
<span>Key Highlights in this Release</span>
</div>
<ul className="grid gap-2 sm:grid-cols-2">
{highlights.map((item, idx) => (
<li
key={idx}
className="flex items-start gap-2.5 text-xs text-[var(--text-muted)] sm:text-sm"
>
<span className="mt-1.5 h-1.5 w-1.5 shrink-0 rounded-full bg-[var(--accent-light)]" />
<span className="leading-relaxed">{item}</span>
</li>
))}
</ul>
</div>
)}

{/* Build Specifications Pill Group */}
{release.internalDetails && (
<div className="mt-4 flex flex-wrap items-center gap-2 text-xs text-[var(--text-dim)]">
<span className="font-medium text-[var(--text-muted)]">Specs:</span>
{release.internalDetails.expoSdk && (
<span className="rounded-md border border-[var(--border-light)] bg-[var(--card-bg)] px-2 py-0.5 font-mono text-[11px]">
Expo SDK {release.internalDetails.expoSdk}
</span>
)}
{release.internalDetails.minAndroid && (
<span className="rounded-md border border-[var(--border-light)] bg-[var(--card-bg)] px-2 py-0.5 font-mono text-[11px]">
Android {release.internalDetails.minAndroid.split(" ")[0]}+
</span>
)}
{release.internalDetails.masterDbVersion && (
<span className="rounded-md border border-[var(--border-light)] bg-[var(--card-bg)] px-2 py-0.5 font-mono text-[11px]">
DB Schema {release.internalDetails.masterDbVersion}
</span>
)}
</div>
)}

{/* Actions */}
<div className="mt-6 flex flex-wrap items-center gap-3 pt-3 border-t border-[var(--border-light)]">
<Link
href="/downloads"
className="btn btn-filled btn-small inline-flex items-center gap-1.5"
>
<Download className="h-3.5 w-3.5" aria-hidden="true" />
<span>Get {release.version} APK</span>
</Link>

<Link
href={`/changelog/${release.slug}`}
className="btn btn-outline btn-small inline-flex items-center gap-1.5"
>
<span>Release Details</span>
<ArrowRight className="h-3.5 w-3.5" aria-hidden="true" />
</Link>

<Link
href={USER_GUIDE_URL}
target="_blank"
rel="noopener noreferrer"
className="btn btn-ghost btn-small inline-flex items-center gap-1.5"
>
<BookOpen className="h-3.5 w-3.5" aria-hidden="true" />
<span>Documentation</span>
<ExternalLink className="h-3 w-3 opacity-60" aria-hidden="true" />
</Link>

{release.githubUrl && (
<Link
href={release.githubUrl}
target="_blank"
rel="noopener noreferrer"
className="btn btn-ghost btn-small inline-flex items-center gap-1.5 text-[var(--text-dim)]"
>
<span>GitHub Release</span>
<ExternalLink className="h-3 w-3 opacity-60" aria-hidden="true" />
</Link>
)}
</div>
</div>
</section>
);
}
109 changes: 109 additions & 0 deletions src/components/changelog/MilestonesSection.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
import Link from "next/link";
import { ArrowRight, Layers, Milestone, GitCommit } from "lucide-react";
import type { ChangelogEntry } from "@/lib/changelog";
import { formatDate } from "@passcodes/passalgo";
import { isDeprecatedEntry } from "@/lib/changelog";

interface MilestonesSectionProps {
milestones: ChangelogEntry[];
}

export function MilestonesSection({ milestones }: MilestonesSectionProps) {
if (milestones.length === 0) return null;

return (
<section aria-labelledby="architecture-milestones-heading" className="space-y-6">
<div>
<span className="editorial-badge mb-2 border border-[var(--border-light)] bg-[var(--card-bg)] text-[var(--accent-light)]">
Architecture &amp; History
</span>
<h2
id="architecture-milestones-heading"
className="text-2xl font-bold tracking-tight text-[var(--text)] sm:text-3xl"
>
Major Architectural Milestones
</h2>
<p className="mt-1.5 text-sm text-[var(--text-muted)] sm:text-base">
Documented architectural shifts and foundational transitions across Passcodes&apos; open-source history.
</p>
</div>

<div className="grid gap-4 sm:grid-cols-2">
{milestones.map((milestone) => {
const isExplicitMilestone = milestone.isMilestone;
const isDeprecated = isDeprecatedEntry(milestone);

return (
<div
key={milestone.slug}
className="subtle-card group flex flex-col justify-between rounded-xl border border-[var(--border-light)] bg-[var(--card-bg)] p-5 transition-all hover:border-[var(--accent-border)]"
>
<div>
<div className="mb-2.5 flex flex-wrap items-center gap-2">
{isExplicitMilestone ? (
<span className="editorial-badge border-[var(--accent-light)]/40 bg-[var(--accent-light)]/15 font-semibold text-[var(--accent-light)]">
Architecture Milestone
</span>
) : (
<span className="editorial-badge border-[var(--border-light)] bg-[var(--card-bg-solid)] text-[var(--text-muted)]">
Milestone
</span>
)}

{isDeprecated && (
<span className="tag deprecated">
Deprecated
</span>
)}

<span className="font-mono text-xs font-semibold text-[var(--text)]">
{milestone.version}
</span>

<span className="text-xs text-[var(--text-dim)]">
· {formatDate(milestone.date)}
</span>
</div>

<h3 className="text-base font-bold tracking-tight text-[var(--text)] group-hover:text-[var(--accent-light)] transition-colors">
<Link href={`/changelog/${milestone.slug}`}>
{milestone.title}
</Link>
</h3>

<p className="mt-2 text-xs leading-relaxed text-[var(--text-muted)] sm:text-sm">
{milestone.summary}
</p>

{milestone.highlights && milestone.highlights.length > 0 && (
<ul className="mt-3 space-y-1.5 border-t border-[var(--border-lighter)] pt-2.5">
{milestone.highlights.slice(0, 2).map((item, idx) => (
<li
key={idx}
className="flex items-start gap-2 text-xs text-[var(--text-dim)]"
>
<span className="mt-1 h-1 w-1 shrink-0 rounded-full bg-[var(--accent-light)]" />
<span className="line-clamp-2">{item}</span>
</li>
))}
</ul>
)}
</div>

<div className="mt-4 pt-3 border-t border-[var(--border-light)]">
<Link
href={`/changelog/${milestone.slug}`}
aria-label={`Read details for ${milestone.version}`}
className="inline-flex items-center gap-1.5 text-xs font-semibold text-[var(--accent-light)] transition-all hover:gap-2"
>
<span>Read details</span>
<ArrowRight className="h-3 w-3 transition-transform group-hover:translate-x-0.5" aria-hidden="true" />
</Link>
</div>
</div>
);
})}
</div>
</section>
);
}
117 changes: 117 additions & 0 deletions src/components/changelog/RecentUpdatesGrid.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
import Link from "next/link";
import { ArrowRight, Calendar } from "lucide-react";
import type { ChangelogEntry } from "@/lib/changelog";
import { ReleaseVisual } from "@/components/visuals/ReleaseVisual";
import { formatDate } from "@passcodes/passalgo";
import { cn } from "@/lib/utils";

interface RecentUpdatesGridProps {
entries: ChangelogEntry[];
}

export function RecentUpdatesGrid({ entries }: RecentUpdatesGridProps) {
if (entries.length === 0) return null;

return (
<section aria-labelledby="recent-updates-heading" className="space-y-6">
<div>
<span className="editorial-badge mb-2 border border-[var(--border-light)] bg-[var(--card-bg)] text-[var(--accent-light)]">
What&apos;s New
</span>
<h2
id="recent-updates-heading"
className="text-2xl font-bold tracking-tight text-[var(--text)] sm:text-3xl"
>
Recent Updates
</h2>
<p className="mt-1.5 text-sm text-[var(--text-muted)] sm:text-base">
The latest stable releases, iterative improvements, and maintenance builds.
</p>
</div>

<div className="grid gap-5 sm:grid-cols-2 lg:grid-cols-3">
{entries.map((entry) => (
<div
key={entry.slug}
className="subtle-card group flex h-full flex-col justify-between transition-all hover:border-[var(--accent-border)] hover:shadow-sm"
>
<div>
{/* Card Top: Channel, Date, Category */}
<div className="mb-3.5 flex items-center justify-between gap-2">
<div className="flex flex-wrap items-center gap-1.5">
<span
className={cn(
"tag",
entry.releaseType === "Stable" && "tag stable",
entry.releaseType === "Beta" && "tag beta",
entry.releaseType === "Alpha" && "tag alpha"
)}
>
{entry.releaseType}
</span>
<span className="font-mono text-xs font-semibold text-[var(--accent-light)]">
{entry.version}
</span>
</div>

<div className="rounded-md border border-[var(--border-light)] bg-[var(--card-bg)] p-1">
<ReleaseVisual
category={entry.category}
className="h-3.5 w-3.5"
/>
</div>
</div>

{/* Date */}
<div className="mb-2 flex items-center gap-1 text-[11px] text-[var(--text-dim)]">
<Calendar className="h-3 w-3" aria-hidden="true" />
<time dateTime={entry.date}>
{formatDate(entry.date)}
</time>
</div>

{/* Title */}
<h3 className="text-base font-bold tracking-tight text-[var(--text)] transition-colors group-hover:text-[var(--accent-light)]">
<Link href={`/changelog/${entry.slug}`}>
{entry.title}
</Link>
</h3>

{/* Summary */}
<p className="mt-2 line-clamp-2 text-xs leading-relaxed text-[var(--text-muted)]">
{entry.summary}
</p>

{/* Highlights Preview */}
{entry.highlights && entry.highlights.length > 0 && (
<ul className="mt-3 space-y-1.5 border-t border-[var(--border-lighter)] pt-2.5">
{entry.highlights.slice(0, 2).map((h, i) => (
<li
key={i}
className="flex items-start gap-2 text-[11px] text-[var(--text-dim)]"
>
<span className="mt-1 h-1 w-1 shrink-0 rounded-full bg-[var(--accent-light)]" />
<span className="line-clamp-1">{h}</span>
</li>
))}
</ul>
)}
</div>

{/* Card Footer Link */}
<div className="mt-4 pt-3 border-t border-[var(--border-light)]">
<Link
href={`/changelog/${entry.slug}`}
aria-label={`Read release notes for ${entry.version}`}
className="inline-flex items-center gap-1 text-xs font-semibold text-[var(--accent-light)] transition-all hover:gap-1.5"
>
<span>Read release notes</span>
<ArrowRight className="h-3 w-3 transition-transform group-hover:translate-x-0.5" aria-hidden="true" />
</Link>
</div>
</div>
))}
</div>
</section>
);
}
Loading
Loading