feat: add changelog and versioning, surface it in admin
Bumps version to 0.2.0 (root + apps/web package.json) and adds CHANGELOG.md as the canonical human-readable history, mirrored by apps/web/lib/changelog.ts as the in-app data source (single ChangelogList component renders both). - /changelog: user-facing page, linked from the account dropdown. - /admin/changelog: same list, plus a "Version" card on the admin overview dashboard linking to it. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,24 @@
|
||||
# Changelog
|
||||
|
||||
All notable changes to Epicure are documented here. This file is mirrored in-app at `/changelog` (and in the admin dashboard) via `apps/web/lib/changelog.ts` — update both together.
|
||||
|
||||
## 0.2.0 — 2026-07-12
|
||||
|
||||
### Added
|
||||
- **Batch cooking**: generate a single "mega recipe" covering several meals at once — merged shopping list, steps tagged per dish (including steps that advance several dishes at once), and per-dish storage/reheat guidance. Available from the recipe list's Generate dialog and from the meal planner.
|
||||
- **Leftover expiry reminders**: mark a batch-cook dish as cooked, track its fridge life, see it surfaced on the Pantry page, and get a daily reminder before it goes bad.
|
||||
- **"Cooked it" photo reviews**: attach a photo and written review when rating a recipe.
|
||||
- **Pantry-expiry recipe suggestions**: the Pantry page now surfaces recipes that use up ingredients about to expire.
|
||||
- **Trending collections**: star public collections; browse trending and recently-added ones under Collections → Discover.
|
||||
- **Add to collection from the recipe list**: select recipes and add them to a new or existing collection without leaving the page.
|
||||
- List and compact view modes for the recipe list, alongside the existing grid view.
|
||||
|
||||
### Fixed
|
||||
- Recipe version-comparison diff was unreadable and partially untranslated.
|
||||
- Print/export pages always render light, regardless of the app's dark/light setting.
|
||||
- Presigned photo-upload URLs were signed against an internal-only address in production, breaking uploads.
|
||||
- Several recipe-list and recipe-form UI issues: filter menu closing on every click, tag filter not accepting keystrokes, inconsistent card badge styling, missing tooltips, a floating save button on long recipe forms.
|
||||
|
||||
## 0.1.0 — earlier
|
||||
|
||||
Initial feature set: recipes (create, edit, AI-generate, import from URL/photo), meal planning, shopping lists, pantry tracking, collections, social features (follow, comment, rate, notifications), nutrition tracking, admin dashboard, and account/billing basics.
|
||||
@@ -0,0 +1,19 @@
|
||||
import type { Metadata } from "next";
|
||||
import { ChangelogList } from "@/components/shared/changelog-list";
|
||||
import { APP_VERSION } from "@/lib/changelog";
|
||||
|
||||
export const metadata: Metadata = {};
|
||||
|
||||
export default function ChangelogPage() {
|
||||
return (
|
||||
<div className="space-y-2">
|
||||
<div>
|
||||
<h1 className="text-3xl font-bold tracking-tight">Changelog</h1>
|
||||
<p className="text-muted-foreground mt-1">Currently running v{APP_VERSION}.</p>
|
||||
</div>
|
||||
<div className="pt-4">
|
||||
<ChangelogList />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
import type { Metadata } from "next";
|
||||
import { ChangelogList } from "@/components/shared/changelog-list";
|
||||
import { APP_VERSION } from "@/lib/changelog";
|
||||
|
||||
export const metadata: Metadata = {};
|
||||
|
||||
export default function AdminChangelogPage() {
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold tracking-tight">Changelog</h1>
|
||||
<p className="text-muted-foreground mt-1">Currently running v{APP_VERSION}. Edit apps/web/lib/changelog.ts to update.</p>
|
||||
</div>
|
||||
<ChangelogList />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -3,7 +3,7 @@ import { headers } from "next/headers";
|
||||
import { auth } from "@/lib/auth/server";
|
||||
import { db, users, eq } from "@epicure/db";
|
||||
import Link from "next/link";
|
||||
import { Shield, Users, BookOpen, Settings, BarChart3, ClipboardList, HardDrive, Bot, ArrowLeft, Gauge, Mail, Flag } from "lucide-react";
|
||||
import { Shield, Users, BookOpen, Settings, BarChart3, ClipboardList, HardDrive, Bot, ArrowLeft, Gauge, Mail, Flag, History } from "lucide-react";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
const adminNav = [
|
||||
@@ -17,6 +17,7 @@ const adminNav = [
|
||||
{ href: "/admin/storage", label: "Storage", icon: HardDrive },
|
||||
{ href: "/admin/ai-config", label: "AI Config", icon: Bot },
|
||||
{ href: "/admin/settings", label: "Settings", icon: Settings },
|
||||
{ href: "/admin/changelog", label: "Changelog", icon: History },
|
||||
];
|
||||
|
||||
export default async function AdminLayout({ children }: { children: React.ReactNode }) {
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import type { Metadata } from "next";
|
||||
import Link from "next/link";
|
||||
import { db } from "@epicure/db";
|
||||
import { users, recipes, userUsage, recipePhotos } from "@epicure/db";
|
||||
import { count, eq, sql } from "@epicure/db";
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import { Users, BookOpen, Sparkles, Image } from "lucide-react";
|
||||
import { Users, BookOpen, Sparkles, Image, History } from "lucide-react";
|
||||
import { APP_VERSION, CHANGELOG } from "@/lib/changelog";
|
||||
|
||||
export const metadata: Metadata = {};
|
||||
|
||||
@@ -46,6 +48,22 @@ export default async function AdminPage() {
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<Card>
|
||||
<CardHeader className="flex flex-row items-center justify-between pb-2">
|
||||
<CardTitle className="text-sm font-medium text-muted-foreground">Version</CardTitle>
|
||||
<History className="h-4 w-4 text-muted-foreground" />
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="text-2xl font-bold">v{APP_VERSION}</div>
|
||||
<p className="text-xs text-muted-foreground mt-1">
|
||||
Released {CHANGELOG[0]?.date} ·{" "}
|
||||
<Link href="/admin/changelog" className="underline hover:text-foreground">
|
||||
View changelog
|
||||
</Link>
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
import Link from "next/link";
|
||||
import { usePathname, useRouter } from "next/navigation";
|
||||
import { useTheme } from "next-themes";
|
||||
import { BookOpen, Calendar, Package, ChefHat, User, Rss, FolderOpen, ShoppingCart, Shield, Search, Compass, Menu, Sun, Moon, Monitor, Apple } from "lucide-react";
|
||||
import { BookOpen, Calendar, Package, ChefHat, User, Rss, FolderOpen, ShoppingCart, Shield, Search, Compass, Menu, Sun, Moon, Monitor, Apple, History } from "lucide-react";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { Button, buttonVariants } from "@/components/ui/button";
|
||||
import {
|
||||
@@ -131,6 +131,12 @@ export function Nav() {
|
||||
<DropdownMenuItem>
|
||||
<Link href="/settings" className="w-full">{t("settings")}</Link>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem>
|
||||
<Link href="/changelog" className="w-full flex items-center gap-2">
|
||||
<History className="h-3.5 w-3.5 text-muted-foreground" />
|
||||
{t("changelog")}
|
||||
</Link>
|
||||
</DropdownMenuItem>
|
||||
<div
|
||||
role="group"
|
||||
aria-label={t("systemMode")}
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Separator } from "@/components/ui/separator";
|
||||
import { CHANGELOG } from "@/lib/changelog";
|
||||
|
||||
export function ChangelogList() {
|
||||
return (
|
||||
<div className="space-y-8 max-w-2xl">
|
||||
{CHANGELOG.map((entry, i) => (
|
||||
<div key={entry.version} className="space-y-3">
|
||||
<div className="flex items-center gap-2">
|
||||
<Badge variant={i === 0 ? "default" : "secondary"}>v{entry.version}</Badge>
|
||||
<span className="text-sm text-muted-foreground">{entry.date}</span>
|
||||
</div>
|
||||
|
||||
{entry.notes && <p className="text-sm text-muted-foreground">{entry.notes}</p>}
|
||||
|
||||
{entry.added && entry.added.length > 0 && (
|
||||
<div className="space-y-1.5">
|
||||
<p className="text-xs font-medium uppercase tracking-wide text-muted-foreground">Added</p>
|
||||
<ul className="space-y-1 text-sm list-disc pl-5">
|
||||
{entry.added.map((line, j) => <li key={j}>{line}</li>)}
|
||||
</ul>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{entry.fixed && entry.fixed.length > 0 && (
|
||||
<div className="space-y-1.5">
|
||||
<p className="text-xs font-medium uppercase tracking-wide text-muted-foreground">Fixed</p>
|
||||
<ul className="space-y-1 text-sm list-disc pl-5">
|
||||
{entry.fixed.map((line, j) => <li key={j}>{line}</li>)}
|
||||
</ul>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{i < CHANGELOG.length - 1 && <Separator className="mt-6" />}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
// Mirrors CHANGELOG.md at the repo root — update both together.
|
||||
export const APP_VERSION = "0.2.0";
|
||||
|
||||
export type ChangelogEntry = {
|
||||
version: string;
|
||||
date: string;
|
||||
added?: string[];
|
||||
fixed?: string[];
|
||||
notes?: string;
|
||||
};
|
||||
|
||||
export const CHANGELOG: ChangelogEntry[] = [
|
||||
{
|
||||
version: "0.2.0",
|
||||
date: "2026-07-12",
|
||||
added: [
|
||||
"Batch cooking: generate a single \"mega recipe\" covering several meals at once — merged shopping list, steps tagged per dish, and per-dish storage/reheat guidance.",
|
||||
"Leftover expiry reminders: mark a batch-cook dish as cooked, track its fridge life, and get a reminder before it goes bad.",
|
||||
"\"Cooked it\" photo reviews: attach a photo and written review when rating a recipe.",
|
||||
"Pantry-expiry recipe suggestions on the Pantry page.",
|
||||
"Trending collections: star public collections, browse trending/recent under Collections → Discover.",
|
||||
"Add to collection directly from the recipe list.",
|
||||
"List and compact view modes for the recipe list.",
|
||||
],
|
||||
fixed: [
|
||||
"Recipe version-comparison diff was unreadable and partially untranslated.",
|
||||
"Print/export pages now always render light, regardless of app theme.",
|
||||
"Presigned photo-upload URLs were signed against an internal-only address in production.",
|
||||
"Recipe list/form UI issues: filter menu closing on every click, tag filter not accepting keystrokes, inconsistent badge styling, missing tooltips, floating save button on long forms.",
|
||||
],
|
||||
},
|
||||
{
|
||||
version: "0.1.0",
|
||||
date: "earlier",
|
||||
notes: "Initial feature set: recipes, meal planning, shopping lists, pantry tracking, collections, social features, nutrition tracking, admin dashboard, account/billing basics.",
|
||||
},
|
||||
];
|
||||
@@ -11,6 +11,7 @@
|
||||
"pantry": "Pantry",
|
||||
"shopping": "Shopping",
|
||||
"settings": "Settings",
|
||||
"changelog": "Changelog",
|
||||
"admin": "Admin",
|
||||
"signOut": "Sign out",
|
||||
"apiKeys": "API keys",
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
"pantry": "Garde-manger",
|
||||
"shopping": "Courses",
|
||||
"settings": "Paramètres",
|
||||
"changelog": "Journal des modifications",
|
||||
"admin": "Admin",
|
||||
"signOut": "Se déconnecter",
|
||||
"apiKeys": "Clés API",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@epicure/web",
|
||||
"version": "0.1.0",
|
||||
"version": "0.2.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "next dev",
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "epicure",
|
||||
"version": "0.0.0",
|
||||
"version": "0.2.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "pnpm --filter web dev",
|
||||
|
||||
Reference in New Issue
Block a user