diff --git a/.gitignore b/.gitignore
index 89327b30..38c0a04f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -40,3 +40,6 @@ yarn-error.log*
*.tfvars
/terraform/.terraform
/terraform/*.plan
+
+# ephemeral build artifacts
+/lib/honeybadger/config.vars.js
diff --git a/components/Footer/Footer.tsx b/components/Footer/Footer.tsx
index 8b78bbb2..5f64bb2a 100644
--- a/components/Footer/Footer.tsx
+++ b/components/Footer/Footer.tsx
@@ -7,7 +7,7 @@ export default function Footer() {
return (
-
+
);
diff --git a/components/Header/Super.tsx b/components/Header/Super.tsx
index f1fb2ed1..28e7b1ed 100644
--- a/components/Header/Super.tsx
+++ b/components/Header/Super.tsx
@@ -38,7 +38,6 @@ export default function HeaderSuper() {
}, []);
const userAuthContext = React.useContext(UserContext);
-
const handleMenu = () => setIsExpanded(!isExpanded);
return (
diff --git a/components/Search/Search.styled.ts b/components/Search/Search.styled.ts
index d4e1b384..da585c48 100644
--- a/components/Search/Search.styled.ts
+++ b/components/Search/Search.styled.ts
@@ -78,14 +78,53 @@ const Clear = styled("button", {
},
});
-const Results = styled("p", {
+const ResultsMessage = styled("span", {
color: "$black50",
- padding: "0 $gr4",
+ padding: "0 $gr4 $gr4",
fontSize: "$gr3",
- "@md": {
- padding: "0",
+ "@lg": {
+ padding: "0 0 $gr3",
},
});
-export { Button, Clear, Input, Results, SearchStyled };
+const NoResultsMessage = styled("span", {
+ display: "flex",
+ flexDirection: "column",
+ justifyContent: "center",
+ height: "100%",
+ alignItems: "center",
+ alignSelf: "center",
+ color: "$black50",
+ padding: "0 0 $gr8",
+ margin: "0 auto",
+ fontSize: "$gr3",
+ fontFamily: "$northwesternSansLight",
+ textAlign: "center",
+ flexGrow: "1",
+
+ strong: {
+ color: "$black",
+ fontFamily: "$northwesternSansBold",
+ fontWeight: "400",
+ display: "block",
+ margin: "0 0 $gr2",
+ fontSize: "$gr4",
+ },
+});
+
+const ResultsWrapper = styled("div", {
+ display: "flex",
+ flexDirection: "column",
+ minHeight: "80vh",
+});
+
+export {
+ Button,
+ Clear,
+ Input,
+ NoResultsMessage,
+ ResultsMessage,
+ ResultsWrapper,
+ SearchStyled,
+};
diff --git a/context/user-context.tsx b/context/user-context.tsx
index d77dac74..2d3adb5e 100644
--- a/context/user-context.tsx
+++ b/context/user-context.tsx
@@ -1,7 +1,6 @@
import React, { ReactNode, createContext, useState } from "react";
import { type User, type UserContext } from "@/types/context/user";
-import { DCAPI_ENDPOINT } from "@/lib/constants/endpoints";
-import axios from "axios";
+import { getUser } from "@/lib/user-helpers";
const UserContext = createContext({ user: null });
@@ -10,31 +9,25 @@ const UserProvider = ({ children }: { children: ReactNode }) => {
React.useEffect(() => {
/* Determine if user is authenticated via cookie */
- axios
- .get(`${DCAPI_ENDPOINT}/auth/whoami`, {
- withCredentials: true,
- })
- .then((result) => {
- if (!result.data) return;
- const {
- email,
- isLoggedIn = false,
- isReadingRoom = false,
- name,
- sub,
- } = result.data;
- setUser({
- email,
- isLoggedIn,
- isReadingRoom,
- name,
- sub,
- });
+ getUser().then((result) => {
+ if (!result) return;
+ const {
+ email,
+ isLoggedIn = false,
+ isReadingRoom = false,
+ name,
+ sub,
+ } = result;
+ setUser({
+ email,
+ isLoggedIn,
+ isReadingRoom,
+ name,
+ sub,
});
+ });
}, []);
- // 165.124.167.1
-
return (
{children}
);
diff --git a/lib/constants/bucket.ts b/lib/constants/bucket.ts
new file mode 100644
index 00000000..32b3561c
--- /dev/null
+++ b/lib/constants/bucket.ts
@@ -0,0 +1,3 @@
+const DC_SITEMAP_BUCKET = process.env.NEXT_PUBLIC_DC_SITEMAP_BUCKET;
+
+export { DC_SITEMAP_BUCKET };
diff --git a/lib/dc-api.ts b/lib/dc-api.ts
index 3b1739d7..48c0b8c4 100644
--- a/lib/dc-api.ts
+++ b/lib/dc-api.ts
@@ -47,7 +47,10 @@ async function apiPostRequest(
async function getIIIFResource(uri: string | null): Promise {
if (!uri) return Promise.resolve(undefined);
try {
- const response = await axios(uri);
+ const response = await axios({
+ url: uri,
+ withCredentials: true,
+ });
return response.data;
} catch (err) {
handleError(err);
@@ -56,7 +59,6 @@ async function getIIIFResource(uri: string | null): Promise {
function handleError(err: unknown) {
const error = err as AxiosError;
- //const error = err as AxiosError;
if (error.response) {
// The request was made and the server responded with a status code
// that falls out of the range of 2xx
@@ -73,4 +75,4 @@ function handleError(err: unknown) {
console.log("Error", error.message);
}
}
-export { apiGetRequest, apiPostRequest, getIIIFResource };
+export { apiGetRequest, apiPostRequest, getIIIFResource, handleError };
diff --git a/lib/ga/data-layer.ts b/lib/ga/data-layer.ts
index f98ab55a..f4a70d72 100644
--- a/lib/ga/data-layer.ts
+++ b/lib/ga/data-layer.ts
@@ -52,7 +52,6 @@ export function buildWorkDataLayer(work: Work): DataLayer {
adminset: work?.library_unit || "",
collections: work?.collection?.title ? work.collection.title : null,
creatorsContributors,
- isLoggedIn: false,
pageTitle: work?.title || "",
rightsStatement: work?.rights_statement?.label
? work.rights_statement.label
diff --git a/lib/honeybadger/config.js b/lib/honeybadger/config.js
index d25b23a3..2fb9691d 100644
--- a/lib/honeybadger/config.js
+++ b/lib/honeybadger/config.js
@@ -1,18 +1,23 @@
+import {
+ HONEYBADGER_API_KEY,
+ HONEYBADGER_ENV,
+ HONEYBADGER_REVISION,
+} from "./config.vars.js";
import Honeybadger from "@honeybadger-io/js";
const setupHoneyBadger = () => {
// https://docs.honeybadger.io/lib/javascript/reference/configuration.html
const sharedHoneybadgerConfig = {
- apiKey: process.env.HONEYBADGER_API_KEY,
- environment: process.env.HONEYBADGER_ENV || process.env.NODE_ENV,
+ apiKey: HONEYBADGER_API_KEY,
+ environment: HONEYBADGER_ENV || process.env.NODE_ENV,
projectRoot: "webpack://_N_E/./",
// Uncomment to report errors in development:
reportData: true,
- revision: process.env.AWS_COMMIT_ID,
+ revision: HONEYBADGER_REVISION,
};
-
+
if (typeof window === "undefined") {
// Node config
const projectRoot = process.cwd();
@@ -20,11 +25,11 @@ const setupHoneyBadger = () => {
...sharedHoneybadgerConfig,
projectRoot: "webpack:///./",
}).beforeNotify((notice) => {
- notice.backtrace.forEach((line) => {
+ notice.backtrace = notice.backtrace.map((line) => {
if (line.file) {
line.file = line.file.replace(
`${projectRoot}/.next/server`,
- `${process.env.HONEYBADGER_ASSETS_URL}/..`
+ `${process.env.NEXT_PUBLIC_DC_URL}/_next/..`
);
}
return line;
@@ -35,6 +40,16 @@ const setupHoneyBadger = () => {
Honeybadger.configure({
...sharedHoneybadgerConfig,
projectRoot: "webpack://_N_E/./",
+ }).beforeNotify((notice) => {
+ notice.backtrace = notice.backtrace.map((line) => {
+ if (line.file) {
+ line.file = line.file.replace(
+ /^.+\/_next\//,
+ `${process.env.NEXT_PUBLIC_DC_URL}/_next/`
+ );
+ }
+ return line;
+ });
});
}
diff --git a/lib/user-helpers.ts b/lib/user-helpers.ts
new file mode 100644
index 00000000..6be6183b
--- /dev/null
+++ b/lib/user-helpers.ts
@@ -0,0 +1,14 @@
+import { DCAPI_ENDPOINT } from "@/lib/constants/endpoints";
+import axios from "axios";
+import { handleError } from "./dc-api";
+
+export async function getUser() {
+ try {
+ const response = await axios.get(`${DCAPI_ENDPOINT}/auth/whoami`, {
+ withCredentials: true,
+ });
+ return response.data;
+ } catch (err) {
+ handleError(err);
+ }
+}
diff --git a/next.config.js b/next.config.js
index fa1834ac..f90e8905 100644
--- a/next.config.js
+++ b/next.config.js
@@ -1,14 +1,27 @@
+const fs = require("fs");
const HoneybadgerSourceMapPlugin = require("@honeybadger-io/webpack");
// Use the HoneybadgerSourceMapPlugin to upload the source maps during build step
const {
HONEYBADGER_API_KEY,
HONEYBADGER_ENV,
- HONEYBADGER_ASSETS_URL,
HONEYBADGER_REPORT_DATA,
+ NEXT_PUBLIC_DC_URL,
} = process.env;
const NODE_ENV = process.env.HONEYBADGER_ENV || process.env.NODE_ENV;
-const HONEYBADGER_REVISION = process.env.AWS_COMMIT_ID;
+const HONEYBADGER_REVISION = process.env.HONEYBADGER_REVISION || process.env.AWS_COMMIT_ID;
+
+const HoneybadgerConfig = JSON.stringify({
+ HONEYBADGER_API_KEY,
+ HONEYBADGER_ENV,
+ HONEYBADGER_REPORT_DATA,
+ HONEYBADGER_REVISION,
+}, null, 2);
+
+fs.writeFileSync(
+ "lib/honeybadger/config.vars.js",
+ `module.exports = ${HoneybadgerConfig};`
+);
/** @type {import('next').NextConfig} */
module.exports = {
@@ -28,7 +41,7 @@ module.exports = {
],
},
reactStrictMode: true,
- swcMinify: false,
+ swcMinify: true,
webpack: (config) => {
// When all the Honeybadger configuration env variables are
// available/configured The Honeybadger webpack plugin gets pushed to the
@@ -36,10 +49,10 @@ module.exports = {
// This is an alternative to manually uploading the source maps.
// See https://docs.honeybadger.io/lib/javascript/guides/using-source-maps.html
// Note: This is disabled in development mode.
+
if (
HONEYBADGER_API_KEY &&
- HONEYBADGER_ASSETS_URL &&
- NODE_ENV === "production"
+ (NODE_ENV === "production" || NODE_ENV === "staging")
) {
// `config.devtool` must be 'hidden-source-map' or 'source-map' to properly pass sourcemaps.
// Next.js uses regular `source-map` which doesnt pass its sourcemaps to Webpack.
@@ -47,11 +60,10 @@ module.exports = {
// Use the hidden-source-map option when you don't want the source maps to be
// publicly available on the servers, only to the error reporting
config.devtool = "hidden-source-map";
-
config.plugins.push(
new HoneybadgerSourceMapPlugin({
apiKey: HONEYBADGER_API_KEY,
- assetsUrl: HONEYBADGER_ASSETS_URL,
+ assetsUrl: `${NEXT_PUBLIC_DC_URL}/_next`,
revision: HONEYBADGER_REVISION,
})
);
diff --git a/package-lock.json b/package-lock.json
index aa980c60..ddd9961a 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -10,7 +10,7 @@
"@honeybadger-io/webpack": "^4.8.2",
"@iiif/parser": "^1.0.10",
"@nulib/dcapi-types": "^2.0.0-rc.4",
- "@nulib/design-system": "^1.6.1",
+ "@nulib/design-system": "^1.6.2",
"@radix-ui/colors": "^0.1.8",
"@radix-ui/react-accordion": "^1.0.1",
"@radix-ui/react-aspect-ratio": "^1.0.1",
@@ -2004,9 +2004,9 @@
"integrity": "sha512-vA1gAHusP9xE8NWEnPdKM0t8UsN8bcgw5a1j7/8fGekl2mgb/6H2F10J0bJ9MslfvLVsA1YFoW9yaV7OxbOzMg=="
},
"node_modules/@nulib/design-system": {
- "version": "1.6.1",
- "resolved": "https://registry.npmjs.org/@nulib/design-system/-/design-system-1.6.1.tgz",
- "integrity": "sha512-U8gKtb+Pu64Knw4P518LK1hDVeyb67+8iSDC/1+WqNHK7KsO+UoOfkiPoDNQSsyNn8/3D5msDiMl01rXbDt46Q==",
+ "version": "1.6.2",
+ "resolved": "https://registry.npmjs.org/@nulib/design-system/-/design-system-1.6.2.tgz",
+ "integrity": "sha512-tY3lxsfvh9fcNiEa/+o3C9gatRwA38UBMsfWIG2g2NeD4IzOnUmKgNr1FE1O7xxsHMAtIJuQ/A2dtMbWAJwNbw==",
"dependencies": {
"@radix-ui/react-popover": "^0.1.7-rc.43",
"@stitches/react": "^1.2.7",
@@ -15064,9 +15064,9 @@
"integrity": "sha512-vA1gAHusP9xE8NWEnPdKM0t8UsN8bcgw5a1j7/8fGekl2mgb/6H2F10J0bJ9MslfvLVsA1YFoW9yaV7OxbOzMg=="
},
"@nulib/design-system": {
- "version": "1.6.1",
- "resolved": "https://registry.npmjs.org/@nulib/design-system/-/design-system-1.6.1.tgz",
- "integrity": "sha512-U8gKtb+Pu64Knw4P518LK1hDVeyb67+8iSDC/1+WqNHK7KsO+UoOfkiPoDNQSsyNn8/3D5msDiMl01rXbDt46Q==",
+ "version": "1.6.2",
+ "resolved": "https://registry.npmjs.org/@nulib/design-system/-/design-system-1.6.2.tgz",
+ "integrity": "sha512-tY3lxsfvh9fcNiEa/+o3C9gatRwA38UBMsfWIG2g2NeD4IzOnUmKgNr1FE1O7xxsHMAtIJuQ/A2dtMbWAJwNbw==",
"requires": {
"@radix-ui/react-popover": "^0.1.7-rc.43",
"@stitches/react": "^1.2.7",
diff --git a/package.json b/package.json
index 0e193b69..4fc95f7a 100644
--- a/package.json
+++ b/package.json
@@ -19,7 +19,7 @@
"@honeybadger-io/webpack": "^4.8.2",
"@iiif/parser": "^1.0.10",
"@nulib/dcapi-types": "^2.0.0-rc.4",
- "@nulib/design-system": "^1.6.1",
+ "@nulib/design-system": "^1.6.2",
"@radix-ui/colors": "^0.1.8",
"@radix-ui/react-accordion": "^1.0.1",
"@radix-ui/react-aspect-ratio": "^1.0.1",
diff --git a/pages/_app.tsx b/pages/_app.tsx
index ed6debda..102667d6 100644
--- a/pages/_app.tsx
+++ b/pages/_app.tsx
@@ -5,8 +5,10 @@ import React from "react";
import Script from "next/script";
import { SearchProvider } from "@/context/search-context";
import Transition from "@/components/Transition";
+import { User } from "@/types/context/user";
import { UserProvider } from "@/context/user-context";
import { defaultOpenGraphData } from "@/lib/open-graph";
+import { getUser } from "@/lib/user-helpers";
import globalStyles from "@/styles/global";
import setupHoneyBadger from "@/lib/honeybadger/config";
@@ -22,21 +24,32 @@ function MyApp({ Component, pageProps }: MyAppProps) {
const { openGraphData = {} } = pageProps;
const ogData = { ...defaultOpenGraphData, ...openGraphData };
const [mounted, setMounted] = React.useState(false);
- React.useEffect(() => setMounted(true), []);
+ const [user, setUser] = React.useState();
+
+ React.useEffect(() => {
+ async function getData() {
+ const userResponse = await getUser();
+ setUser(userResponse);
+ setMounted(true);
+ }
+ getData();
+ }, []);
{
/** Add GTM (Google Tag Manager) data */
}
React.useEffect(() => {
- if (typeof window !== "undefined") {
- // @ts-ignore
- window.dataLayer?.push({
+ if (typeof window !== "undefined" && mounted) {
+ const payload = {
event: "VirtualPageView",
// @ts-ignore
...pageProps.dataLayer,
- });
+ isLoggedIn: user?.isLoggedIn,
+ };
+ // @ts-ignore
+ window.dataLayer?.push(payload);
}
- }, [pageProps]);
+ }, [mounted, pageProps, user]);
return (
<>
diff --git a/pages/api/sitemap/[filename].tsx b/pages/api/sitemap/[filename].tsx
new file mode 100644
index 00000000..811ef755
--- /dev/null
+++ b/pages/api/sitemap/[filename].tsx
@@ -0,0 +1,47 @@
+import { NextApiRequest, NextApiResponse } from "next";
+import axios, { AxiosError, AxiosResponse } from "axios";
+import { DC_SITEMAP_BUCKET } from "@/lib/constants/bucket";
+
+class NotFound extends Error {
+ constructor() {
+ super("Not Found");
+ }
+}
+
+const getObject = async (filename: string): Promise => {
+ if (!DC_SITEMAP_BUCKET || !filename) throw new NotFound();
+ try {
+ return await axios(`http://${DC_SITEMAP_BUCKET}/${filename}`, { responseType: "stream" });
+ } catch (err) {
+ console.warn('caught in getObject', err);
+ if (err instanceof AxiosError) throw new NotFound();
+ throw err;
+ }
+};
+
+export default async function handler(req: NextApiRequest, res: NextApiResponse): Promise {
+ const { filename } = req.query;
+ try {
+ const response: AxiosResponse = await getObject(filename as string);
+
+ res
+ .status(200)
+ .setHeader("Content-Type", response.headers["content-type"] as string)
+ .setHeader("Content-Length", response.headers["content-length"] as string)
+ .setHeader("ETag", response.headers["etag"] as string)
+ .setHeader("Last-Modified", response.headers["last-modified"] as string);
+ response.data.pipe(res);
+ } catch (err) {
+ if (err instanceof NotFound) {
+ res.status(404).end("Not Found");
+ } else {
+ throw err;
+ }
+ }
+}
+
+export const config = {
+ api: {
+ responseLimit: false,
+ },
+};
diff --git a/pages/search.tsx b/pages/search.tsx
index a73ce7a5..4077071a 100644
--- a/pages/search.tsx
+++ b/pages/search.tsx
@@ -1,3 +1,8 @@
+import {
+ NoResultsMessage,
+ ResultsMessage,
+ ResultsWrapper,
+} from "@/components/Search/Search.styled";
import React, { useEffect, useState } from "react";
import { ApiSearchRequestBody } from "@/types/api/request";
import { ApiSearchResponse } from "@/types/api/response";
@@ -11,7 +16,6 @@ import Layout from "@/components/layout";
import { NextPage } from "next";
import { PRODUCTION_URL } from "@/lib/constants/endpoints";
import PaginationAltCounts from "@/components/Search/PaginationAltCounts";
-import { Results } from "@/components/Search/Search.styled";
import { apiPostRequest } from "@/lib/dc-api";
import axios from "axios";
import { buildDataLayer } from "@/lib/ga/data-layer";
@@ -130,19 +134,32 @@ const SearchPage: NextPage = () => {
-
+
{loading && <>>}
{error && {error}
}
{apiData && (
<>
-
- {totalResults && pluralize("result", totalResults)}
-
+ {totalResults ? (
+
+ {" "}
+ {pluralize("result", totalResults)}
+
+ ) : (
+
+ Your search did not match any results.{" "}
+ Please try broadening your search terms or adjusting your
+ filters.
+
+ )}
-
+ {totalResults ? (
+
+ ) : (
+ <>>
+ )}
>
)}
-
+
>
diff --git a/pages/shared/[id].tsx b/pages/shared/[id].tsx
index 09d53257..10446a6c 100644
--- a/pages/shared/[id].tsx
+++ b/pages/shared/[id].tsx
@@ -32,8 +32,8 @@ const SharedPage: NextPage = () => {
}
useEffect(() => {
- getWorkAndManifest(router.query?.id as string);
- }, [router]);
+ !!router.query.id && getWorkAndManifest(router.query.id as string);
+ }, [router.query.id]);
if (!(work && manifest)) return <>>;
diff --git a/public/sitemap.xml b/public/sitemap.xml
deleted file mode 100644
index 0eb99f7d..00000000
--- a/public/sitemap.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-
-
-
-
- https://dc.library.northwestern.edu/sitemap.xml.gz
- 2023-01-19
-
-
-
\ No newline at end of file
diff --git a/terraform/main.tf b/terraform/main.tf
index 2f3fa4e7..3be34afb 100644
--- a/terraform/main.tf
+++ b/terraform/main.tf
@@ -69,22 +69,39 @@ resource "aws_amplify_app" "dc-next" {
lifecycle {
ignore_changes = [
- custom_rule,
basic_auth_credentials
]
}
environment_variables = {
- ENV = var.environment_name
- HONEYBADGER_API_KEY = var.honeybadger_api_key
- HONEYBADGER_ENV = var.environment_name
- NEXT_PUBLIC_DCAPI_ENDPOINT = var.next_public_dcapi_endpoint
- NEXT_PUBLIC_DC_URL = var.next_public_dc_url
+ ENV = var.environment_name
+ HONEYBADGER_API_KEY = var.honeybadger_api_key
+ HONEYBADGER_ENV = var.environment_name
+ NEXT_PUBLIC_DCAPI_ENDPOINT = var.next_public_dcapi_endpoint
+ NEXT_PUBLIC_DC_URL = var.next_public_dc_url
+ NEXT_PUBLIC_DC_SITEMAP_BUCKET = aws_s3_bucket_website_configuration.sitemap_website.website_endpoint
+ }
+
+ custom_rule {
+ source = "/sitemap.xml"
+ target = "/api/sitemap/sitemap.xml"
+ status = 200
+ }
+
+ custom_rule {
+ source = "/sitemap.xml.gz"
+ target = "/api/sitemap/sitemap.xml.gz"
+ status = 200
+ }
+
+ custom_rule {
+ source = "/sitemap-<*>"
+ target = "/api/sitemap/sitemap-<*>"
+ status = 200
}
}
resource "aws_iam_role" "dc_next_amplify_role" {
-
name = "${var.project}-role"
assume_role_policy = join("", data.aws_iam_policy_document.assume_role.*.json)
managed_policy_arns = ["arn:aws:iam::aws:policy/AdministratorAccess-Amplify"]
diff --git a/terraform/sitemap_bucket.tf b/terraform/sitemap_bucket.tf
new file mode 100644
index 00000000..7689f4ae
--- /dev/null
+++ b/terraform/sitemap_bucket.tf
@@ -0,0 +1,28 @@
+data "aws_iam_policy_document" "allow_sitemap_bucket_access_from_cloudfront" {
+ statement {
+ actions = ["s3:GetObject"]
+ resources = ["${aws_s3_bucket.sitemap_bucket.arn}/*"]
+
+ principals {
+ type = "AWS"
+ identifiers = ["*"]
+ }
+ }
+}
+
+resource "aws_s3_bucket_policy" "sitemap_bucket" {
+ bucket = aws_s3_bucket.sitemap_bucket.id
+ policy = data.aws_iam_policy_document.allow_sitemap_bucket_access_from_cloudfront.json
+}
+
+resource "aws_s3_bucket" "sitemap_bucket" {
+ bucket = "${var.project}-${var.environment_name}-sitemaps"
+}
+
+resource "aws_s3_bucket_website_configuration" "sitemap_website" {
+ bucket = aws_s3_bucket.sitemap_bucket.bucket
+
+ index_document {
+ suffix = "sitemap.xml"
+ }
+}