Docker images / prepare-release (push) Successful in 5s
Docker images / backend-image (push) Successful in 2m36s
Docker images / frontend-image (push) Successful in 2m25s
Docker images / updater-image (push) Successful in 37s
Docker images / notify-webhook (push) Has been skipped
Docker images / publish-release (push) Successful in 6s
113 lines
3.9 KiB
TypeScript
113 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 {
|
|
formatCommitSubject,
|
|
getReleaseCommitPreview,
|
|
RELEASE_COMMIT_PREVIEW_LIMIT,
|
|
} from "@/lib/release-commits"
|
|
import type { ReleaseManifest } from "@/lib/release-manifest"
|
|
import { hasSeenReleaseModal, markReleaseModalSeen } from "@/lib/release-modal-storage"
|
|
|
|
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())
|
|
const commits = getReleaseCommitPreview(manifest?.commits ?? [])
|
|
|
|
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">
|
|
Последние {RELEASE_COMMIT_PREVIEW_LIMIT} коммитов текущей сборки MikrotikManager.
|
|
</DialogDescription>
|
|
</DialogHeader>
|
|
|
|
<div className="min-h-0 flex-1 overflow-y-auto px-6 py-4">
|
|
{commits.length ? (
|
|
<ul className="flex flex-col gap-2">
|
|
{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>
|
|
)
|
|
}
|