Skip to content
6 changes: 3 additions & 3 deletions static-site/app/pathogens/callback.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
"use client";

import { ResourceListingInfo } from "../../components/list-resources/types";

/**
* A callback function used by the <ListResources> component (which
* see) to get the resources to display on the `/pathogens` page.
*
* @returns A Promise that resolves to a ResourceListingInfo
*/
export async function pathogenResourceListingCallback(): Promise<ResourceListingInfo> {
const sourceId = "core";
const sourceUrl = `list-resources/${sourceId}`;
const sourceUrl = `/list-resources/${sourceId}`;

const response = await fetch(sourceUrl, {
headers: { accept: "application/json" },
Expand Down
3 changes: 3 additions & 0 deletions static-site/app/pathogens/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import ListResources from "../../components/list-resources";
import { SmallSpacer, HugeSpacer } from "../../components/spacers";
import * as coreResources from "../../content/resource-listing.yaml";

import { pathogenResourceListingCallback } from "./callback";

/**
* React Server Component that generates and presents a list of
* pathogen resources.
Expand Down Expand Up @@ -43,6 +45,7 @@ export default function Pathogens(): React.ReactElement {
defaultGroupLinks
groupDisplayNames={coreResources["coreGroupDisplayNames"]}
quickLinks={coreResources["coreQuickLinks"]}
resourceListingCallback={pathogenResourceListingCallback}
resourceType="dataset"
tileData={coreResources["coreTiles"]}
versioned
Expand Down
23 changes: 23 additions & 0 deletions static-site/app/staging/[[...staging]]/callback.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
"use client";

import { ResourceListingInfo } from "../../../components/list-resources/types";

/**
* A callback function used by the <ListResources> component (which
* see) to get the resources to display on the /staging page.
*/
export async function stagingResourceListingCallback(): Promise<ResourceListingInfo> {
const sourceId = "staging";
const sourceUrl = `/list-resources/${sourceId}`;

const response = await fetch(sourceUrl, {
headers: { accept: "application/json" },
});
if (response.status !== 200) {
throw new Error(
`fetching data from "${sourceUrl}" returned status code ${response.status}`,
);
}

return (await response.json()).dataset[sourceId];
}
64 changes: 64 additions & 0 deletions static-site/app/staging/[[...staging]]/content.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import React from "react";
import type { Metadata } from "next";

import FlexCenter from "../../../components/flex-center";
import { FocusParagraphCentered } from "../../../components/focus-paragraph";
import ListResources from "../../../components/list-resources";
import { SmallSpacer, HugeSpacer } from "../../../components/spacers";

import { stagingResourceListingCallback } from "./callback";

/**
* A React Server component that generates the contents of the
* /staging page.
*
* This is abstracted out into a distinct component so that it can
* also be used in the "./not-found.tsx" component, to render the
* /staging page content beneath an error banner, when a bad URL is
* requested.
*/
export default function StagingPageContent({
metadata,
}: {
/**
* A Metadata object, that is assumed to have a `title` key with a
* string value
*/
metadata: Metadata;
}): React.ReactElement {
// the cast is not ideal, but it _is_ going to be a string...
const title = metadata.title as string; // eslint-disable-line @typescript-eslint/consistent-type-assertions

return (
<>
<HugeSpacer />
<HugeSpacer />

<h1>{title}</h1>

<SmallSpacer />

<FlexCenter>
<FocusParagraphCentered>
Staging datasets & narratives are intended primarily for internal
(Nextstrain team) usage. They should be considered unreleased and/or
out of date; they should not be used to draw scientific conclusions.
</FocusParagraphCentered>
</FlexCenter>

<HugeSpacer />

<ListResources
defaultGroupLinks={false}
groupDisplayNames={{}}
quickLinks={[]}
resourceListingCallback={stagingResourceListingCallback}
resourceType="dataset"
tileData={[]}
versioned={false}
/>

<HugeSpacer />
</>
);
}
43 changes: 43 additions & 0 deletions static-site/app/staging/[[...staging]]/error-banner.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
"use client";

import React from "react";
import { useParams } from "next/navigation";

import ErrorMessage from "../../../components/error-message";

/**
* A React Client component that detects if the requested URL
* contains path elements past `/staging`, and, if so, returns a
* component that displays an error message banner. If additional
* path elements are not detected, returns null.
*
* N.b., the way this component is used, we only render it when we've
* already determined that there _is_ a need to display an error
* message. In other words, it is fully expected that the `else`
* branch of the conditional will never actually execute.
*/
export default function ErrorBanner(): React.ReactElement | null {
const params = useParams();

if (params && params["staging"]) {
// n.b., I don't think `params["staging"]` is ever going to be
// anything other than a list, but let's make the type checker
// happy…
const path =
typeof params["staging"] === "string"
? params["staging"]
: params["staging"].join("/");

const resourceType = path.startsWith("narratives")
? "narrative"
: "dataset";

const title = `The staging ${resourceType} "nextstrain.org/staging/${path}" doesn't exist.`;
const contents = `Here is the staging page instead.`;

return <ErrorMessage title={title} contents={contents} />;
} else {
// this will never happen
return null;
}
}
19 changes: 19 additions & 0 deletions static-site/app/staging/[[...staging]]/not-found.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import React from "react";

import StagingPageContent from "./content";
import ErrorBanner from "./error-banner";
import { metadata } from "./page";

/**
* A React Server component that renders the usual `/staging` page
* content, with an error banner up-top explaining that the requested
* dataset doesn't actually exist.
*/
export default function FourOhFour(): React.ReactElement {
return (
<>
<ErrorBanner />
<StagingPageContent metadata={metadata} />
</>
);
}
68 changes: 68 additions & 0 deletions static-site/app/staging/[[...staging]]/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import React from "react";
import { Metadata } from "next";

import StagingPageContent from "./content";
import ValidateStagingUrl from "./validateStagingUrl";

const title = "Staging Data";

export const metadata: Metadata = {
title,
};

/**
* A React Server Component for `/staging`
*
* A note about how this page works:
*
* We expect three different types of requests for resources under
* `/staging`:
*
* 1) Requests for real, existing datasets (e.g., `/staging/ebola`) —
* these requests are handled by the Express-level router, and this
* Next.js page never sees them
*
* 2) Requests for the plain `/staging` page — that request is handled
* by this page, and we expect it to return a resource listing of
* staging datasets, with an HTTP status code of 200
*
* 3) Requests for some longer URL that does NOT correspond to a real,
* existing dataset (e.g., `/staging/foo`) — in this case, we want
* to display the same resource listing as the base `/staging`
* page, but to also include an error banner indicating that the
* requested resource (`nextstrain.org/staging/foo` in our example)
* does not exist. We also want the HTTP status code for the
* response to this request to be a 404
*
* We accomplish this as follows:
*
* Requests of type #1 are handled completely at the Express level,
* and this page never sees them.
*
* Requests of type #2 and type #3 _are_ handled by this page. It uses
* the `<ValidateStagingUrl>` component to detect whether the
* requested URL was the plain `/staging` or whether there are
* additional path components beyond that (again, `/staging/foo` in
* our example). If there _are_ additional path elements,
* `<ValidateStagingUrl>` detects that and calls Next.js's
* `notFound()` method, which results in the `./not-found.tsx` page
* being rendered and returned. If there are not additional path
* elements (i.e., if the request was for `/staging`),
* `<ValidateStagingUrl>` returns nothing, and the
* `<StagingPageContent>` component delivers the desired resource
* listing.
*
* If the `./not-found.tsx` page is rendered, it handles the display
* of the error banner; it also uses the `<StagingPageContent>`
* component to render the same resource listing as the default case.
* However, because it has been invoked via the Next.js `notFound()`
* method, it will return a 404 status code.
*/
export default function StagingPage(): React.ReactElement {
return (
<>
<ValidateStagingUrl />
<StagingPageContent metadata={metadata} />
</>
);
}
23 changes: 23 additions & 0 deletions static-site/app/staging/[[...staging]]/validateStagingUrl.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
"use client";

import { notFound, useParams }from "next/navigation";

/**
* A React Client Component to detect when an invalid `/staging/` URL
* is requested, which calls the `notFound()` method to redirect to
* the `not-found.tsx` component
*
* Note that any actually valid `/staging/<something>` URL will be
* redirected at the Express router level, before the Next.js router
* is engaged, so if we are trying to render a `/staging/<something>`
* URL, it _is_ an error
*/
export default function ValidateStagingUrl(): null {
const params = useParams();

if (params && params["staging"]) {
notFound();
}
else { return null; }

}
25 changes: 25 additions & 0 deletions static-site/components/error-message/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import React from "react";

import styles from "./styles.module.css";

/**
* A React Server Component that displays an error message, intended
* to be used near the top of a page.
*/
export default function ErrorMessage({
title,
contents,
}: {
title: string;
contents: string;
}): React.ReactElement {
return (
<div className={styles.errorMessage}>
<span className={styles.strongerText}>{title}</span>
<br />
Please <a href="/contact">contact us</a> if you believe this to be an
error.
<p>{contents}</p>
</div>
);
}
16 changes: 16 additions & 0 deletions static-site/components/error-message/styles.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
.errorMessage {
background-color: #c2c1be;
font-size: 18px;
height: 15%;
left: 0px;
margin: 25px 0px 25px 0px;
padding: 25px 0px 25px 0px;
text-align: center;
}
.errorMessage p {
margin: 20px 0 0;
}

.strongerText {
font-weight: 500;
}
9 changes: 6 additions & 3 deletions static-site/components/list-resources/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,14 +28,13 @@ import { createFilterOption, useFilterOptions } from "./use-filter-options";
import useSortAndFilter from "./use-sort-and-filter";
import useDataFetch from "./use-data-fetch";

import { pathogenResourceListingCallback } from "../../app/pathogens/callback";

import {
FilterTile,
FilterOption,
Group,
QuickLink,
Resource,
ResourceListingInfo,
SortMethod,
convertVersionedResource,
} from "./types";
Expand All @@ -57,6 +56,9 @@ interface ListResourcesProps {

tileData: FilterTile[];

/** callback to use to get data */
resourceListingCallback: () => Promise<ResourceListingInfo>;

/** this is currently unused */
resourceType: string;
}
Expand Down Expand Up @@ -142,14 +144,15 @@ function ListResourcesContent({
defaultGroupLinks = false,
groupDisplayNames,
tileData,
resourceListingCallback,
}: ListResourcesProps & {
elWidth: number;
}): React.ReactElement {
const { groups, dataFetchError } = useDataFetch(
versioned,
defaultGroupLinks,
groupDisplayNames,
pathogenResourceListingCallback,
resourceListingCallback,
);

const tiles = useTiles(tileData, groups);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,6 @@
}

.unlinkedGroupName {
font-size: "2rem";
font-weight: "500";
font-size: 2rem;
font-weight: 500;
}
3 changes: 0 additions & 3 deletions static-site/pages/staging/[[...staging]].jsx

This file was deleted.

Loading