Files
MikrotikManager/components/release-notes-modal.tsx
T
Denozordec 0435608ef9
Docker images / prepare-release (push) Successful in 5s
Docker images / backend-image (push) Successful in 1m33s
Docker images / frontend-image (push) Successful in 1m39s
Docker images / updater-image (push) Successful in 37s
Docker images / notify-webhook (push) Has been skipped
Docker images / publish-release (push) Successful in 6s
chore: обновить правила коммитов и улучшить форматирование релизных заметок
- Уточнены правила формирования сообщений коммитов, включая использование русского языка и уточнение типов изменений (fix/chore/feat).
- Добавлена функция для форматирования заголовков коммитов в компоненте модалки релизов, улучшая читаемость.
- Обновлены ссылки на правила коммитов в документации для большей ясности.
2026-05-12 18:06:29 +07:00

114 lines
3.9 KiB
TypeScript

"use client"
import * as React from "react"
import Link from "next/link"
import { Badge } from "@/components/ui/badge"
import { Button } from "@/components/ui/button"
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
} from "@/components/ui/dialog"
import { formatAppVersionLabel, getAppVersion } from "@/lib/app-version"
import type { ReleaseManifest } from "@/lib/release-manifest"
import { hasSeenReleaseModal, markReleaseModalSeen } from "@/lib/release-modal-storage"
function formatCommitSubject(subject: string): string {
return subject.replace(
/^(feat|fix|chore|docs|refactor|style|test|build|ci)(\([^)]+\))?!?:\s*/i,
"",
)
}
export function ReleaseNotesModal() {
const [open, setOpen] = React.useState(false)
const [manifest, setManifest] = React.useState<ReleaseManifest | null>(null)
React.useEffect(() => {
let cancelled = false
async function loadManifest() {
try {
const response = await fetch("/release-manifest.json", { cache: "no-store" })
if (!response.ok) return
const data = (await response.json()) as ReleaseManifest
if (cancelled || !data.commits?.length || hasSeenReleaseModal(data.tag)) return
setManifest(data)
setOpen(true)
} catch {
// Локальная сборка или недоступный manifest — модалку не показываем.
}
}
void loadManifest()
return () => {
cancelled = true
}
}, [])
const handleOpenChange = (nextOpen: boolean) => {
setOpen(nextOpen)
if (!nextOpen && manifest) {
markReleaseModalSeen(manifest.tag)
}
}
const versionLabel = formatAppVersionLabel(manifest?.version ?? getAppVersion())
return (
<Dialog open={open} onOpenChange={handleOpenChange}>
<DialogContent className="flex max-h-[min(85vh,720px)] w-full max-w-[calc(100%-2rem)] flex-col gap-0 overflow-hidden p-0 sm:max-w-lg">
<DialogHeader className="gap-1.5 border-b px-6 py-5 pr-12 text-left">
<DialogTitle className="text-base leading-snug">
Что нового в {versionLabel}
</DialogTitle>
<DialogDescription className="text-sm leading-relaxed">
Краткая сводка изменений текущего релиза MikrotikManager.
</DialogDescription>
</DialogHeader>
<div className="min-h-0 flex-1 overflow-y-auto px-6 py-4">
{manifest?.commits.length ? (
<ul className="flex flex-col gap-2">
{manifest.commits.map((commit) => (
<li
key={commit.sha}
className="rounded-lg border bg-card/80 p-3"
>
<div className="flex flex-wrap items-center gap-x-2 gap-y-1">
<Badge variant="secondary" className="font-mono uppercase">
{commit.type}
</Badge>
<span className="font-mono text-xs text-muted-foreground">{commit.shortSha}</span>
<span className="text-xs text-muted-foreground">{commit.author}</span>
</div>
<p className="mt-2 text-sm leading-relaxed text-foreground">
{formatCommitSubject(commit.subject)}
</p>
</li>
))}
</ul>
) : null}
</div>
<DialogFooter className="mx-0 mb-0 shrink-0 gap-3 border-t bg-muted/30 px-6 py-4 sm:flex-row sm:items-center sm:justify-between">
<Button
variant="outline"
render={<Link href="/releases" />}
onClick={() => handleOpenChange(false)}
>
Все релизы
</Button>
<Button onClick={() => handleOpenChange(false)}>Понятно</Button>
</DialogFooter>
</DialogContent>
</Dialog>
)
}