Files
mdm-ordinis/ordinis-admin-ui/src/components/version/UpdateBanner.tsx
T
2026-05-11 10:29:45 +00:00

79 lines
3.1 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { useTranslation } from 'react-i18next'
import { Button } from '@/ui'
import { ArrowClockwise, BookOpen, X } from '@phosphor-icons/react'
import { useState } from 'react'
import { useAppVersion } from './useAppVersion'
const DOCS_URL = '/docs/' // ordinis-docs deployment, mirrors prod /docs path
/**
* Banner — показывается top-of-page когда client bundle отстаёт от server.
*
* <p>Pattern такой же как Altum VS / Geoportal: persistent strip, contextual
* CTAs, dismissable (но re-appears после next refetch если still mismatched).
*
* <p>Two CTAs:
* <ul>
* <li><b>Обновить</b> — {@code window.location.reload()}. Browser fetches new
* bundle (cache-busting через Vite hashed filenames).</li>
* <li><b>Что нового →</b> — opens user руководство (changelog / release notes).</li>
* </ul>
*
* <p>Dismiss button (×) — temporarily скрывает banner до следующего
* {@code useServerVersion} refetch (5 min). Не persists в localStorage —
* критичный update должен progressively становиться более insistent.
*/
export function UpdateBanner() {
const { t } = useTranslation()
const { updateAvailable, serverVersion, serverCommit, serverTag } = useAppVersion()
const [dismissed, setDismissed] = useState(false)
if (!updateAvailable || dismissed) return null
// serverTag === 'none' для branch builds (Spring Boot build-info default
// sentinel non-null). Не показываем "none" как версию — fallback к
// serverVersion (e.g. "0.1.0-SNAPSHOT") и потом к 7-char commit hash.
const tag = serverTag && serverTag !== 'none' ? serverTag : null
const versionLabel = tag || serverVersion || serverCommit?.substring(0, 7) || ''
return (
<div
role="status"
aria-live="polite"
className="bg-accent text-on-accent"
>
<div className="max-w-6xl mx-auto px-4 sm:px-6 py-2 flex items-center gap-3 flex-wrap text-sm">
<ArrowClockwise size={18} weight="bold" className="shrink-0" aria-hidden />
<span className="flex-1 min-w-0">
{t('updateBanner.message', { version: versionLabel })}
</span>
<div className="flex items-center gap-2 ml-auto">
<Button
size="sm"
variant="secondary"
onClick={() => window.open(DOCS_URL, '_blank', 'noopener,noreferrer')}
>
<BookOpen size={16} weight="regular" className="mr-1" aria-hidden />
{t('updateBanner.docs')}
</Button>
<Button
size="sm"
variant="primary"
onClick={() => window.location.reload()}
>
{t('updateBanner.reload')}
</Button>
<button
type="button"
aria-label={t('updateBanner.dismiss') as string}
onClick={() => setDismissed(true)}
className="ml-1 p-1 rounded hover:bg-white/10"
>
<X size={16} weight="bold" aria-hidden />
</button>
</div>
</div>
</div>
)
}