import fs from 'fs';
import path from 'path';
import { Metadata } from 'next';
import DashboardLayout from '@/components/dashboard/DashboardLayout';
import { ClockIcon, CodeBracketIcon, DocumentTextIcon } from '@heroicons/react/24/outline';
export const metadata: Metadata = {
title: 'Changelog - Bookwiz',
description: 'Latest updates and changes to Bookwiz',
};
// Get version from package.json
function getAppVersion() {
try {
const packagePath = path.join(process.cwd(), 'package.json');
const packageJson = JSON.parse(fs.readFileSync(packagePath, 'utf8'));
return packageJson.version || '0.1.0';
} catch (error) {
return '0.1.0';
}
}
function parseChangelog(content: string) {
// Split by version headers (## [version] or ## version)
const sections = content.split(/(?=^## )/gm).filter(section => section.trim());
return sections.map((section, index) => {
const lines = section.split('\n');
const header = lines[0];
const body = lines.slice(1).join('\n').trim();
// Extract version and date from header
const versionMatch = header.match(/## \[?([^\]]+)\]?(?:\s*-\s*(.+))?/);
const version = versionMatch?.[1] || `Section ${index + 1}`;
const date = versionMatch?.[2] || '';
return {
version,
date,
content: body,
isTitle: index === 0 && !versionMatch, // First section might be title
};
});
}
function formatChangelogContent(content: string) {
// Parse markdown-style lists and formatting
const lines = content.split('\n');
const formattedLines: JSX.Element[] = [];
lines.forEach((line, index) => {
if (line.trim() === '') {
formattedLines.push(<br key={index} />);
} else if (line.startsWith('### ')) {
const heading = line.replace('### ', '');
formattedLines.push(
<h4 key={index} className="text-lg font-bold text-white mt-6 mb-3 first:mt-0">
{heading}
</h4>
);
} else if (line.startsWith('- ')) {
const item = line.replace('- ', '');
formattedLines.push(
<div key={index} className="flex items-start space-x-3 mb-2">
<div className="w-1.5 h-1.5 rounded-full bg-blue-400 mt-2 flex-shrink-0"></div>
<span className="text-gray-300">{item}</span>
</div>
);
} else if (line.trim()) {
formattedLines.push(
<p key={index} className="text-gray-300 mb-2">
{line}
</p>
);
}
});
return formattedLines;
}
export default function ChangelogPage() {
const appVersion = getAppVersion();
let changelog = '';
let parsedSections: any[] = [];
try {
const changelogPath = path.join(process.cwd(), 'CHANGELOG.md');
if (fs.existsSync(changelogPath)) {
changelog = fs.readFileSync(changelogPath, 'utf8');
parsedSections = parseChangelog(changelog);
}
} catch (error) {
console.error('Error reading changelog:', error);
}
if (!changelog) {
return (
<div className="min-h-screen bg-black relative overflow-hidden">
{/* Modern gradient background */}
<div className="absolute inset-0 bg-gradient-to-br from-black via-gray-950 to-black pointer-events-none" />
{/* Subtle animated gradient overlay */}
<div className="absolute inset-0 opacity-30 pointer-events-none">
<div className="absolute top-0 left-1/4 w-96 h-96 bg-gradient-to-r from-blue-600/20 to-purple-600/20 rounded-full blur-3xl animate-pulse" />
<div className="absolute bottom-0 right-1/4 w-96 h-96 bg-gradient-to-r from-purple-600/20 to-pink-600/20 rounded-full blur-3xl animate-pulse" style={{ animationDelay: '1s' }} />
</div>
<div className="relative z-10">
<DashboardLayout
title="Changelog"
subtitle={`Track the latest updates and improvements to Bookwiz v${appVersion}`}
>
<div className="max-w-4xl mx-auto">
<div className="bg-white/5 backdrop-blur-sm border border-white/10 rounded-2xl p-8 text-center">
<DocumentTextIcon className="w-16 h-16 mx-auto mb-4 text-gray-400" />
<h3 className="text-xl font-bold text-white mb-2">No changelog available yet</h3>
<p className="text-gray-400 mb-4">
The changelog will be automatically generated from commit messages using conventional commits.
</p>
<div className="text-sm text-gray-500">
<p>Start using conventional commit messages to populate this changelog:</p>
<div className="mt-3 space-y-1 text-left max-w-md mx-auto">
<code className="block bg-black/30 rounded px-2 py-1">feat: add new feature</code>
<code className="block bg-black/30 rounded px-2 py-1">fix: resolve bug</code>
<code className="block bg-black/30 rounded px-2 py-1">docs: update documentation</code>
</div>
</div>
</div>
</div>
</DashboardLayout>
</div>
</div>
);
}
return (
<div className="min-h-screen bg-black relative overflow-hidden">
{/* Modern gradient background */}
<div className="absolute inset-0 bg-gradient-to-br from-black via-gray-950 to-black pointer-events-none" />
{/* Subtle animated gradient overlay */}
<div className="absolute inset-0 opacity-30 pointer-events-none">
<div className="absolute top-0 left-1/4 w-96 h-96 bg-gradient-to-r from-blue-600/20 to-purple-600/20 rounded-full blur-3xl animate-pulse" />
<div className="absolute bottom-0 right-1/4 w-96 h-96 bg-gradient-to-r from-purple-600/20 to-pink-600/20 rounded-full blur-3xl animate-pulse" style={{ animationDelay: '1s' }} />
</div>
<div className="relative z-10">
<DashboardLayout
title="Changelog"
subtitle={`Track the latest updates and improvements to Bookwiz v${appVersion}`}
>
<div className="max-w-4xl mx-auto">
{/* Current Version Banner */}
<div className="bg-gradient-to-r from-blue-500/10 to-purple-500/10 border border-white/10 rounded-2xl p-6 mb-8">
<div className="flex items-center justify-between">
<div className="flex items-center space-x-4">
<div className="w-12 h-12 rounded-xl bg-gradient-to-br from-blue-500 to-purple-600 flex items-center justify-center">
<CodeBracketIcon className="w-6 h-6 text-white" />
</div>
<div>
<h3 className="text-lg font-bold text-white">Current Version</h3>
<p className="text-gray-400">Bookwiz v{appVersion}</p>
</div>
</div>
<div className="text-right">
<div className="inline-flex items-center space-x-2 px-3 py-1 bg-green-500/20 text-green-400 rounded-full text-sm font-medium">
<div className="w-2 h-2 bg-green-400 rounded-full animate-pulse"></div>
<span>Live</span>
</div>
</div>
</div>
</div>
{/* Changelog Sections */}
<div className="space-y-6">
{parsedSections.map((section, index) => {
if (section.isTitle) {
return null; // Skip title section as we have our own header
}
const isUnreleased = section.version.toLowerCase() === 'unreleased';
return (
<div key={index} className="bg-white/5 backdrop-blur-sm border border-white/10 rounded-2xl overflow-hidden">
<div className={`px-6 py-4 border-b border-white/10 ${
isUnreleased
? 'bg-gradient-to-r from-amber-500/20 to-orange-500/20'
: 'bg-gradient-to-r from-blue-500/10 to-purple-500/10'
}`}>
<div className="flex items-center justify-between">
<div className="flex items-center space-x-3">
{isUnreleased ? (
<div className="w-8 h-8 rounded-lg bg-gradient-to-br from-amber-500 to-orange-600 flex items-center justify-center">
<ClockIcon className="w-4 h-4 text-white" />
</div>
) : (
<div className="w-8 h-8 rounded-lg bg-gradient-to-br from-blue-500 to-purple-600 flex items-center justify-center">
<DocumentTextIcon className="w-4 h-4 text-white" />
</div>
)}
<h2 className="text-xl font-black text-white">
{isUnreleased ? 'Unreleased' : (section.version.startsWith('v') ? section.version : `v${section.version}`)}
</h2>
{isUnreleased && (
<span className="inline-flex items-center px-2 py-1 rounded-full text-xs font-medium bg-amber-500/20 text-amber-400">
Coming Soon
</span>
)}
</div>
{section.date && !isUnreleased && (
<span className="text-gray-400 text-sm font-medium">{section.date}</span>
)}
</div>
</div>
<div className="px-6 py-6">
<div className="space-y-1">
{formatChangelogContent(section.content)}
</div>
</div>
</div>
);
})}
</div>
{/* Footer */}
<div className="mt-12 text-center">
<div className="bg-white/5 backdrop-blur-sm border border-white/10 rounded-2xl p-6">
<p className="text-gray-400 text-sm mb-3">
This changelog is automatically generated from our commit messages.
</p>
<p className="text-gray-500 text-xs">
<span className="text-gray-400">Repository:</span>{' '}
<span className="font-mono">github.com/kristiyanTs/bookwiz.io</span>{' '}
<span className="text-gray-600">(Private)</span>
</p>
</div>
</div>
</div>
</DashboardLayout>
</div>
</div>
);
}