diff --git a/spotlight-client/src/App.test.tsx b/spotlight-client/src/App.test.tsx
index b8c38282..74390e84 100644
--- a/spotlight-client/src/App.test.tsx
+++ b/spotlight-client/src/App.test.tsx
@@ -128,16 +128,14 @@ describe("navigation", () => {
const homeLink = inNav.getByRole("link", { name: "Spotlight" });
const tenantLink = inNav.getByRole("link", { name: "North Dakota" });
const sentencingLink = await screen.findByRole("link", {
- name: "Racial Disparities Data",
+ name: "Explore Prison Data",
});
fireEvent.click(sentencingLink);
// NOTE: *ByRole queries can be too expensive to run async with this much DOM,
// so we are using *ByTestId queries here instead
await waitFor(async () =>
- expect(await screen.findByTestId("PageTitle")).toHaveTextContent(
- "Racial Disparities"
- )
+ expect(await screen.findByTestId("PageTitle")).toHaveTextContent("Prison")
);
fireEvent.click(tenantLink);
diff --git a/spotlight-client/src/OtherNarrativeLinksPreview/OtherNarrativeLinksPreview.tsx b/spotlight-client/src/OtherNarrativeLinksPreview/OtherNarrativeLinksPreview.tsx
index bedf958d..b52bbd62 100644
--- a/spotlight-client/src/OtherNarrativeLinksPreview/OtherNarrativeLinksPreview.tsx
+++ b/spotlight-client/src/OtherNarrativeLinksPreview/OtherNarrativeLinksPreview.tsx
@@ -15,25 +15,27 @@
// along with this program. If not, see .
// =============================================================================
-import { Link } from "@reach/router";
+import {
+ Tabs,
+ TabList as BasicTabList,
+ Tab,
+ TabPanel as BasicTabPanel,
+} from "@recidiviz/design-system";
import { ascending } from "d3-array";
import { observer } from "mobx-react-lite";
import { rem } from "polished";
import React from "react";
-import { animated, useSpring } from "react-spring/web.cjs";
import styled from "styled-components/macro";
-import { track } from "../analytics";
-import { NarrativeTypeId, TenantId } from "../contentApi/types";
+import { DEFAULT_SELECTED_TAB, DEFAULT_CAROUSEL_INTERVAL } from "../constants";
+import { NarrativeTypeId } from "../contentApi/types";
import RacialDisparitiesNarrative from "../contentModels/RacialDisparitiesNarrative";
import SystemNarrative from "../contentModels/SystemNarrative";
import { Narrative } from "../contentModels/types";
import MetricVizMapper from "../MetricVizMapper";
import ModelHydrator from "../ModelHydrator";
import BarChartPair from "../RacialDisparitiesNarrativePage/BarChartPair";
-import getUrlForResource from "../routerUtils/getUrlForResource";
import { useDataStore } from "../StoreProvider";
-import { breakpoints, colors } from "../UiLibrary";
-import Arrow from "../UiLibrary/Arrow";
+import { breakpoints, colors, fluidFontSizeStyles } from "../UiLibrary";
// grid styles adapted from IE-safe auto placement grid
// https://css-tricks.com/css-grid-in-ie-faking-an-auto-placement-grid-with-gaps/
@@ -45,69 +47,63 @@ const Wrapper = styled.div`
}
`;
-const LinkList = styled.ul`
- display: flex;
- flex-wrap: wrap;
- font-size: ${rem(24)};
- line-height: 1.5;
- /* this margin makes the cells flush left and right */
- margin: ${rem(48)} -${rem(32)} 0 0;
+const ChartTitle = styled.div`
+ margin-top: ${rem(16)};
+ font-size: ${rem(16)};
`;
-const LinkListItem = styled.li`
- /* creates gaps */
- border: 0 solid transparent;
- border-width: 0 ${rem(32)} 0 0;
- flex: 0 0 auto;
- white-space: nowrap;
- /* use width to create 1-4 columns, depending on screen size */
- width: 100%;
-
- @media (min-width: ${breakpoints.tablet[0]}px) {
- width: calc(100% / 1);
- }
-
- @media (min-width: ${breakpoints.desktop[0]}px) {
- width: calc(100% / 1);
- }
-
- @media (min-width: ${breakpoints.xl[0]}px) {
- width: calc(100% / 2);
- }
+const ChartPreview = styled.div`
+ margin-top: ${rem(16)};
+ height: ${rem(440)};
+ animation: fadeIn 0.5s ease;
- a {
- border-top: 1px solid ${colors.rule};
- color: ${colors.text};
- display: block;
- padding-right: ${rem(8)};
- padding-top: ${rem(24)};
- padding-bottom: ${rem(24)};
- text-decoration: none;
- width: 100%;
+ @media screen and (max-width: ${breakpoints.tablet[0]}px) {
+ height: ${rem(330)};
+ overflow: hidden;
}
- a:not(:last-child) {
- border-bottom: 1px solid ${colors.rule};
+ @keyframes fadeIn {
+ from {
+ opacity: 0;
+ }
+ to {
+ opacity: 1;
+ }
}
`;
-const LinkText = styled.span`
- white-space: normal;
+const TabList = styled(BasicTabList)`
+ display: flex;
+ align-items: center;
+ flex-wrap: nowrap;
+ padding: 0;
+ overflow-x: auto;
`;
-const ChartTitle = styled.span`
- font-size: ${rem(16)};
+const TabItem = styled(Tab)<{ minSize: number; maxSize: number }>`
+ padding: ${rem(25)} 0;
+ color: ${colors.caption};
+ border-bottom: 1px solid transparent;
+ font-family: "Libre Baskerville";
+ font-style: normal;
+ font-weight: 400;
+
+ &:first-child {
+ margin-left: 0;
+ }
+
+ ${(props) => fluidFontSizeStyles(props.minSize, props.maxSize)}
`;
-const ChartPreview = styled.div`
- padding-top: ${rem(16)};
+const TabPanel = styled(BasicTabPanel)`
+ padding: 0;
`;
const PREVIEW_ORDER: NarrativeTypeId[] = [
"Prison",
"RacialDisparities",
- "Parole",
"Probation",
+ "Parole",
"Sentencing",
];
@@ -117,7 +113,6 @@ const ChartPreviewComponent: React.FC<{
if (narrative instanceof SystemNarrative && narrative.preview)
return (
<>
- {narrative.previewTitle}
+ {narrative.previewTitle}
>
);
if (narrative instanceof RacialDisparitiesNarrative) {
@@ -141,50 +137,74 @@ const RacialDisparitiesPreview = observer(
return (
<>
- Population by Race/Ethnicity
{narrative.populationDataSeries && (
-
+
+
+
)}
+ Population by Race/Ethnicity
>
);
}
);
-const NarrativeLink: React.FC<{
- narrative: Narrative;
- tenantId: TenantId;
-}> = observer(({ narrative, tenantId }) => {
- const [animationStyles, setAnimationStyles] = useSpring(() => ({
- opacity: 0,
- from: { opacity: 0 },
- }));
+const NarrativeTabs: React.FC<{
+ narratives: Narrative[];
+ onTabChange: (selectedTab: NarrativeTypeId) => void;
+}> = observer(({ narratives, onTabChange }) => {
+ const tabs = narratives.map((narrative) => narrative.id);
+ const defaultTab = tabs.includes(DEFAULT_SELECTED_TAB)
+ ? DEFAULT_SELECTED_TAB
+ : tabs[0];
+
+ const [selectedTab, selectTab] = React.useState(defaultTab);
+ const [tabIndex, setTabIndex] = React.useState(tabs.indexOf(defaultTab));
+ const [isHovered, setHovered] = React.useState(false);
+
+ React.useEffect(() => {
+ const nextIndex = (tabIndex + 1) % tabs.length;
+
+ const timer = setTimeout(
+ () => {
+ setTabIndex(nextIndex);
+ selectTab(tabs[nextIndex]);
+ },
+ isHovered ? DEFAULT_CAROUSEL_INTERVAL * 1000 : DEFAULT_CAROUSEL_INTERVAL
+ );
+
+ onTabChange(selectedTab);
+
+ return () => clearTimeout(timer);
+ }, [tabIndex, selectedTab, onTabChange, tabs, isHovered]);
return (
-
-
- track("narrative_body_link_clicked", {
- category: "navigation",
- label: narrative.id,
- })
- }
- onMouseOver={() => setAnimationStyles({ opacity: 1 })}
- onFocus={() => setAnimationStyles({ opacity: 1 })}
- onMouseOut={() => setAnimationStyles({ opacity: 0 })}
- onBlur={() => setAnimationStyles({ opacity: 0 })}
- >
- {narrative.title} Data
-
-
-
-
-
-
+ setTabIndex(index)}
+ onMouseOver={() => setHovered(true)}
+ onFocus={() => setHovered(true)}
+ onMouseOut={() => setHovered(false)}
+ onBlur={() => setHovered(false)}
+ >
+
+ {narratives.map((narrative) => (
+ selectTab(narrative.id)}
+ >
+ {narrative.title}
+
+ ))}
+
+ {narratives.map((narrative) => (
+
+
+
+ ))}
+
);
});
@@ -192,7 +212,9 @@ const NarrativeLink: React.FC<{
* Produces a grid of links to available narratives for the current tenant.
* If there is a current narrative selected, it will be excluded from the grid.
*/
-const OtherNarrativeLinksPreview = (): React.ReactElement | null => {
+const OtherNarrativeLinksPreview: React.FC<{
+ onTabChange: (selectedTab: NarrativeTypeId) => void;
+}> = ({ onTabChange }): React.ReactElement | null => {
const { tenant } = useDataStore();
if (!tenant) return null;
@@ -209,17 +231,10 @@ const OtherNarrativeLinksPreview = (): React.ReactElement | null => {
return (
-
- {narrativesToDisplay.map((narrative) => {
- return (
-
- );
- })}
-
+
);
};
diff --git a/spotlight-client/src/PageTenant/PageTenant.tsx b/spotlight-client/src/PageTenant/PageTenant.tsx
index 5c05772f..47fbadf1 100644
--- a/spotlight-client/src/PageTenant/PageTenant.tsx
+++ b/spotlight-client/src/PageTenant/PageTenant.tsx
@@ -21,21 +21,51 @@ import { observer } from "mobx-react-lite";
import { rem } from "polished";
import React from "react";
import styled from "styled-components/macro";
-import { NAV_BAR_HEIGHT } from "../constants";
+import { DEFAULT_SELECTED_TAB, NAV_BAR_HEIGHT } from "../constants";
+import { NarrativeTypeId } from "../contentApi/types";
import OtherNarrativeLinksPreview from "../OtherNarrativeLinksPreview";
import { useDataStore } from "../StoreProvider";
-import { breakpoints, PageSection, PageTitle } from "../UiLibrary";
+import { breakpoints, PageSection, PageTitle, typefaces } from "../UiLibrary";
+import ExploreNarrativeButton from "../UiLibrary/ExploreNarrativeButton";
import withRouteSync from "../withRouteSync";
+const Page = styled.article`
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+
+ a {
+ text-decoration: none;
+ }
+
+ @media screen and (min-width: ${breakpoints.xl[0]}px) {
+ flex-direction: row;
+ min-height: calc(100vh - ${rem(NAV_BAR_HEIGHT)});
+ padding: 0 ${rem(120)};
+ gap: ${rem(70)};
+ }
+`;
+
const Introduction = styled(PageSection)`
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
padding-bottom: ${rem(48)};
padding-top: ${rem(48)};
+ @media screen and (min-width: ${breakpoints.xl[0]}px) {
+ padding-left: 0;
+ padding-right: 0;
+ max-width: 40%;
+ }
+
@media screen and (min-width: ${breakpoints.tablet[0]}px) {
- display: flex;
- flex-direction: column;
- justify-content: center;
- align-items: center;
+ a {
+ margin-right: auto;
+ }
+
/* try to keep the links "above the fold" */
min-height: calc(100vh - ${rem(NAV_BAR_HEIGHT)} - ${rem(130)});
@@ -46,28 +76,63 @@ const Introduction = styled(PageSection)`
}
`;
-const Links = styled(PageSection)``;
+const Links = styled(PageSection)`
+ width: 100%;
+
+ @media screen and (min-width: ${breakpoints.xl[0]}px) {
+ width: 60%;
+ padding: 0;
+ }
+`;
const Title = styled(PageTitle)`
- font-size: ${rem(40)};
- max-width: ${rem(1100)};
+ font-size: ${rem(34)};
+
+ @media screen and (max-width: ${breakpoints.tablet[0]}px) {
+ font-size: ${rem(28)};
+ }
+`;
+
+const Subtitle = styled.h2`
+ font-family: ${typefaces.body};
+ letter-spacing: -0.04em;
+ font-size: ${rem(21)};
+ margin-top: ${rem(24)};
+
+ @media screen and (max-width: ${breakpoints.tablet[0]}px) {
+ font-size: ${rem(18)};
+ }
`;
const PageTenant: React.FC = () => {
const { tenant } = useDataStore();
+ const [narrativeId, setNarrativeId] = React.useState(
+ DEFAULT_SELECTED_TAB
+ );
if (!tenant) return null;
return (
// tenant may be briefly undefined during initial page load
-
+
- {HTMLReactParser(tenant.description)}
+
+ {HTMLReactParser(tenant.description)}
+ {tenant.ctaCopy && (
+ {HTMLReactParser(tenant.ctaCopy)}
+ )}
+
+
-
+ setNarrativeId(selectedTab)}
+ />
-
+
);
};
diff --git a/spotlight-client/src/RacialDisparitiesNarrativePage/BarChartPair.tsx b/spotlight-client/src/RacialDisparitiesNarrativePage/BarChartPair.tsx
index fa49cc84..e14da5b9 100644
--- a/spotlight-client/src/RacialDisparitiesNarrativePage/BarChartPair.tsx
+++ b/spotlight-client/src/RacialDisparitiesNarrativePage/BarChartPair.tsx
@@ -25,10 +25,10 @@ import VizControls, { VizControlsProps } from "../VizControls";
import VizNotes from "../VizNotes";
const CHART_HEIGHT = 165;
-
const CHART_HEIGHT_MOBILE = 100;
-const CHART_HEIGHT_PREVIEW = 135;
+const CHART_HEIGHT_PREVIEW = 420;
+const CHART_HEIGHT_PREVIEW_MOBILE = 300;
const Wrapper = styled.div`
padding: ${rem(48)} 0;
@@ -57,13 +57,30 @@ export default function BarChartPair({
ItemToHighlight | undefined
>();
- let chartHeight = useBreakpoint(CHART_HEIGHT, [
+ const chartHeight = useBreakpoint(CHART_HEIGHT, [
"mobile-",
CHART_HEIGHT_MOBILE,
]);
+ const previewChartHeight = useBreakpoint(CHART_HEIGHT_PREVIEW, [
+ "mobile-",
+ CHART_HEIGHT_PREVIEW_MOBILE,
+ ]);
+
if (preview) {
- chartHeight = CHART_HEIGHT_PREVIEW;
+ return (
+ <>
+
+ >
+ );
}
return (
@@ -80,7 +97,6 @@ export default function BarChartPair({
title={data[0].label}
height={chartHeight}
highlighted={highlightedCategory}
- showLegend={false}
/>
.
+// =============================================================================
+
+import { Button as BasicButton } from "@recidiviz/design-system";
+import { Link } from "@reach/router";
+import { observer } from "mobx-react-lite";
+import { rem } from "polished";
+import React from "react";
+import startCase from "lodash/startCase";
+import styled from "styled-components/macro";
+import { track } from "../analytics";
+import { NarrativeTypeId, TenantId } from "../contentApi/types";
+import getUrlForResource from "../routerUtils/getUrlForResource";
+import colors from "./colors";
+import Arrow from "./Arrow";
+import { fluidFontSizeStyles } from "./typography";
+
+const Button = styled(BasicButton)<{ minSize: number; maxSize: number }>`
+ background: ${colors.text};
+ padding: ${rem(16)} ${rem(24)};
+ box-shadow: 0px 10px 20px rgba(0, 108, 103, 0.3);
+
+ span {
+ display: flex;
+ flex-wrap: wrap;
+ align-items: center;
+ justify-content: center;
+
+ strong {
+ animation: fadeInUp 0.5s ease;
+ }
+ }
+
+ svg {
+ margin-left: ${rem(16)};
+ }
+
+ @keyframes fadeInUp {
+ from {
+ opacity: 0;
+ transform: translateY(${rem(30)});
+ }
+ to {
+ opacity: 1;
+ transform: translateY(0);
+ }
+ }
+
+ ${(props) => fluidFontSizeStyles(props.minSize, props.maxSize)}
+`;
+
+const ExploreNarrativeButton: React.FC<{
+ narrativeId: NarrativeTypeId;
+ tenantId: TenantId;
+}> = observer(({ narrativeId, tenantId }) => {
+ return (
+
+ track("narrative_body_link_clicked", {
+ category: "navigation",
+ label: narrativeId,
+ })
+ }
+ >
+
+
+ );
+});
+
+export default ExploreNarrativeButton;
diff --git a/spotlight-client/src/UiLibrary/breakpoints.ts b/spotlight-client/src/UiLibrary/breakpoints.ts
index 2b2bad7b..8d5a547f 100644
--- a/spotlight-client/src/UiLibrary/breakpoints.ts
+++ b/spotlight-client/src/UiLibrary/breakpoints.ts
@@ -19,8 +19,8 @@
* these are overrides to defaults in @w11r/use-breakpoint
*/
export default {
- mobile: [320, 767],
- tablet: [768, 1023],
+ mobile: [400, 768],
+ tablet: [769, 1023],
desktop: [1024, 1279],
xl: [1280, 10000],
};
diff --git a/spotlight-client/src/VizDemographicsByCategory/VizDemographicsByCategory.tsx b/spotlight-client/src/VizDemographicsByCategory/VizDemographicsByCategory.tsx
index b3ed2c66..23ee8dd2 100644
--- a/spotlight-client/src/VizDemographicsByCategory/VizDemographicsByCategory.tsx
+++ b/spotlight-client/src/VizDemographicsByCategory/VizDemographicsByCategory.tsx
@@ -82,6 +82,7 @@ const VizDemographicsByCategory: React.FC = ({
) : (
item.dataSeries.map(
diff --git a/spotlight-client/src/charts/BubbleChart.tsx b/spotlight-client/src/charts/BubbleChart.tsx
index d0098f6d..af02099b 100644
--- a/spotlight-client/src/charts/BubbleChart.tsx
+++ b/spotlight-client/src/charts/BubbleChart.tsx
@@ -72,11 +72,13 @@ const LegendWrapper = styled.div`
type BubbleChartProps = {
data: CategoricalChartRecord[];
height: number;
+ showLegend?: boolean;
};
export default function BubbleChart({
data,
height,
+ showLegend = true,
}: BubbleChartProps): React.ReactElement {
const { highlighted, setHighlighted } = useHighlightedItem();
@@ -132,13 +134,15 @@ export default function BubbleChart({
size={[width, height]}
/>
-
-
-
+ {showLegend && (
+
+
+
+ )}
>
)}
diff --git a/spotlight-client/src/charts/ProportionalBar.tsx b/spotlight-client/src/charts/ProportionalBar.tsx
index bec33a17..eb0836b3 100644
--- a/spotlight-client/src/charts/ProportionalBar.tsx
+++ b/spotlight-client/src/charts/ProportionalBar.tsx
@@ -75,6 +75,7 @@ type ProportionalBarProps = {
highlighted?: ItemToHighlight;
setHighlighted?: (item?: ItemToHighlight) => void;
showLegend?: boolean;
+ preview?: boolean;
title: string;
};
@@ -84,6 +85,7 @@ export default function ProportionalBar({
highlighted: externalHighlighted,
setHighlighted: setExternalHighlighted,
showLegend = true,
+ preview = false,
title,
}: ProportionalBarProps): React.ReactElement {
const {
@@ -135,12 +137,15 @@ export default function ProportionalBar({
/>
-
-
- {title}
- {noData && ", No Data"}
-
- {showLegend && (
+ {showLegend && (
+
+ {!preview && (
+
+ {title}
+ {noData && ", No Data"}
+
+ )}
+
- )}
-
+
+ )}
>
)}
diff --git a/spotlight-client/src/charts/WindowedTimeSeries.tsx b/spotlight-client/src/charts/WindowedTimeSeries.tsx
index 58bf2a28..35c643b9 100644
--- a/spotlight-client/src/charts/WindowedTimeSeries.tsx
+++ b/spotlight-client/src/charts/WindowedTimeSeries.tsx
@@ -166,6 +166,11 @@ const WindowedTimeSeries: React.FC<{
matte: true,
};
+ if (!showMinimap) {
+ MARGIN.left = 0;
+ MARGIN.bottom = 0;
+ }
+
return (
{({ measureRef, width }) => {
@@ -247,16 +252,7 @@ const WindowedTimeSeries: React.FC<{
/>
) : (
// @ts-expect-error Semiotic typedefs are wrong, can be true for default matte
-
+
)}
diff --git a/spotlight-client/src/constants.ts b/spotlight-client/src/constants.ts
index 967606d0..bac92ff6 100644
--- a/spotlight-client/src/constants.ts
+++ b/spotlight-client/src/constants.ts
@@ -26,7 +26,7 @@ export const ERROR_MESSAGES = {
export const NAV_BAR_HEIGHT = 80;
-export const FOOTER_HEIGHT = 248;
+export const FOOTER_HEIGHT = 130;
export const REVOCATION_TYPE_LABELS = {
ABSCOND: "Absconsion",
@@ -40,3 +40,7 @@ export const SENTENCE_TYPE_LABELS = {
PROBATION: "Probation",
DUAL_SENTENCE: "Both",
};
+
+export const DEFAULT_SELECTED_TAB = "Prison";
+
+export const DEFAULT_CAROUSEL_INTERVAL = 5000;
diff --git a/spotlight-client/src/contentApi/sources/us_id.ts b/spotlight-client/src/contentApi/sources/us_id.ts
index 7d037d59..ebf9da26 100644
--- a/spotlight-client/src/contentApi/sources/us_id.ts
+++ b/spotlight-client/src/contentApi/sources/us_id.ts
@@ -66,7 +66,9 @@ in a Idaho court may occasionally complete their supervision in a different stat
const content: TenantContent = {
name: "Idaho",
- description: "Placeholder for IDOC Mission",
+ description: "Explore data from Idaho’s corrections system.",
+ ctaCopy: `The Idaho Department of Corrections aims to protect the public, our staff,
+ and those within our custody and supervision through safety, accountability, partnerships and providing opportunities for change.`,
coBrandingCopy:
'Produced in collaboration with the Idaho Department of Correction.',
feedbackUrl:
diff --git a/spotlight-client/src/contentApi/sources/us_nd.ts b/spotlight-client/src/contentApi/sources/us_nd.ts
index 3d58ca40..de3bfb81 100644
--- a/spotlight-client/src/contentApi/sources/us_nd.ts
+++ b/spotlight-client/src/contentApi/sources/us_nd.ts
@@ -83,9 +83,9 @@ in a North Dakota court may occasionally complete their supervision in a differe
const content: TenantContent = {
name: "North Dakota",
description:
- "Our mission is to transform lives, influence change, and strengthen community. Transparency is a critical element of our mission; sharing information builds greater accountability between the DOCR and the communities we serve.",
- coBrandingCopy:
- 'Produced in collaboration with the North Dakota Department of Corrections and Rehabilitation.',
+ "Our mission is to transform lives, influence change, and strengthen community.",
+ ctaCopy: `At the North Dakota Department of Corrections and Rehabilitation, transparency is a critical element of our mission; sharing information builds greater accountability between the DOCR and the communities we serve.`,
+ coBrandingCopy: `Produced in collaboration with the North Dakota Department of Corrections and Rehabilitation.`,
feedbackUrl:
"https://docs.google.com/forms/d/e/1FAIpQLSc3_wV2ltGumMdGTcLehUM41tQri0ZW5RjIKh0JJlhpJGE9Hg/viewform",
demographicCategories: {
diff --git a/spotlight-client/src/contentApi/sources/us_pa.ts b/spotlight-client/src/contentApi/sources/us_pa.ts
index 172e5ca4..c0c52d66 100644
--- a/spotlight-client/src/contentApi/sources/us_pa.ts
+++ b/spotlight-client/src/contentApi/sources/us_pa.ts
@@ -19,12 +19,9 @@ import { TenantContent } from "../types";
const content: TenantContent = {
name: "Pennsylvania",
- description: `
- The Pennsylvania Department of Corrections (DOC)
- is committed to enhancing public safety. The DOC's mission is to reduce criminal
- behavior by providing individualized treatment and education to incarcerated
- individuals, resulting in successful community reintegration through accountability
- and positive change.`,
+ description: `The Pennsylvania Department of Corrections (DOC)
+ is committed to enhancing public safety.`,
+ ctaCopy: `The DOC's mission is to reduce criminal behavior by providing individualized treatment and education to incarcerated individuals, resulting in successful community reintegration through accountability and positive change.`,
coBrandingCopy:
'Produced in collaboration with the Pennsylvania Department of Corrections.',
feedbackUrl: "https://forms.gle/7bZMpgGR69uaW1eNA",
diff --git a/spotlight-client/src/contentApi/sources/us_tn.ts b/spotlight-client/src/contentApi/sources/us_tn.ts
index 02705d11..0210f193 100644
--- a/spotlight-client/src/contentApi/sources/us_tn.ts
+++ b/spotlight-client/src/contentApi/sources/us_tn.ts
@@ -66,7 +66,9 @@ in a Tennessee court may occasionally complete their supervision in a different
const content: TenantContent = {
name: "Tennessee",
- description: "Placeholder for TDOC Mission",
+ description: "Explore data from Tennessee’s corrections system.",
+ ctaCopy: `The Tennessee Department of Correction has an inherent responsibility to provide those
+ within our custody with opportunities to grow. Sharing information builds greater accountability between our department and the communities we serve.`,
coBrandingCopy:
'Produced in collaboration with the Tennessee Department of Correction.',
feedbackUrl:
diff --git a/spotlight-client/src/contentApi/types.ts b/spotlight-client/src/contentApi/types.ts
index 9215f735..94d2a2ae 100644
--- a/spotlight-client/src/contentApi/types.ts
+++ b/spotlight-client/src/contentApi/types.ts
@@ -43,6 +43,7 @@ export type DemographicCategoryFilter = {
export type TenantContent = {
name: string;
description: string;
+ ctaCopy?: string;
coBrandingCopy: string;
feedbackUrl: string;
smallDataDisclaimer: string;
diff --git a/spotlight-client/src/contentModels/Tenant.ts b/spotlight-client/src/contentModels/Tenant.ts
index c2cf234b..95398089 100644
--- a/spotlight-client/src/contentModels/Tenant.ts
+++ b/spotlight-client/src/contentModels/Tenant.ts
@@ -26,6 +26,7 @@ type InitOptions = {
id: TenantId;
name: string;
description: string;
+ ctaCopy?: string;
coBrandingCopy: string;
feedbackUrl: string;
smallDataDisclaimer: string;
@@ -48,6 +49,8 @@ export default class Tenant {
readonly description: string;
+ readonly ctaCopy?: string;
+
readonly coBrandingCopy: string;
readonly feedbackUrl: string;
@@ -64,6 +67,7 @@ export default class Tenant {
id,
name,
description,
+ ctaCopy,
coBrandingCopy,
feedbackUrl,
smallDataDisclaimer,
@@ -74,6 +78,7 @@ export default class Tenant {
this.id = id;
this.name = name;
this.description = description;
+ this.ctaCopy = ctaCopy;
this.coBrandingCopy = coBrandingCopy;
this.feedbackUrl = feedbackUrl;
this.smallDataDisclaimer = smallDataDisclaimer;
@@ -140,6 +145,7 @@ export function createTenant({ tenantId }: TenantFactoryOptions): Tenant {
id: tenantId,
name: allTenantContent.name,
description: allTenantContent.description,
+ ctaCopy: allTenantContent.ctaCopy,
coBrandingCopy: allTenantContent.coBrandingCopy,
feedbackUrl: allTenantContent.feedbackUrl,
smallDataDisclaimer: allTenantContent.smallDataDisclaimer,
diff --git a/spotlight-client/src/contentModels/__fixtures__/tenant_content_exhaustive.ts b/spotlight-client/src/contentModels/__fixtures__/tenant_content_exhaustive.ts
index 7e7a214b..6198e25b 100644
--- a/spotlight-client/src/contentModels/__fixtures__/tenant_content_exhaustive.ts
+++ b/spotlight-client/src/contentModels/__fixtures__/tenant_content_exhaustive.ts
@@ -32,6 +32,7 @@ type ExhaustiveTenantContent = Required &
const content: ExhaustiveTenantContent = {
name: "Test Tenant",
description: "test tenant description",
+ ctaCopy: "test tenant call to action",
coBrandingCopy: "test tenant co-branding",
feedbackUrl: "https://example.com/feedback",
smallDataDisclaimer: "test small data disclaimer",