diff --git a/src/images/headshots/ben-ogorek.jpeg b/src/images/headshots/ben-ogorek.jpeg new file mode 100644 index 000000000..828c31c47 Binary files /dev/null and b/src/images/headshots/ben-ogorek.jpeg differ diff --git a/src/images/headshots/maria-juaristi.jpeg b/src/images/headshots/maria-juaristi.jpeg new file mode 100644 index 000000000..4131c9e93 Binary files /dev/null and b/src/images/headshots/maria-juaristi.jpeg differ diff --git a/src/images/posts/obbba-household-by-household.png b/src/images/posts/obbba-household-by-household.png new file mode 100644 index 000000000..59f832da8 Binary files /dev/null and b/src/images/posts/obbba-household-by-household.png differ diff --git a/src/pages/BlogPage.jsx b/src/pages/BlogPage.jsx index b2c067c40..f8da37a7d 100644 --- a/src/pages/BlogPage.jsx +++ b/src/pages/BlogPage.jsx @@ -49,25 +49,29 @@ export default function BlogPage() { // /uk/research/blog-slug-here const { postName } = useParams(); const countryId = useCountryId(); + const [content, setContent] = useState(""); const post = posts.find((post) => post.slug === postName); - const postDate = formatFullDate(moment(post.date), countryId); - const imageUrl = post.image ? handleImageLoad(post.image) : ""; - const file = require(`../posts/articles/${post.filename}`); + const isNotebook = + post && post.filename ? post.filename.endsWith(".ipynb") : false; + const file = + post && post.filename + ? require(`../posts/articles/${post.filename}`) + : null; - const [content, setContent] = useState(""); - const isNotebook = post.filename.endsWith(".ipynb"); useEffect(() => { - fetch(file) - .then((response) => response.text()) - .then((text) => { - if (isNotebook) { - setContent(JSON.parse(text)); - } else { - setContent(text); - } - }); + if (file) { + fetch(file) + .then((response) => response.text()) + .then((text) => { + if (isNotebook) { + setContent(JSON.parse(text)); + } else { + setContent(text); + } + }); + } }, [file, isNotebook]); // Some old links might point to a dated URL format @@ -76,6 +80,19 @@ export default function BlogPage() { return ; } + // Handle external URL redirects + if (post && post.external_url) { + return ; + } + + // Handle missing post or filename + if (!post || !post.filename) { + return ; + } + + const postDate = formatFullDate(moment(post.date), countryId); + const imageUrl = post.image ? handleImageLoad(post.image) : ""; + let markdown; if (isNotebook && content) { diff --git a/src/pages/home/HomeBlogPreview.jsx b/src/pages/home/HomeBlogPreview.jsx index 7d1bb2702..7e7e6eef8 100644 --- a/src/pages/home/HomeBlogPreview.jsx +++ b/src/pages/home/HomeBlogPreview.jsx @@ -403,8 +403,8 @@ export function FeaturedBlogPreview({ blogs, width, imageHeight }) { export function MediumBlogPreview({ blog, minHeight }) { const displayCategory = useDisplayCategory(); const countryId = useCountryId(); - const slug = blog.filename.split(".")[0]; - const link = `/${countryId}/research/${slug}`; + const slug = blog.filename ? blog.filename.split(".")[0] : blog.slug; + const link = blog.external_url || `/${countryId}/research/${slug}`; const imageUrl = blog.image ? handleImageLoad(blog.image) : ""; diff --git a/src/posts/authors.json b/src/posts/authors.json index dcc5c3bd0..ce4b118df 100644 --- a/src/posts/authors.json +++ b/src/posts/authors.json @@ -150,5 +150,23 @@ "github": "https://github.com/eccuraa", "headshot": "elena-cura.png", "title": "Data Science Intern at PolicyEngine" + }, + "ben-ogorek": { + "name": "Ben Ogorek", + "email": "ben.ogorek@policyengine.org", + "bio": "Ben is a data scientist at PolicyEngine.", + "linkedin": "https://www.linkedin.com/in/benogorek/", + "github": "https://github.com/baogorek", + "headshot": "ben-ogorek.jpeg", + "title": "Data Scientist at PolicyEngine" + }, + "maria-juaristi": { + "name": "Maria Juaristi", + "email": "maria@policyengine.org", + "bio": "Maria is a data scientist at PolicyEngine.", + "linkedin": "https://www.linkedin.com/in/mariajuaristi/", + "github": "https://github.com/juaristi22", + "headshot": "maria-juaristi.jpeg", + "title": "Data Scientist at PolicyEngine" } } diff --git a/src/posts/postTransformers.js b/src/posts/postTransformers.js index 5b48e3338..052d48b54 100644 --- a/src/posts/postTransformers.js +++ b/src/posts/postTransformers.js @@ -3,7 +3,15 @@ import posts from "./posts.json"; const postsSorted = posts.sort((a, b) => (a.date < b.date ? 1 : -1)); for (let post of postsSorted) { - post.slug = post.filename.substring(0, post.filename.indexOf(".")); + if (post.filename) { + post.slug = post.filename.substring(0, post.filename.indexOf(".")); + } else if (post.external_url) { + // For external URLs, generate a slug from the title + post.slug = post.title + .toLowerCase() + .replace(/\s+/g, "-") + .replace(/[^a-z0-9-]/g, ""); + } } const tags = postsSorted.map((post) => post.tags); diff --git a/src/posts/posts.json b/src/posts/posts.json index 021149df8..0a32b186c 100644 --- a/src/posts/posts.json +++ b/src/posts/posts.json @@ -2,11 +2,25 @@ { "title": "Enhanced CPS full launch: Comprehensive microdata for policy analysis", "description": "The Enhanced Current Population Survey now includes tip, overtime, and auto loan interest imputations, plus upcoming state and congressional district calibration.", - "date": "2025-08-08", + "date": "2025-08-11", "tags": ["us", "data", "featured"], "filename": "enhanced-cps-launch.md", "image": "enhanced-cps-launch.png", - "authors": ["max-ghenis", "nikhil-woodruff"] + "authors": ["max-ghenis", "nikhil-woodruff", "ben-ogorek", "maria-juaristi"] + }, + { + "title": "The One Big Beautiful Bill Act, household by household", + "description": "Our latest interactive shows how the reconciliation bill affects each of 20,000 representative households across income levels, states, and provisions.", + "date": "2025-08-11", + "tags": ["us", "policy", "featured", "reconciliation", "interactives"], + "external_url": "/us/obbba-household-by-household", + "image": "obbba-household-by-household.png", + "authors": [ + "max-ghenis", + "pavel-makarchuk", + "ben-ogorek", + "nikhil-woodruff" + ] }, { "title": "Analysis of individual income tax provisions in the final reconciliation bill",