feat(ajustes): adicionar aba changelog com histórico de versões

Parser lê o CHANGELOG.md e exibe as versões com badges por tipo de mudança
(Adicionado, Alterado, Corrigido) na página de ajustes.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Felipe Coutinho
2026-02-06 17:45:24 +00:00
parent 95e9a61741
commit 390754c0e8
4 changed files with 124 additions and 0 deletions

View File

@@ -11,6 +11,7 @@ e este projeto adere ao [Versionamento Semântico](https://semver.org/lang/pt-BR
- Calculadora arrastável via drag handle no header do dialog
- Callback `onSelectValue` na calculadora para inserir valor diretamente no campo de lançamento
- Aba "Changelog" em Ajustes com histórico de versões parseado do CHANGELOG.md
### Alterado

View File

@@ -1,6 +1,7 @@
import { headers } from "next/headers";
import { redirect } from "next/navigation";
import { ChangelogTab } from "@/components/ajustes/changelog-tab";
import { CompanionTab } from "@/components/ajustes/companion-tab";
import { DeleteAccountForm } from "@/components/ajustes/delete-account-form";
import { PreferencesForm } from "@/components/ajustes/preferences-form";
@@ -10,6 +11,7 @@ import { UpdatePasswordForm } from "@/components/ajustes/update-password-form";
import { Card } from "@/components/ui/card";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { auth } from "@/lib/auth/config";
import { parseChangelog } from "@/lib/changelog/parse-changelog";
import { fetchAjustesPageData } from "./data";
@@ -28,6 +30,8 @@ export default async function Page() {
const { authProvider, userPreferences, userApiTokens } =
await fetchAjustesPageData(session.user.id);
const changelogVersions = parseChangelog();
return (
<div className="w-full">
<Tabs defaultValue="preferencias" className="w-full">
@@ -37,6 +41,7 @@ export default async function Page() {
<TabsTrigger value="nome">Alterar nome</TabsTrigger>
<TabsTrigger value="senha">Alterar senha</TabsTrigger>
<TabsTrigger value="email">Alterar e-mail</TabsTrigger>
<TabsTrigger value="changelog">Changelog</TabsTrigger>
<TabsTrigger value="deletar" className="text-destructive">
Deletar conta
</TabsTrigger>
@@ -114,6 +119,10 @@ export default async function Page() {
</Card>
</TabsContent>
<TabsContent value="changelog" className="mt-4">
<ChangelogTab versions={changelogVersions} />
</TabsContent>
<TabsContent value="deletar" className="mt-4">
<Card className="p-6">
<div className="space-y-4">

View File

@@ -0,0 +1,54 @@
import { Badge } from "@/components/ui/badge";
import { Card } from "@/components/ui/card";
import type { ChangelogVersion } from "@/lib/changelog/parse-changelog";
const sectionBadgeVariant: Record<
string,
"success" | "info" | "destructive" | "secondary"
> = {
Adicionado: "success",
Alterado: "info",
Corrigido: "destructive",
Removido: "secondary",
};
function getSectionVariant(type: string) {
return sectionBadgeVariant[type] ?? "secondary";
}
export function ChangelogTab({ versions }: { versions: ChangelogVersion[] }) {
return (
<div className="space-y-4">
{versions.map((version) => (
<Card key={version.version} className="p-6">
<div className="flex items-baseline gap-3">
<h3 className="text-lg font-bold">v{version.version}</h3>
<span className="text-sm text-muted-foreground">
{version.date}
</span>
</div>
<div className="space-y-4">
{version.sections.map((section) => (
<div key={section.type}>
<Badge
variant={getSectionVariant(section.type)}
className="mb-2"
>
{section.type}
</Badge>
<ul className="space-y-1.5 text-muted-foreground">
{section.items.map((item) => (
<li key={item} className="flex gap-2">
<span className="text-primary select-none">&bull;</span>
<span className="text-sm">{item}</span>
</li>
))}
</ul>
</div>
))}
</div>
</Card>
))}
</div>
);
}

View File

@@ -0,0 +1,60 @@
import fs from "node:fs";
import path from "node:path";
export type ChangelogSection = {
type: string;
items: string[];
};
export type ChangelogVersion = {
version: string;
date: string;
sections: ChangelogSection[];
};
export function parseChangelog(): ChangelogVersion[] {
const filePath = path.join(process.cwd(), "CHANGELOG.md");
const content = fs.readFileSync(filePath, "utf-8");
const lines = content.split("\n");
const versions: ChangelogVersion[] = [];
let currentVersion: ChangelogVersion | null = null;
let currentSection: ChangelogSection | null = null;
for (const line of lines) {
const versionMatch = line.match(/^## \[(.+?)\] - (.+)$/);
if (versionMatch) {
if (currentSection && currentVersion) {
currentVersion.sections.push(currentSection);
}
currentVersion = {
version: versionMatch[1],
date: versionMatch[2],
sections: [],
};
versions.push(currentVersion);
currentSection = null;
continue;
}
const sectionMatch = line.match(/^### (.+)$/);
if (sectionMatch && currentVersion) {
if (currentSection) {
currentVersion.sections.push(currentSection);
}
currentSection = { type: sectionMatch[1], items: [] };
continue;
}
const itemMatch = line.match(/^- (.+)$/);
if (itemMatch && currentSection) {
currentSection.items.push(itemMatch[1]);
}
}
if (currentSection && currentVersion) {
currentVersion.sections.push(currentSection);
}
return versions;
}