From a45f032fe40a868987ceebbe1fb0b6eff9e138c2 Mon Sep 17 00:00:00 2001 From: Anthony Volk Date: Tue, 10 Mar 2026 20:07:14 +0100 Subject: [PATCH] Display policyengine-us and policyengine-uk versions on overview page Fetch latest package versions from PyPI JSON API and show them in a "Current versions" section at the bottom of the overview page. Co-Authored-By: Claude Opus 4.6 --- src/components/microsim/PackageVersions.tsx | 81 +++++++++++++++++++++ src/data/fetchPackageVersion.ts | 20 +++++ src/pages/OverviewPage.tsx | 2 + 3 files changed, 103 insertions(+) create mode 100644 src/components/microsim/PackageVersions.tsx create mode 100644 src/data/fetchPackageVersion.ts diff --git a/src/components/microsim/PackageVersions.tsx b/src/components/microsim/PackageVersions.tsx new file mode 100644 index 0000000..0172f3f --- /dev/null +++ b/src/components/microsim/PackageVersions.tsx @@ -0,0 +1,81 @@ +import { useState, useEffect } from 'react'; +import { motion } from 'framer-motion'; +import { colors } from '../../designTokens'; +import { fetchPackageVersion } from '../../data/fetchPackageVersion'; + +const PACKAGES = [ + { id: 'us', pypiName: 'policyengine-us', label: 'policyengine-us' }, + { id: 'uk', pypiName: 'policyengine-uk', label: 'policyengine-uk' }, +] as const; + +export default function PackageVersions() { + const [versions, setVersions] = useState>({}); + const [loading, setLoading] = useState(true); + + useEffect(() => { + let cancelled = false; + Promise.all( + PACKAGES.map(async (pkg) => { + const version = await fetchPackageVersion(pkg.pypiName); + return [pkg.id, version] as const; + }), + ).then((results) => { + if (cancelled) return; + setVersions(Object.fromEntries(results)); + setLoading(false); + }); + return () => { + cancelled = true; + }; + }, []); + + return ( +
+

+ Current versions +

+

+ PolicyEngine's country models are open-source Python packages, published + to PyPI with every release. +

+
+ {PACKAGES.map((pkg, i) => ( + { + e.currentTarget.style.borderColor = colors.primary[500]; + e.currentTarget.style.backgroundColor = colors.primary[50]; + }} + onMouseLeave={(e) => { + e.currentTarget.style.borderColor = colors.border.light; + e.currentTarget.style.backgroundColor = colors.white; + }} + > + + {pkg.label} + + + {loading ? '...' : versions[pkg.id] ? `v${versions[pkg.id]}` : '—'} + + + ))} +
+
+ ); +} diff --git a/src/data/fetchPackageVersion.ts b/src/data/fetchPackageVersion.ts new file mode 100644 index 0000000..eff1295 --- /dev/null +++ b/src/data/fetchPackageVersion.ts @@ -0,0 +1,20 @@ +const cache = new Map(); + +export async function fetchPackageVersion( + packageName: string, +): Promise { + if (cache.has(packageName)) return cache.get(packageName)!; + + try { + const res = await fetch(`https://pypi.org/pypi/${packageName}/json`); + if (!res.ok) throw new Error(`PyPI returned ${res.status}`); + const data = await res.json(); + const version: string = data.info?.version; + if (!version) throw new Error('No version in PyPI response'); + cache.set(packageName, version); + return version; + } catch (err) { + console.warn(`Failed to fetch version for ${packageName}:`, err); + return null; + } +} diff --git a/src/pages/OverviewPage.tsx b/src/pages/OverviewPage.tsx index 1423529..c27975c 100644 --- a/src/pages/OverviewPage.tsx +++ b/src/pages/OverviewPage.tsx @@ -1,11 +1,13 @@ import Walkthrough from '../components/microsim/Walkthrough'; import PageHeader from '../components/layout/PageHeader'; +import PackageVersions from '../components/microsim/PackageVersions'; export default function OverviewPage({ country }: { country: string }) { return (
+
); }