address greptile review feedback (greploop iteration 3)

- Fix P1: show warning banner when license API call fails instead of
  silently swallowing errors
- Fix P2: add periodic refresh (6h interval) so long-lived sessions
  detect license expiry without requiring page reload
- Fix P2: correct test comments to reflect UTC time (Z suffix)
- Add test for API failure error state

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
yuneng-jiang 2026-03-24 09:13:50 -07:00
parent aad2d59c14
commit ec93880450
2 changed files with 46 additions and 7 deletions

View file

@ -95,7 +95,7 @@ describe("LicenseExpiryBanner", () => {
});
it("should show a warning banner when license expires within 14 days", async () => {
// Now is 2026-03-15T12:00:00Z, exp is 2026-03-22T23:59:59 => ~7.5 days => ceil = 8
// Now is 2026-03-15T12:00:00Z, exp is 2026-03-22T23:59:59Z (UTC) => ~7.5 days => ceil = 8
mockGetLicenseInfo.mockResolvedValue(makeLicense("2026-03-22"));
render(<LicenseExpiryBanner />);
@ -106,7 +106,7 @@ describe("LicenseExpiryBanner", () => {
});
it("should show singular 'day' when 1 day remains", async () => {
// Now is 2026-03-15T12:00:00Z, exp is 2026-03-15T23:59:59 => ~0.5 days => ceil = 1
// Now is 2026-03-15T12:00:00Z, exp is 2026-03-15T23:59:59Z (UTC) => ~0.5 days => ceil = 1
mockGetLicenseInfo.mockResolvedValue(makeLicense("2026-03-15"));
render(<LicenseExpiryBanner />);
@ -173,6 +173,16 @@ describe("LicenseExpiryBanner", () => {
});
});
it("should show a warning when getLicenseInfo API call fails", async () => {
mockGetLicenseInfo.mockRejectedValue(new Error("Network error"));
render(<LicenseExpiryBanner />);
expect(
await screen.findByText("Unable to verify enterprise license")
).toBeInTheDocument();
});
it("should render nothing when expiration_date is unparseable", async () => {
mockGetLicenseInfo.mockResolvedValue(makeLicense("N/A"));

View file

@ -6,26 +6,55 @@ import useAuthorized from "@/app/(dashboard)/hooks/useAuthorized";
import { getLicenseInfo, LicenseInfo } from "./networking";
const EXPIRY_WARNING_DAYS = 14;
/** Re-fetch license info every 6 hours so long-lived sessions pick up expiry. */
const REFRESH_INTERVAL_MS = 6 * 60 * 60 * 1000;
export const LicenseExpiryBanner: React.FC = () => {
const { accessToken } = useAuthorized();
const [licenseInfo, setLicenseInfo] = useState<LicenseInfo | null>(null);
const [fetchError, setFetchError] = useState(false);
useEffect(() => {
if (!accessToken) return;
let cancelled = false;
getLicenseInfo(accessToken)
.then((info) => {
if (!cancelled) setLicenseInfo(info);
})
.catch(() => null);
const fetchLicense = () => {
getLicenseInfo(accessToken)
.then((info) => {
if (!cancelled) {
setLicenseInfo(info);
setFetchError(false);
}
})
.catch(() => {
if (!cancelled) setFetchError(true);
});
};
fetchLicense();
const intervalId = setInterval(fetchLicense, REFRESH_INTERVAL_MS);
return () => {
cancelled = true;
clearInterval(intervalId);
};
}, [accessToken]);
if (fetchError) {
return (
<Alert
message="Unable to verify enterprise license"
description="Could not reach the license server. If your license is near expiry, you may not see a warning. Please check your connection or contact support."
type="warning"
showIcon
banner
closable={false}
style={{ marginBottom: 0, borderRadius: 0 }}
/>
);
}
if (!licenseInfo?.has_license || !licenseInfo.expiration_date) {
return null;
}