Skip to content

Commit

Permalink
Merge pull request #255 from NYPL/feature/DR-3100/connect-all-collect…
Browse files Browse the repository at this point in the history
…ions-page-to-api

DR-3100 connect all collections page to api
  • Loading branch information
avertrees authored Dec 18, 2024
2 parents 8b2f99f + 18cd456 commit dc7c4f1
Show file tree
Hide file tree
Showing 19 changed files with 1,639 additions and 455 deletions.
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Added `/healthcheck` endpoint (DR-3304)

### Updated

- Update `/collections` page to fetch data from Repo API and meet designs (DR-3100)
- Update `/collections/lane/:slug` page to fetch data from Repo API (DR-3701)
- Refactored implementation of default featured item & updated default number of digitized items (DR-3305)
- Update thumbnail logic so thumbnails are never restricted (DR-3293)
- Update feedback form credentials to use official DR service account (DR-2794)
Expand Down
541 changes: 541 additions & 0 deletions __tests__/__mocks__/data/mockApiResponses.tsx

Large diffs are not rendered by default.

929 changes: 511 additions & 418 deletions __tests__/__mocks__/data/mockPaginatedCollections.tsx

Large diffs are not rendered by default.

35 changes: 35 additions & 0 deletions __tests__/pages/collectionspage.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { render } from "@testing-library/react";
import { axe } from "jest-axe";
import React from "react";
import { mockCollectionsResponse } from "__tests__/__mocks__/data/mockApiResponses";
import { CollectionsPage } from "@/src/components/pages/collectionsPage/collectionsPage";
import { useRouter } from "next/navigation";

jest.mock("next/navigation", () => ({
useRouter: jest.fn(),
usePathname: jest.fn(),
}));

beforeEach(() => {
(useRouter as jest.Mock).mockImplementation(() => ({
pathname: "/collections",
}));
});

describe.skip("Collections page Accessibility", () => {
const searchParams = {
collection_keywords: "flower",
sort: "title-asc",
page: "2",
};
it("passes axe accessibility test", async () => {
const { container } = render(
<CollectionsPage
data={mockCollectionsResponse}
params={searchParams}
renderCollections={true}
/>
);
expect(await axe(container)).toHaveNoViolations();
}, 60000);
});
38 changes: 34 additions & 4 deletions app/collections/page.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
import React from "react";
import React, { Suspense } from "react";
import { Metadata } from "next";
import { CollectionsPage } from "../src/components/pages/collectionsPage/collectionsPage";
import { mockCollectionCards } from "__tests__/__mocks__/data/mockCollectionCards";
import { getCollectionsData } from "@/src/utils/apiHelpers";
import { redirect } from "next/navigation";
import CollectionSearchParams from "@/src/types/CollectionSearchParams";
export type CollectionsProps = {
params: { slug: string };
searchParams: CollectionSearchParams;
};

export const metadata: Metadata = {
title: "Collections - NYPL Digital Collections",
Expand All @@ -10,6 +16,30 @@ export const metadata: Metadata = {
},
};

export default async function Collections() {
return <CollectionsPage data={mockCollectionCards} />;
export default async function Collections({ searchParams }: CollectionsProps) {
const data = await getCollectionsData({
keyword: searchParams.collection_keywords,
sortID: searchParams.sort,
pageNum: searchParams.page,
});

// Repo API returns 404s within the data.
if (
data?.headers?.code === "404" &&
data?.headers?.message !== "No collections found"
) {
redirect("/404");
}

const renderCollections =
data?.collection !== undefined && !data?.collection?.nil;
return (
<Suspense>
<CollectionsPage
data={data}
params={searchParams}
renderCollections={renderCollections}
/>
</Suspense>
);
}
3 changes: 1 addition & 2 deletions app/divisions/[slug]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,10 @@ export default async function Division({
if (data?.headers?.code === "404") {
redirect("/404");
}
const currentPage = Number(searchParams.page) || 1;

return (
<Suspense>
<DivisionPage data={data} currentPage={currentPage} />
<DivisionPage data={data} />
</Suspense>
);
}
1 change: 0 additions & 1 deletion app/src/components/featuredItem/campaignHero.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ const CampaignHero = ({ featuredItemData }) => {
const handleError = (e: React.SyntheticEvent<HTMLImageElement, Event>) => {
console.log("error loading campaign hero:", e);
setData(defaultFeaturedItemResponse);
console.log("data in CampaignHero component is:", data);
};

return data?.featuredItem ? (
Expand Down
2 changes: 1 addition & 1 deletion app/src/components/header/header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ const Header = () => {
position="sticky"
id="header"
top={0}
zIndex={999}
zIndex={99999}
bgColor="ui.white"
sx={{
[`@media screen and (min-width: ${headerBreakpoints.smTablet}px)`]: {
Expand Down
221 changes: 213 additions & 8 deletions app/src/components/pages/collectionsPage/collectionsPage.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,115 @@
"use client";
import PageLayout from "../../pageLayout/pageLayout";
import React from "react";
import SearchResults from "../../search/results";
import React, { useState, useRef, useEffect } from "react";
import {
Box,
Heading,
HorizontalRule,
SearchBar,
Menu,
Text,
Pagination,
Flex,
Spacer,
} from "@nypl/design-system-react-components";
import { usePathname, useRouter } from "next/navigation";
import { headerBreakpoints } from "../../../utils/breakpoints";
import { CardsGrid } from "../../grids/cardsGrid";
import LaneLoading from "../../lane/laneLoading";
import {
displayResults,
totalNumPages,
createCollectionsQueryStringFromObject,
} from "../../../utils/utils";
import type { SyntheticEvent } from "react";
import { createAdobeAnalyticsPageName } from "@/src/utils/utils";
import NoResultsFound from "../../results/noResultsFound";
import {
DEFAULT_PAGE_NUM,
DEFAULT_COLLECTION_SORT,
DEFAULT_SEARCH_TERM,
} from "@/src/config/constants";

export function CollectionsPage({ data, params, renderCollections }) {
const { push } = useRouter();
const pathname = usePathname();
const [isLoaded, setIsLoaded] = useState(false);
let collections = [];

if (renderCollections) {
collections = Array.isArray(data.collection)
? data.collection
: [data.collection];
}

const headingRef = useRef<HTMLHeadingElement>(null);

const numFound = data.numFound || data.numResults;
const totalPages = totalNumPages(numFound, data.perPage);

const [currentPage, setCurrentPage] = useState<number>(
Number(params.page) || Number(DEFAULT_PAGE_NUM)
);

const [currentSort, setCurrentSort] = useState<string>(
params.sort || DEFAULT_COLLECTION_SORT
);

const [currentCollectionKeywords, setcurrentCollectionKeywords] =
useState<string>(params.collection_keywords || DEFAULT_SEARCH_TERM);

const updateURL = async (queryString) => {
setIsLoaded(false);
await push(`${pathname}?${queryString}`);
setTimeout(() => {
setIsLoaded(true);
headingRef.current?.focus();
}, 1000);
};

const handleSearchSubmit = async (e: SyntheticEvent) => {
e.preventDefault();
// something is so weird about state here.
// change the values in the object passed down to createCollectionsQueryStringFromObject to be currentPage and currentSort and tell me if it works for you....
setCurrentPage(Number(DEFAULT_PAGE_NUM));
setCurrentSort(DEFAULT_COLLECTION_SORT);
const queryString = createCollectionsQueryStringFromObject({
collection_keywords: currentCollectionKeywords,
sort: DEFAULT_COLLECTION_SORT,
page: DEFAULT_PAGE_NUM,
});
updateURL(queryString);
};

const handleSearchChange = (e: SyntheticEvent) => {
const target = e.target as HTMLInputElement;
setcurrentCollectionKeywords(target.value);
};

const onPageChange = async (pageNumber: number) => {
setCurrentPage(pageNumber);
const queryString = createCollectionsQueryStringFromObject({
collection_keywords: currentCollectionKeywords,
sort: currentSort,
page: pageNumber.toString(),
});
updateURL(`${queryString}#collections`);
};

const onMenuClick = async (id) => {
setCurrentSort(id);
const queryString = createCollectionsQueryStringFromObject({
collection_keywords: currentCollectionKeywords,
sort: id,
page: String(currentPage),
});
updateURL(`${queryString}#collections`);
};

useEffect(() => {
setIsLoaded(true);
}, []);

export const CollectionsPage = ({ data }) => {
return (
<PageLayout
activePage="collections"
Expand All @@ -23,12 +121,22 @@ export const CollectionsPage = ({ data }) => {
>
<Box
sx={{
maxWidth: "730px",
display: "flex",
flexDirection: "column",
"> hgroup": {
marginBottom: 0,
},
[`@media screen and (min-width: ${headerBreakpoints.smTablet})`]: {
maxWidth: "715px",
},
"> hgroup > p": {
fontWeight: "400 !important",
},
"> a > span": {
fontWeight: "500",
},
gap: "m",
}}
>
<Heading
Expand All @@ -37,20 +145,117 @@ export const CollectionsPage = ({ data }) => {
subtitle="Explore the New York Public Library's diverse collections, including digitized photographs, manuscripts, maps, and more. Start exploring by using the search bar below or browse through the collections."
/>
<SearchBar
sx={{ maxWidth: "462px", marginTop: "l" }}
sx={{ maxWidth: "462px" }}
id={"search-collections"}
textInputProps={{
isClearable: true,
isClearableCallback: () => {
setcurrentCollectionKeywords(DEFAULT_SEARCH_TERM);
},
labelText: "Search by collection title",
name: "searchinput",
name: "collection_keywords",
placeholder: "Search by collection title",
defaultValue: currentCollectionKeywords,
onChange: (e) => handleSearchChange(e),
}}
onSubmit={function (event: React.FormEvent): void {}}
onSubmit={handleSearchSubmit}
labelText={""}
/>
</Box>
<HorizontalRule sx={{ marginTop: "xxl", marginBottom: "xxl" }} />
<SearchResults showFilter isSearchPage={false} data={data} />
<Flex>
<Heading
size="heading5"
sx={{
display: collections?.length > 0 ? "flex" : "none",
marginBottom: "l",
}}
ref={headingRef}
tabIndex={-1}
id="collections"
width="max-content"
>
{`Displaying ${displayResults(
data.numResults,
data.perPage,
data.page
)}
results`}
</Heading>
<Spacer />
<Box
sx={{
display: collections?.length > 0 ? "flex" : "none",
gap: "xs",
marginBottom: "l",
}}
>
<Text sx={{ fontWeight: "500", marginBottom: 0, marginTop: "xs" }}>
{" "}
Sort by{" "}
</Text>{" "}
<Menu
showSelectionAsLabel
showLabel
selectedItem={currentSort}
labelText={"Sort By"}
listItemsData={[
{
id: "date-desc",
label: "Newest to oldest",
onClick: onMenuClick,
type: "action",
},
{
id: "date-asc",
label: "Oldest to newest",
onClick: onMenuClick,
type: "action",
},
{
id: "title-asc",
label: "Title A to Z",
onClick: onMenuClick,
type: "action",
},
{
id: "title-desc",
label: "Title Z to A",
onClick: onMenuClick,
type: "action",
},
]}
/>
</Box>
</Flex>

{isLoaded ? (
collections.length > 0 ? (
<CardsGrid records={collections} />
) : (
<NoResultsFound searchTerm={params.collection_keywords} />
)
) : (
Array(Math.ceil(collections.length / 4)).fill(
<LaneLoading withTitle={false} />
)
)}

{totalPages > 1 && (
<Pagination
id="pagination-id"
initialPage={currentPage}
currentPage={currentPage}
pageCount={totalPages}
onPageChange={onPageChange}
sx={{
display: "flex",
justifyContent: "center",
gap: "s",
marginTop: "xxl",
}}
/>
)}
</PageLayout>
);
};
}
Loading

0 comments on commit dc7c4f1

Please sign in to comment.