diff --git a/src/PolicyEngine.jsx b/src/PolicyEngine.jsx
index 8ed2c72f2..c22fa7e29 100644
--- a/src/PolicyEngine.jsx
+++ b/src/PolicyEngine.jsx
@@ -44,6 +44,8 @@ import CountryIdLayout from "./routing/CountryIdLayout";
import RedirectBlogPost from "./routing/RedirectBlogPost";
import { StatusPage } from "./pages/StatusPage";
import ManifestosComparison from "./applets/ManifestosComparison";
+import DeveloperLayout from "./pages/DeveloperLayout";
+import DeveloperHome from "./pages/DeveloperHome";
import CTCComparison from "./applets/CTCComparison";
import { wrappedResponseJson } from "./data/wrappedJson";
@@ -299,6 +301,11 @@ export default function PolicyEngine() {
} />
} />
} />
+ }>
+ } />
+ } />
+ } />
+
}>
} />
} />
@@ -350,7 +357,6 @@ export default function PolicyEngine() {
} />
} />
- } />
}
diff --git a/src/data/developerToolsList.js b/src/data/developerToolsList.js
new file mode 100644
index 000000000..9d1ff1121
--- /dev/null
+++ b/src/data/developerToolsList.js
@@ -0,0 +1,18 @@
+// A JSON data structure that describes PolicyEngine Developer tools,
+// for use on the Developer Hub page
+import apiImage from "../images/devTools/apistatus.png";
+import simImage from "../images/devTools/simulation.png";
+export const devTools = [
+ {
+ title: "API Status",
+ desc: "Monitor the health and performance of the PolicyEngine API. View real-time status updates, scheduled maintenance notifications, and historical incident data to stay informed about the API's reliability and availability.",
+ path: "api_status",
+ image: apiImage,
+ },
+ {
+ title: "Policy Simulations",
+ desc: "View simulations that are currently running or have run in the past for the current version of the API. The table below updates every 15 seconds, providing you with real-time insights into your policy testing.",
+ path: "simulations",
+ image: simImage,
+ },
+];
diff --git a/src/images/devTools/apistatus.png b/src/images/devTools/apistatus.png
new file mode 100644
index 000000000..12cd8527c
Binary files /dev/null and b/src/images/devTools/apistatus.png differ
diff --git a/src/images/devTools/simulation.png b/src/images/devTools/simulation.png
new file mode 100644
index 000000000..360ca6d96
Binary files /dev/null and b/src/images/devTools/simulation.png differ
diff --git a/src/layout/Footer.jsx b/src/layout/Footer.jsx
index 22b5ddc6a..6821cf93e 100644
--- a/src/layout/Footer.jsx
+++ b/src/layout/Footer.jsx
@@ -62,6 +62,11 @@ function LinkSection() {
label: "Terms and Conditions",
isInternal: true,
},
+ {
+ link: `/${countryId}/developer-tools`,
+ label: "Developer Tools",
+ isInternal: true,
+ },
];
const links = linkData.map((link, index) => {
diff --git a/src/pages/DeveloperHome.jsx b/src/pages/DeveloperHome.jsx
new file mode 100644
index 000000000..4dae1aabb
--- /dev/null
+++ b/src/pages/DeveloperHome.jsx
@@ -0,0 +1,192 @@
+import React, { useEffect } from "react";
+import Section from "../layout/Section";
+import { devTools } from "../data/developerToolsList";
+import style from "../style/index.js";
+import LinkButton from "../controls/LinkButton.jsx";
+import useDisplayCategory from "../hooks/useDisplayCategory.js";
+import { useLocation } from "react-router-dom";
+
+const DeveloperHome = () => {
+ const displayCategory = useDisplayCategory();
+ const { pathname } = useLocation();
+
+ useEffect(() => {
+ window.scrollTo(0, 0);
+ }, [pathname]);
+ return (
+ <>
+
+
+ {devTools.map((tool, index) => (
+
+ ))}
+
+
+ >
+ );
+};
+
+export default DeveloperHome;
+
+function ToolsCard({ tool }) {
+ const displayCategory = useDisplayCategory();
+ const mobile = displayCategory === "mobile";
+ const tablet = displayCategory === "tablet";
+
+ return (
+
+ {" "}
+ {/* Set width to 100% */}
+
+
+ }
+ bottomRight={
+
+
+
+ }
+ style={{
+ backgroundColor: style.colors.LIGHT_GRAY,
+ // maxWidth: "1000px", // Set a max width for ToolBox
+ height: "100%",
+ position: "relative",
+ }}
+ >
+
+
+ {tool.title}
+
+ {displayCategory !== "mobile" && (
+
+ {tool.desc}
+
+ )}
+
+
+ );
+}
+
+function ToolsLayout({
+ children,
+ top,
+ tablet,
+
+ bottomRight,
+ noBorder,
+ style,
+ mobile,
+}) {
+ return (
+
+
+
+ {top}
+
+
+ {children}
+
+ {/*
{bottomLeft}
*/}
+
{bottomRight}
+
+
+
+
+ );
+}
diff --git a/src/pages/DeveloperLayout.jsx b/src/pages/DeveloperLayout.jsx
new file mode 100644
index 000000000..b8dc45435
--- /dev/null
+++ b/src/pages/DeveloperLayout.jsx
@@ -0,0 +1,43 @@
+import Header from "../layout/Header.jsx";
+import Footer from "../layout/Footer.jsx";
+import style from "../style/index.js";
+import PageHeader from "../layout/PageHeader.jsx";
+import { Outlet, useLocation } from "react-router-dom";
+import { Helmet } from "react-helmet";
+
+export default function DeveloperLayout() {
+ const { pathname } = useLocation();
+
+ if (pathname.length > 20) {
+ return (
+
+
+
+ );
+ }
+
+ return (
+
+
+ Developer Tools | PolicyEngine
+
+
+
+
+
+
+ Welcome to the Developer Tools page for PolicyEngine. This hub is
+ designed to enhance your experience with our open-source projects by
+ providing quick access to essential resources.
+
+
+
+
+
+
+
+ );
+}
diff --git a/src/pages/Simulations.jsx b/src/pages/Simulations.jsx
index a1e022865..b1e274ea6 100644
--- a/src/pages/Simulations.jsx
+++ b/src/pages/Simulations.jsx
@@ -1,4 +1,3 @@
-import Header from "../layout/Header";
import Footer from "../layout/Footer";
import { apiCall } from "../api/call";
import { Table } from "antd";
@@ -13,6 +12,7 @@ import CodeBlock from "../layout/CodeBlock";
import PageHeader from "../layout/PageHeader";
import style from "../style";
import { Link } from "react-router-dom";
+import Header from "../layout/Header";
export default function SimulationsPage() {
// call /simulations endpoint, which returns
diff --git a/src/pages/StatusPage.jsx b/src/pages/StatusPage.jsx
index 9073c8d34..3f27b87ae 100644
--- a/src/pages/StatusPage.jsx
+++ b/src/pages/StatusPage.jsx
@@ -1,7 +1,6 @@
import { useEffect, useState } from "react";
import useMobile from "../layout/Responsive";
-import Header from "../layout/Header";
import Footer from "../layout/Footer";
import { countryApiCall, apiCall } from "../api/call";
import {
@@ -13,6 +12,7 @@ import {
COUNTRY_NAMES,
} from "../data/countries";
import { Helmet } from "react-helmet";
+import Header from "../layout/Header";
import { wrappedResponseJson } from "../data/wrappedJson";
function ApiStatus({ apiStatus, apiCategory, countryNames }) {