import { Alert } from 'antd' import { Link } from 'react-router-dom' import { useQuery } from '@tanstack/react-query' import { useTranslation } from 'react-i18next' import apiClient, { isEnvelope } from '../api/client' interface LicenseStatus { license_key?: string status: string type?: string valid_until?: string expires_at?: string last_error?: string | null } // LicenseBanner shows up in AppLayout next to UpdateBanner. // Three visible states: // - trial-expiring (≤14d remaining) → warning // - expired / invalid → error // - last verify failed → warning (transient) // Everything else stays silent. export default function LicenseBanner() { const { t } = useTranslation() const { data: s } = useQuery({ queryKey: ['license', 'status'], queryFn: async () => { const r = await apiClient.get('/license/status') return isEnvelope(r.data) ? (r.data.data as LicenseStatus) : null }, refetchInterval: 5 * 60 * 1000, }) if (!s) return null const expiry = s.valid_until || s.expires_at const days = expiry ? Math.ceil((new Date(expiry).getTime() - Date.now()) / 86_400_000) : null const isTrial = s.type === 'trial' || !s.license_key if (s.status === 'expired' || s.status === 'invalid') { return ( {t('licenseBanner.expired')}{' '} {t('licenseBanner.cta')} } /> ) } if (isTrial && days !== null && days <= 14) { return ( {t('licenseBanner.trialExpiring', { days })}{' '} {t('licenseBanner.cta')} } /> ) } if (s.last_error) { return ( {t('licenseBanner.verifyFailed')}: {s.last_error}{' '} {t('licenseBanner.openPage')} } closable /> ) } return null }