Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Tenant homepage #344

Merged
merged 12 commits into from
Mar 2, 2021
59 changes: 15 additions & 44 deletions spotlight-client/src/App.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ import {
ByRoleOptions,
within,
fireEvent,
waitFor,
} from "@testing-library/react";
import testContent from "./contentApi/sources/us_nd";
import { renderNavigableApp } from "./testUtils";
Expand Down Expand Up @@ -69,14 +68,6 @@ describe("navigation", () => {
return verifyWithNavigation({ targetPath, lookupArgs });
});

test("narratives page", () => {
expect.hasAssertions();
const targetPath = "/us-nd/collections";
const lookupArgs = ["heading", { name: "Collections", level: 1 }] as const;

return verifyWithNavigation({ targetPath, lookupArgs });
});

test("single narrative page", () => {
expect.hasAssertions();
const targetPath = "/us-nd/collections/prison";
Expand All @@ -91,53 +82,33 @@ describe("navigation", () => {
return verifyWithNavigation({ targetPath, lookupArgs });
});

test("nav bar", async () => {
const dataPortalLabel = "Explore";
const narrativesLabel = "Collections";

test("links", async () => {
const {
history: { navigate },
} = renderNavigableApp();
const inNav = within(screen.getByRole("navigation"));

expect(
inNav.queryByRole("link", { name: dataPortalLabel })
).not.toBeInTheDocument();
expect(
inNav.queryByRole("link", { name: narrativesLabel })
).not.toBeInTheDocument();

await act(() => navigate("/us-nd"));
const homeLink = inNav.getByRole("link", { name: "Spotlight" });
const tenantLink = inNav.getByRole("link", { name: "North Dakota" });
const narrativesLink = inNav.getByRole("link", { name: narrativesLabel });
const sentencingLink = screen.getByRole("link", { name: "Sentencing" });

const verifyNavLinks = () => {
expect(homeLink).toBeInTheDocument();
expect(tenantLink).toBeInTheDocument();
expect(narrativesLink).toBeInTheDocument();
};

fireEvent.click(narrativesLink);
await waitFor(() =>
expect(
screen.getByRole("heading", { name: "Collections", level: 1 })
).toBeInTheDocument()
);
verifyNavLinks();
fireEvent.click(sentencingLink);
expect(
await screen.findByRole("heading", { name: "Sentencing", level: 1 })
).toBeInTheDocument();

fireEvent.click(tenantLink);
await waitFor(() =>
expect(
screen.getByRole("heading", { name: "North Dakota", level: 1 })
).toBeInTheDocument()
);
expect(
await screen.findByRole("heading", {
name: "Explore correctional data from North Dakota.",
level: 1,
})
).toBeInTheDocument();

fireEvent.click(homeLink);
await waitFor(() =>
expect(
screen.getByRole("heading", { name: "Spotlight", level: 1 })
).toBeInTheDocument()
);
expect(
await screen.findByRole("heading", { name: "Spotlight", level: 1 })
).toBeInTheDocument();
});
});
14 changes: 7 additions & 7 deletions spotlight-client/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,10 @@ import { FOOTER_HEIGHT, NAV_BAR_HEIGHT } from "./constants";
import GlobalStyles from "./GlobalStyles";
import PageHome from "./PageHome";
import PageNarrative from "./PageNarrative";
import PageNarrativeList from "./PageNarrativeList";
import PageNotFound from "./PageNotFound";
import PageTenant from "./PageTenant";
import { NarrativesSlug } from "./routerUtils/types";
import ScrollManager from "./ScrollManager";
import SiteFooter from "./SiteFooter";
import SiteNavigation from "./SiteNavigation";
import StoreProvider from "./StoreProvider";
Expand All @@ -48,8 +48,8 @@ const PassThroughPage: React.FC<RouteComponentProps> = ({ children }) => (
<>{children}</>
);

const Main = styled.div.attrs((props) => ({ role: "main" }))`
margin-top: ${rem(NAV_BAR_HEIGHT)};
const Main = styled.div.attrs({ role: "main" })`
padding-top: ${rem(NAV_BAR_HEIGHT)};
min-height: calc(100vh - ${rem(NAV_BAR_HEIGHT + FOOTER_HEIGHT)});
`;

Expand All @@ -69,14 +69,14 @@ const App: React.FC = () => {
<PageHome path="/" />
<PassThroughPage path="/:tenantId">
<PageTenant path="/" />
<PassThroughPage path={`/${NarrativesSlug}`}>
<PageNarrativeList path="/" />
<PageNarrative path="/:narrativeTypeId/*sectionNumber" />
</PassThroughPage>
<PageNarrative
path={`/${NarrativesSlug}/:narrativeTypeId/*sectionNumber`}
/>
<PageNotFound default />
</PassThroughPage>
<PageNotFound default />
</Router>
<ScrollManager />
</Main>
<SiteFooter />
<TooltipMobile />
Expand Down
36 changes: 35 additions & 1 deletion spotlight-client/src/DataStore/UiStore.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,12 @@ import { reactImmediately } from "../testUtils";
import RootStore from "./RootStore";
import type UiStore from "./UiStore";

let rootStore: RootStore;
let store: UiStore;

beforeEach(() => {
store = new RootStore().uiStore;
rootStore = new RootStore();
store = rootStore.uiStore;
});

test("set info panel contents", () => {
Expand Down Expand Up @@ -53,3 +55,35 @@ test("set info panel contents", () => {
expect(store.renderTooltipMobile).toBeUndefined();
});
});

test("current page ID", () => {
reactImmediately(() => {
expect(store.currentPageId).toBe("");
});

runInAction(() => {
rootStore.tenantStore.currentTenantId = "US_ND";
});

reactImmediately(() => {
expect(store.currentPageId).toBe("US_ND");
});

runInAction(() => {
rootStore.tenantStore.currentNarrativeTypeId = "Parole";
});

reactImmediately(() => {
expect(store.currentPageId).toBe("US_ND::Parole");
});

runInAction(() => {
rootStore.tenantStore.currentTenantId = undefined;
});

reactImmediately(() => {
expect(store.currentPageId).toBe("");
});

expect.hasAssertions();
});
20 changes: 20 additions & 0 deletions spotlight-client/src/DataStore/UiStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,4 +39,24 @@ export default class UiStore {
this.tooltipMobileData = undefined;
this.renderTooltipMobile = undefined;
}

/**
* An easily watchable indicator of what page we're on
* (because not all routes are necessarily pages)
*/
get currentPageId(): string {
const idParts: string[] = [];

const { tenant, narrative } = this.rootStore;

if (tenant) {
idParts.push(tenant.id);

if (narrative) {
idParts.push(narrative.id);
}
}

return idParts.join("::");
}
}
76 changes: 3 additions & 73 deletions spotlight-client/src/NarrativeFooter/NarrativeFooter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,8 @@ import { Link } from "@reach/router";
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 { TenantId } from "../contentApi/types";
import SystemNarrative from "../contentModels/SystemNarrative";
import OtherNarrativeLinks from "../OtherNarrativeLinks";
import getUrlForResource from "../routerUtils/getUrlForResource";
import { useDataStore } from "../StoreProvider";
import { colors, typefaces } from "../UiLibrary";
Expand All @@ -49,83 +47,15 @@ const Heading = styled.h2`
letter-spacing: -0.04em;
`;

const LinkList = styled.ul`
display: flex;
font-size: ${rem(24)};
line-height: 1.5;
margin: ${rem(48)} -${rem(16)};
`;

const LinkListItem = styled.li`
border-top: 1px solid ${colors.rule};
flex: 1 1 auto;
margin: 0 ${rem(16)};

a {
color: ${colors.text};
display: block;
padding-top: ${rem(32)};
text-decoration: none;
width: 100%;
}
`;

const FooterLink: React.FC<{
narrative: SystemNarrative;
tenantId: TenantId;
}> = ({ narrative, tenantId }) => {
const [animationStyles, setAnimationStyles] = useSpring(() => ({
opacity: 0,
from: { opacity: 0 },
}));

return (
<LinkListItem>
<Link
to={getUrlForResource({
page: "narrative",
params: { tenantId, narrativeTypeId: narrative.id },
})}
onMouseOver={() => setAnimationStyles({ opacity: 1 })}
onFocus={() => setAnimationStyles({ opacity: 1 })}
onMouseOut={() => setAnimationStyles({ opacity: 0 })}
onBlur={() => setAnimationStyles({ opacity: 0 })}
>
{narrative.title}{" "}
<animated.span style={animationStyles}>
<Arrow color={colors.link} direction="right" />
</animated.span>
</Link>
</LinkListItem>
);
};

const Footer: React.FC = () => {
const {
tenant,
tenantStore: { currentNarrativeTypeId },
} = useDataStore();
const { tenant } = useDataStore();

if (!tenant) return null;

const narrativesToDisplay = Object.values(tenant.systemNarratives).filter(
(narrative) => narrative && narrative.id !== currentNarrativeTypeId
) as SystemNarrative[]; // this assertion is safe because undefined items were filtered out

return (
<Container aria-label="collections">
<Heading>Continue Reading</Heading>
<LinkList>
{narrativesToDisplay.map((narrative) => {
return (
<FooterLink
key={narrative.id}
tenantId={tenant.id}
narrative={narrative}
/>
);
})}
</LinkList>
<OtherNarrativeLinks />
<Link
className="NarrativeFooter__BackLink"
to={getUrlForResource({
Expand Down
Loading