- A site the Hub answers INSTALL_NOT_FOUND for drops its install and licence, so it no longer shows "Active" on the old token. - Unlicensed is read-only like suspended; read-only also blocks editing events, options and variants. LICENCE_CHECK_DISABLED (ignored in production) for e2e. - Heartbeat 30 s after the server starts and after a licence key is connected. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
63 lines
2.7 KiB
TypeScript
63 lines
2.7 KiB
TypeScript
"use client";
|
|
|
|
import React, { useEffect, useState } from "react";
|
|
import Link from "next/link";
|
|
import { ShieldAlert, KeyRound, ArrowUpCircle } from "lucide-react";
|
|
import { apiFetch } from "@/lib/api";
|
|
import { useAuth } from "@/hooks/useAuth";
|
|
import type { HubStatus } from "@/components/admin/LicenceTab";
|
|
|
|
/**
|
|
* Licence and update notices for **site admins only** (never supervisors, staff, users or the
|
|
* public site): grace period, suspended/revoked, not connected, update available.
|
|
*/
|
|
export function LicenceBanner() {
|
|
const { user, token } = useAuth();
|
|
const [status, setStatus] = useState<HubStatus | null>(null);
|
|
const isAdmin = user?.role === "admin";
|
|
|
|
useEffect(() => {
|
|
if (!isAdmin || !token) return;
|
|
apiFetch<HubStatus>("/api/hub/status", { authToken: token }).then(setStatus).catch(() => {});
|
|
}, [isAdmin, token]);
|
|
|
|
if (!isAdmin || !status) return null;
|
|
const link = <Link href="/dashboard/admin/settings?tab=licence" className="font-medium underline">Licence settings</Link>;
|
|
|
|
if (status.state === "grace") {
|
|
return (
|
|
<div role="status" className="bg-amber-50 border-b border-amber-200 text-amber-900 text-sm px-4 py-2 flex items-center gap-2">
|
|
<ShieldAlert className="w-4 h-4 shrink-0" />
|
|
<span>Licence needs renewing — {status.daysLeft} day{status.daysLeft === 1 ? "" : "s"} left. {link}</span>
|
|
</div>
|
|
);
|
|
}
|
|
if (status.state === "suspended" || status.state === "revoked") {
|
|
return (
|
|
<div role="alert" className="bg-red-50 border-b border-red-200 text-red-800 text-sm px-4 py-2 flex items-center gap-2">
|
|
<ShieldAlert className="w-4 h-4 shrink-0" />
|
|
<span>
|
|
The licence is {status.state === "revoked" ? "revoked" : "suspended"}: the site is read-only — events can't be created or edited, and no new registrations or payments. Tickets still scan and all data can be exported. {link}
|
|
</span>
|
|
</div>
|
|
);
|
|
}
|
|
if (status.state === "unlicensed" && status.readOnly) {
|
|
return (
|
|
<div role="alert" className="bg-red-50 border-b border-red-200 text-red-800 text-sm px-4 py-2 flex items-center gap-2">
|
|
<KeyRound className="w-4 h-4 shrink-0" />
|
|
<span>This site has no licence, so it's read-only — events can't be created or edited. Connect your licence key in {link}</span>
|
|
</div>
|
|
);
|
|
}
|
|
if (status.updateAvailable) {
|
|
return (
|
|
<div role="status" className="bg-brand-50 border-b border-brand-100 text-brand-800 text-sm px-4 py-2 flex items-center gap-2">
|
|
<ArrowUpCircle className="w-4 h-4 shrink-0" />
|
|
<span>Hope Events {status.latestVersion} is available (this site runs {status.version}). {link}</span>
|
|
</div>
|
|
);
|
|
}
|
|
return null;
|
|
}
|