diff --git a/scripts/sql/007_add_manifold_url.sql b/scripts/sql/007_add_manifold_url.sql new file mode 100644 index 0000000..0b10fee --- /dev/null +++ b/scripts/sql/007_add_manifold_url.sql @@ -0,0 +1,4 @@ +-- Add manifold_url column to research table for prediction market links +ALTER TABLE research ADD COLUMN IF NOT EXISTS manifold_url TEXT; + +COMMENT ON COLUMN research.manifold_url IS 'Manifold Markets prediction market URL for this bill'; diff --git a/src/components/ManifoldBadge.jsx b/src/components/ManifoldBadge.jsx new file mode 100644 index 0000000..6be2b65 --- /dev/null +++ b/src/components/ManifoldBadge.jsx @@ -0,0 +1,79 @@ +import { useState, useEffect } from "react"; +import { colors, typography, spacing } from "../designTokens"; + +/** + * Displays a live prediction market probability badge from Manifold Markets. + * Fetches the current probability on mount and links to the market. + */ +export default function ManifoldBadge({ marketUrl }) { + const [prob, setProb] = useState(null); + + useEffect(() => { + if (!marketUrl) return; + + // Extract slug from URL: https://manifold.markets/User/slug -> slug + const slug = marketUrl.split("/").pop(); + if (!slug) return; + + fetch(`https://api.manifold.markets/v0/slug/${slug}`) + .then((r) => r.json()) + .then((data) => { + if (data.probability != null) { + setProb(data.probability); + } + }) + .catch(() => {}); + }, [marketUrl]); + + if (prob == null) return null; + + const pct = Math.round(prob * 100); + + return ( + e.stopPropagation()} + title="Prediction market probability (Manifold Markets)" + style={{ + display: "inline-flex", + alignItems: "center", + gap: spacing.xs, + padding: `2px ${spacing.sm}`, + borderRadius: spacing.radius.md, + backgroundColor: `${colors.primary[600]}12`, + border: `1px solid ${colors.primary[600]}30`, + color: colors.primary[700], + fontSize: typography.fontSize.xs, + fontWeight: typography.fontWeight.semibold, + fontFamily: typography.fontFamily.body, + textDecoration: "none", + flexShrink: 0, + transition: "background-color 0.15s ease", + }} + onMouseEnter={(e) => + (e.currentTarget.style.backgroundColor = `${colors.primary[600]}25`) + } + onMouseLeave={(e) => + (e.currentTarget.style.backgroundColor = `${colors.primary[600]}12`) + } + > + + + + {pct}% + + ); +} diff --git a/src/components/StatePanel.jsx b/src/components/StatePanel.jsx index 35ae81e..da0878c 100644 --- a/src/components/StatePanel.jsx +++ b/src/components/StatePanel.jsx @@ -3,6 +3,7 @@ import { stateData } from "../data/states"; import { useData } from "../context/DataContext"; import ResearchCard from "./ResearchCard"; import ReformAnalyzer from "./reform/ReformAnalyzer"; +import ManifoldBadge from "./ManifoldBadge"; import { colors, typography, spacing } from "../designTokens"; import { track } from "../lib/analytics"; import { BASE_PATH } from "../lib/basePath"; @@ -279,13 +280,18 @@ const StatePanel = memo(({ stateAbbr, onClose, initialBillId }) => {
-

{bill.bill}

+
+

{bill.bill}

+ {bill.manifoldUrl && ( + + )} +