diff --git a/CHANGELOG.md b/CHANGELOG.md index 67adf529..39b601f5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ - SFR-2033: Verify the external NYPL header links of DRB App - Update PR template with new Jira link - Add error page for /read links with invalid source +- Implement "Read Online" for UP items ## [0.18.1] diff --git a/package-lock.json b/package-lock.json index ab6011f7..5c2fa50f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,7 +11,7 @@ "@chakra-ui/react": "2.5.4", "@newrelic/next": "^0.7.0", "@nypl/design-system-react-components": "3.1.1", - "@nypl/web-reader": "4.3.4", + "@nypl/web-reader": "4.3.5", "@types/node": "^16.11.6", "css-loader": "^7.1.1", "dotenv": "^16.0.3", @@ -22,7 +22,7 @@ "next": "^13.5.6", "next-transpile-modules": "^7.0.0", "react": "^18.2.0", - "react-cookie": "^4.1.1", + "react-cookie": "7.1.4", "redux": "4.0.1", "sass": "^1.62.1", "typescript": "^4.1.3" @@ -2332,11 +2332,11 @@ } }, "node_modules/@grpc/grpc-js": { - "version": "1.10.6", - "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.10.6.tgz", - "integrity": "sha512-xP58G7wDQ4TCmN/cMUHh00DS7SRDv/+lC+xFLrTkMIN8h55X5NhZMLYbvy7dSELP15qlI6hPhNCRWVMtZMwqLA==", + "version": "1.10.10", + "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.10.10.tgz", + "integrity": "sha512-HPa/K5NX6ahMoeBv15njAc/sfF4/jmiXLar9UlC2UfHFKZzsCVLc3wbe7+7qua7w9VPh2/L6EBxyAV7/E8Wftg==", "dependencies": { - "@grpc/proto-loader": "^0.7.10", + "@grpc/proto-loader": "^0.7.13", "@js-sdsl/ordered-map": "^4.4.2" }, "engines": { @@ -2344,13 +2344,13 @@ } }, "node_modules/@grpc/proto-loader": { - "version": "0.7.12", - "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.7.12.tgz", - "integrity": "sha512-DCVwMxqYzpUCiDMl7hQ384FqP4T3DbNpXU8pt681l3UWCip1WUiD5JrkImUwCB9a7f2cq4CUTmi5r/xIMRPY1Q==", + "version": "0.7.13", + "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.7.13.tgz", + "integrity": "sha512-AiXO/bfe9bmxBjxxtYxFAXGZvMaN5s8kO+jBHAJCON8rJoB5YS/D6X7ZNc6XQkuHNmyl4CYaMI1fJ/Gn27RGGw==", "dependencies": { "lodash.camelcase": "^4.3.0", "long": "^5.0.0", - "protobufjs": "^7.2.4", + "protobufjs": "^7.2.5", "yargs": "^17.7.2" }, "bin": { @@ -5894,9 +5894,9 @@ "integrity": "sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ==" }, "node_modules/@nypl/web-reader": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/@nypl/web-reader/-/web-reader-4.3.4.tgz", - "integrity": "sha512-nC0yL3rsGkipG7Qyv+Uv5oGkwV0Bqo/LY2im3OfRzBNn0ytltynWWtrz6PJRbOt3n4YYWsxKrqk3dj7XpEZrDA==", + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/@nypl/web-reader/-/web-reader-4.3.5.tgz", + "integrity": "sha512-OKgqk6wWE76wVc+Gnl0jfNu2hOnAQeYLM+PQjkdus1IVtv7D3V4IjAzwUcB7B2IT2ubqJhPEcGHUqk23uUfr3w==", "dependencies": { "@chakra-ui/react": "2.8.1", "comlink": "4.4.1", @@ -7723,9 +7723,9 @@ } }, "node_modules/@types/cookie": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.3.3.tgz", - "integrity": "sha512-LKVP3cgXBT9RYj+t+9FDKwS5tdI+rPBXaNSkma7hvqy35lc7mAokC2zsqWJH0LaqIt3B962nuYI77hsJoT1gow==" + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.6.0.tgz", + "integrity": "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==" }, "node_modules/@types/eslint": { "version": "8.56.10", @@ -10212,11 +10212,11 @@ } }, "node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "dependencies": { - "fill-range": "^7.0.1" + "fill-range": "^7.1.1" }, "engines": { "node": ">=8" @@ -10861,9 +10861,9 @@ "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==" }, "node_modules/cookie": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", - "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==", + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", + "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", "engines": { "node": ">= 0.6" } @@ -13041,9 +13041,9 @@ } }, "node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "dependencies": { "to-regex-range": "^5.0.1" }, @@ -20655,13 +20655,13 @@ } }, "node_modules/react-cookie": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/react-cookie/-/react-cookie-4.1.1.tgz", - "integrity": "sha512-ffn7Y7G4bXiFbnE+dKhHhbP+b8I34mH9jqnm8Llhj89zF4nPxPutxHT1suUqMeCEhLDBI7InYwf1tpaSoK5w8A==", + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/react-cookie/-/react-cookie-7.1.4.tgz", + "integrity": "sha512-wDxxa/HYaSXSMlyWJvJ5uZTzIVtQTPf1gMksFgwAz/2/W3lCtY8r4OChCXMPE7wax0PAdMY97UkNJedGv7KnDw==", "dependencies": { - "@types/hoist-non-react-statics": "^3.0.1", - "hoist-non-react-statics": "^3.0.0", - "universal-cookie": "^4.0.0" + "@types/hoist-non-react-statics": "^3.3.5", + "hoist-non-react-statics": "^3.3.2", + "universal-cookie": "^7.0.0" }, "peerDependencies": { "react": ">= 16.3.0" @@ -23532,12 +23532,12 @@ } }, "node_modules/universal-cookie": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/universal-cookie/-/universal-cookie-4.0.4.tgz", - "integrity": "sha512-lbRVHoOMtItjWbM7TwDLdl8wug7izB0tq3/YVKhT/ahB4VDvWMyvnADfnJI8y6fSvsjh51Ix7lTGC6Tn4rMPhw==", + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/universal-cookie/-/universal-cookie-7.1.4.tgz", + "integrity": "sha512-Q+DVJsdykStWRMtXr2Pdj3EF98qZHUH/fXv/gwFz/unyToy1Ek1w5GsWt53Pf38tT8Gbcy5QNsj61Xe9TggP4g==", "dependencies": { - "@types/cookie": "^0.3.3", - "cookie": "^0.4.0" + "@types/cookie": "^0.6.0", + "cookie": "^0.6.0" } }, "node_modules/universalify": { @@ -24256,9 +24256,9 @@ } }, "node_modules/ws": { - "version": "8.16.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.16.0.tgz", - "integrity": "sha512-HS0c//TP7Ina87TfiPUz1rQzMhHrl/SG2guqRcTOIUYD2q8uhUdNHZYJUaQ8aTGPzCh+c6oawMKW35nFl1dxyQ==", + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", + "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", "engines": { "node": ">=10.0.0" }, diff --git a/package.json b/package.json index 2e449daf..32d15646 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,7 @@ "@chakra-ui/react": "2.5.4", "@newrelic/next": "^0.7.0", "@nypl/design-system-react-components": "3.1.1", - "@nypl/web-reader": "4.3.4", + "@nypl/web-reader": "4.3.5", "@types/node": "^16.11.6", "css-loader": "^7.1.1", "dotenv": "^16.0.3", @@ -28,7 +28,7 @@ "next": "^13.5.6", "next-transpile-modules": "^7.0.0", "react": "^18.2.0", - "react-cookie": "^4.1.1", + "react-cookie": "7.1.4", "redux": "4.0.1", "sass": "^1.62.1", "typescript": "^4.1.3" diff --git a/src/components/EditionCard/EditionCard.test.tsx b/src/components/EditionCard/EditionCard.test.tsx index b4c436f4..f1e5f6a1 100644 --- a/src/components/EditionCard/EditionCard.test.tsx +++ b/src/components/EditionCard/EditionCard.test.tsx @@ -8,6 +8,7 @@ import { upEdition, } from "~/src/__tests__/fixtures/EditionCardFixture"; import { NYPL_SESSION_ID } from "~/src/constants/auth"; +import { Cookies, CookiesProvider } from "react-cookie"; jest.mock("next/router", () => require("next-router-mock")); @@ -124,8 +125,13 @@ describe("Edition with EDD", () => { test("Shows EDD Request button when user is logged in", () => { // Set cookie before rendering the component - document.cookie = `${NYPL_SESSION_ID}="randomvalue"`; - render(); + const cookies = new Cookies(); + cookies.set(NYPL_SESSION_ID, "randomvalue"); + render( + + + + ); expect( screen.getByRole("link", { name: "Request scan for title" }) @@ -151,7 +157,6 @@ describe("Edition with EDD", () => { describe("Edition with UP", () => { test("Shows Login button when user is not logged in", () => { - document.cookie = `${NYPL_SESSION_ID}=""`; render(); expect( screen.getByRole("link", { name: "title Log in to read online" }) @@ -171,8 +176,13 @@ describe("Edition with UP", () => { }); test("Shows Read Online and Download buttons when user is logged in", () => { // Set cookie before rendering the component - document.cookie = `${NYPL_SESSION_ID}="randomvalue"`; - render(); + const cookies = new Cookies(); + cookies.set(NYPL_SESSION_ID, "randomvalue"); + render( + + + + ); expect( screen.getByRole("link", { name: "title Read Online" }) diff --git a/src/components/EditionCard/ReadOnlineLink.tsx b/src/components/EditionCard/ReadOnlineLink.tsx index 95e84f95..f9c3bcf5 100644 --- a/src/components/EditionCard/ReadOnlineLink.tsx +++ b/src/components/EditionCard/ReadOnlineLink.tsx @@ -9,13 +9,18 @@ const ReadOnlineLink: React.FC<{ readOnlineLink: ItemLink; isLoggedIn: boolean; title: string; + loginCookie?: any; }> = ({ readOnlineLink, isLoggedIn, title }) => { let linkText = "Read Online"; let linkUrl: any = { pathname: `/read/${readOnlineLink.link_id}`, }; - if (readOnlineLink.flags.nypl_login && !isLoggedIn) { + if ( + (readOnlineLink.flags.nypl_login || + readOnlineLink.flags.fulfill_limited_access) && + !isLoggedIn + ) { linkText = "Log in to read online"; linkUrl = LOGIN_LINK_BASE + encodeURIComponent(window.location.href); } diff --git a/src/components/InstanceCard/InstanceCard.test.tsx b/src/components/InstanceCard/InstanceCard.test.tsx index 06b7ed83..28d8296f 100644 --- a/src/components/InstanceCard/InstanceCard.test.tsx +++ b/src/components/InstanceCard/InstanceCard.test.tsx @@ -13,6 +13,7 @@ import { eddInstance, upInstance, } from "~/src/__tests__/fixtures/InstanceCardFixture"; +import { Cookies, CookiesProvider } from "react-cookie"; jest.mock("next/router", () => require("next-router-mock")); @@ -114,9 +115,15 @@ describe("Instance with EDD", () => { test("Shows EDD Request button when user is logged in", () => { // Set cookie before rendering the component - document.cookie = `${NYPL_SESSION_ID}="randomvalue"`; + const cookies = new Cookies(); + cookies.set(NYPL_SESSION_ID, "randomvalue"); render( - + + + ); expect( @@ -143,7 +150,6 @@ describe("Instance with EDD", () => { describe("Instance with UP", () => { test("Shows Login button when user is not logged in", () => { - document.cookie = `${NYPL_SESSION_ID}=""`; render(); expect( screen.getByRole("link", { name: "title Log in to read online" }) @@ -163,8 +169,13 @@ describe("Instance with UP", () => { }); test("Shows Read Online and Download buttons when user is logged in", () => { // Set cookie before rendering the component - document.cookie = `${NYPL_SESSION_ID}="randomvalue"`; - render(); + const cookies = new Cookies(); + cookies.set(NYPL_SESSION_ID, "randomvalue"); + render( + + + + ); expect( screen.getByRole("link", { name: "title Read Online" }) diff --git a/src/components/InstanceCard/InstanceCard.tsx b/src/components/InstanceCard/InstanceCard.tsx index 44f5df04..2a7a52ba 100644 --- a/src/components/InstanceCard/InstanceCard.tsx +++ b/src/components/InstanceCard/InstanceCard.tsx @@ -43,6 +43,7 @@ export const InstanceCard: React.FC<{ border="1px" borderColor="ui.border.default" padding="s" + paddingLeft={{ base: "l", md: null }} paddingBottom="l" paddingRight="l" > @@ -61,13 +62,22 @@ export const InstanceCard: React.FC<{ isAlignedRightActions id={`card-${instance.instance_id}`} paddingTop="m" + flexFlow={{ md: "column nowrap", lg: "row" }} + justifyContent={{ md: "center", lg: "left" }} + sx={{ + ".card-right": { + maxWidth: { base: "100%", lg: "200px" }, + marginStart: { base: "0", lg: "m" }, + marginTop: { base: "xs", lg: 0 }, + }, + }} > - + {edition.publication_date ? edition.publication_date @@ -92,6 +102,9 @@ export const InstanceCard: React.FC<{ display="flex" flexDir="column" whiteSpace="nowrap" + sx={{ + width: { base: "100%", lg: "200px" }, + }} gap="xs" > diff --git a/src/components/ReaderLayout/ReaderLayout.tsx b/src/components/ReaderLayout/ReaderLayout.tsx index 65699184..95944943 100644 --- a/src/components/ReaderLayout/ReaderLayout.tsx +++ b/src/components/ReaderLayout/ReaderLayout.tsx @@ -10,6 +10,15 @@ import { MAX_TITLE_LENGTH } from "~/src/constants/editioncard"; import dynamic from "next/dynamic"; import { MediaTypes } from "~/src/constants/mediaTypes"; import ReaderLogoSvg from "../Svgs/ReaderLogoSvg"; +import Link from "../Link/Link"; +import { addTocToManifest } from "@nypl/web-reader"; +import Loading from "../Loading/Loading"; +import { trackCtaClick } from "~/src/lib/adobe/Analytics"; +import { NYPL_SESSION_ID } from "~/src/constants/auth"; +import { useCookies } from "react-cookie"; +import { useRouter } from "next/router"; +import NotFound404 from "~/src/pages/404"; + const WebReader = dynamic(() => import("@nypl/web-reader"), { ssr: false }); // This is how we can import a css file as a url. It's complex, but necessary // eslint-disable-next-line @typescript-eslint/ban-ts-comment @@ -22,12 +31,6 @@ import readiumDefault from "!file-loader!extract-loader!css-loader!@nypl/web-rea // @ts-ignore import readiumAfter from "!file-loader!extract-loader!css-loader!@nypl/web-reader/dist/injectable-html-styles/ReadiumCSS-after.css"; -import Link from "../Link/Link"; -import { addTocToManifest } from "@nypl/web-reader"; -import Loading from "../Loading/Loading"; -import { trackCtaClick } from "~/src/lib/adobe/Analytics"; -import NotFound404 from "~/src/pages/404"; - const origin = typeof window !== "undefined" && window.location?.origin ? window.location.origin @@ -68,6 +71,11 @@ const ReaderLayout: React.FC<{ const isEmbed = MediaTypes.embed.includes(link.media_type); const isRead = MediaTypes.read.includes(link.media_type); + const isLimitedAccess = link.flags.fulfill_limited_access; + + const [cookies] = useCookies([NYPL_SESSION_ID]); + const nyplIdentityCookie = cookies[NYPL_SESSION_ID]; + const router = useRouter(); const pdfWorkerSrc = `${origin}/pdf-worker/pdf.worker.min.js`; @@ -114,7 +122,8 @@ const ReaderLayout: React.FC<{ if ( manifest && manifest.readingOrder && - manifest.readingOrder.length === 1 + manifest.readingOrder.length === 1 && + !isLimitedAccess ) { const modifiedManifest = await addTocToManifest( manifest, @@ -135,7 +144,7 @@ const ReaderLayout: React.FC<{ document.getElementById("nypl-header").style.display = "none"; document.getElementById("nypl-footer").style.display = "none"; } - }, [isRead, pdfWorkerSrc, proxyUrl, url]); + }, [isLimitedAccess, isRead, pdfWorkerSrc, proxyUrl, url]); const BackButton = () => { return ( @@ -185,6 +194,11 @@ const ReaderLayout: React.FC<{ pdfWorkerSrc={pdfWorkerSrc} headerLeft={} injectablesFixed={injectables} + getContent={ + isLimitedAccess + ? EditionCardUtils.createGetContent(nyplIdentityCookie, router) + : undefined + } /> )} diff --git a/src/types/DataModel.ts b/src/types/DataModel.ts index 3b434cee..a8e6de32 100644 --- a/src/types/DataModel.ts +++ b/src/types/DataModel.ts @@ -65,6 +65,7 @@ export type LinkFlags = { edd?: boolean; embed?: boolean; nypl_login?: boolean; + fulfill_limited_access?: boolean; }; export type ItemLink = { diff --git a/src/util/EditionCardUtils.tsx b/src/util/EditionCardUtils.tsx index e59b2334..3849fc38 100644 --- a/src/util/EditionCardUtils.tsx +++ b/src/util/EditionCardUtils.tsx @@ -1,4 +1,3 @@ -import React from "react"; import { Agent, Instance, @@ -16,6 +15,8 @@ import { PLACEHOLDER_COVER_LINK, } from "../constants/editioncard"; import { MediaTypes } from "../constants/mediaTypes"; +import { NextRouter } from "next/router"; +import { LOGIN_LINK_BASE } from "../constants/links"; // EditionCard holds all the methods needed to build an Edition Card export default class EditionCardUtils { @@ -220,4 +221,41 @@ export default class EditionCardUtils { : undefined; return universityPress !== undefined; } + + static createGetContent = (nyplIdentityCookie: any, router: NextRouter) => { + const fetchWithAuth = async (fulfillUrl: string, proxyUrl?: string) => { + const url = new URL(fulfillUrl); + const res = await fetch(url.toString(), { + method: "GET", + headers: { + Authorization: `Bearer ${nyplIdentityCookie.access_token}`, + }, + }); + + if (res.ok) { + // Generate the resource URL using the proxy + const resourceUrl = res.url; + const proxiedUrl: string = proxyUrl + ? `${proxyUrl}${encodeURIComponent(resourceUrl)}` + : resourceUrl; + const response = await fetch(proxiedUrl, { mode: "cors" }); + const resourceAsByteArray = new Uint8Array( + await response.arrayBuffer() + ); + + if (!response.ok) { + throw new Error("Response not Ok for URL: " + url); + } + return resourceAsByteArray; + } else { + // redirect to the NYPL login page if access token is invalid + if (res.status === 401) { + router.push( + LOGIN_LINK_BASE + encodeURIComponent(window.location.href) + ); + } + } + }; + return fetchWithAuth; + }; }