-
-
-
- {isError ? (
-
There was an error fetching the mutations to display.
- ) : isPending ? (
-
- ) : (
-
- {displayMutations?.length === 0 ? (
-
- ) : (
-
- )}
-
+
+
-
+ {isError ? (
+
There was an error fetching the mutations to display.
+ ) : isPending ? (
+
+ ) : (
+
+ {displayMutations?.length === 0 ? (
+
+ ) : (
+
+ )}
+
+
+ )}
+
+
+
);
};
diff --git a/website/src/lapis/getLastUpdatedDate.spec.ts b/website/src/lapis/getLastUpdatedDate.spec.ts
index 410ee68a8..4dea079e4 100644
--- a/website/src/lapis/getLastUpdatedDate.spec.ts
+++ b/website/src/lapis/getLastUpdatedDate.spec.ts
@@ -28,7 +28,7 @@ describe('getLastUpdatedDate', () => {
const result = await getLastUpdatedDate(DUMMY_LAPIS_URL);
- expect(result).toBeUndefined();
+ expect(result).toBeNull();
});
test('should handle non-numeric data versions gracefully', async () => {
@@ -36,6 +36,6 @@ describe('getLastUpdatedDate', () => {
const result = await getLastUpdatedDate(DUMMY_LAPIS_URL);
- expect(result).toBeUndefined();
+ expect(result).toBeNull();
});
});
diff --git a/website/src/lapis/getLastUpdatedDate.ts b/website/src/lapis/getLastUpdatedDate.ts
index 4fcdac581..013724881 100644
--- a/website/src/lapis/getLastUpdatedDate.ts
+++ b/website/src/lapis/getLastUpdatedDate.ts
@@ -2,14 +2,14 @@ import axios from 'axios';
import dayjs from 'dayjs';
import { z } from 'zod';
-import { getInstanceLogger } from '../logger.ts';
+import { getClientLogger } from '../clientLogger.ts';
const lapisInfoSchema = z.object({
dataVersion: z.string(),
});
export type LapisInfo = z.infer
;
-const logger = getInstanceLogger('getLastUpdatedDate');
+const logger = getClientLogger('getLastUpdatedDate');
export async function getLastUpdatedDate(lapisUrl: string) {
try {
@@ -20,7 +20,7 @@ export async function getLastUpdatedDate(lapisUrl: string) {
const timestamp = Number(parsedLapisInfo.data.dataVersion);
if (isNaN(timestamp)) {
logger.error(`Got invalid timestamp from lapis info: '${parsedLapisInfo.data.dataVersion}'`);
- return undefined;
+ return null;
}
return dayjs.unix(timestamp).locale('de').calendar(null, { sameElse: '[on] YYYY-MM-DD' });
}
@@ -28,9 +28,9 @@ export async function getLastUpdatedDate(lapisUrl: string) {
logger.error(
`Failed to parse lapis info: ${JSON.stringify(parsedLapisInfo)} (was ${JSON.stringify(response.data)})`,
);
- return undefined;
+ return null;
} catch (error) {
logger.error(`Failed to fetch lapis info: ${JSON.stringify(error)}`);
- return undefined;
+ return null;
}
}
diff --git a/website/src/layouts/OrganismPage/AccessionsDownloadButton.astro b/website/src/layouts/OrganismPage/AccessionsDownloadButton.astro
deleted file mode 100644
index 3457d3132..000000000
--- a/website/src/layouts/OrganismPage/AccessionsDownloadButton.astro
+++ /dev/null
@@ -1,53 +0,0 @@
----
-import type { LapisFilter } from '@genspectrum/dashboard-components/util';
-
-import { assembleDownloadUrl } from './assembleDownloadUrl';
-import { Modal } from '../../styles/containers/Modal';
-import { ModalContent } from '../../styles/containers/ModalContent';
-import { ModalHeader } from '../../styles/containers/ModalHeader';
-
-type DownloadLink = {
- label: string;
- filter: LapisFilter;
- downloadFileBasename: string;
-};
-
-interface Props {
- accessionDownloadFields: string[];
- downloadLinks: DownloadLink[];
- lapisUrl: string;
-}
-
-const { accessionDownloadFields, downloadLinks, lapisUrl } = Astro.props;
-
-const modalId = 'accessionDownloadModal';
----
-
-
-
-
-
- You can download a list of accessions that are used for the visualizations on this page:
-
- {
- downloadLinks
- .map(({ label, filter, downloadFileBasename }) => ({
- label,
- url: assembleDownloadUrl(accessionDownloadFields, filter, downloadFileBasename, lapisUrl),
- }))
- .map(({ label, url }) => (
-
-
- {label}
-
- ))
- }
-
-
-
-
diff --git a/website/src/layouts/OrganismPage/AccessionsDownloadButton.tsx b/website/src/layouts/OrganismPage/AccessionsDownloadButton.tsx
new file mode 100644
index 000000000..4bb1fd7a3
--- /dev/null
+++ b/website/src/layouts/OrganismPage/AccessionsDownloadButton.tsx
@@ -0,0 +1,63 @@
+import type { LapisFilter } from '@genspectrum/dashboard-components/util';
+import type { FC } from 'react';
+
+import { assembleDownloadUrl } from './assembleDownloadUrl';
+import { Modal, useModalRef } from '../../styles/containers/Modal';
+import { ModalContent } from '../../styles/containers/ModalContent';
+import { ModalHeader } from '../../styles/containers/ModalHeader';
+
+export type DownloadLink = {
+ label: string;
+ filter: LapisFilter;
+ downloadFileBasename: string;
+};
+
+export type AccessionsDownloadButtonProps = {
+ accessionDownloadFields: string[];
+ downloadLinks: DownloadLink[];
+ lapisUrl: string;
+};
+
+export const AccessionsDownloadButton: FC = ({
+ accessionDownloadFields,
+ downloadLinks,
+ lapisUrl,
+}) => {
+ const modalRef = useModalRef();
+
+ return (
+ <>
+
+
+
+
+ You can download a list of accessions that are used for the visualizations on this page:
+
+ {downloadLinks
+ .map(({ label, filter, downloadFileBasename }) => ({
+ label,
+ url: assembleDownloadUrl(
+ accessionDownloadFields,
+ filter,
+ downloadFileBasename,
+ lapisUrl,
+ ),
+ }))
+ .map(({ label, url }) => (
+
+
+ {label}
+
+ ))}
+
+
+
+
+ >
+ );
+};
diff --git a/website/src/layouts/OrganismPage/DataPageLayout.astro b/website/src/layouts/OrganismPage/DataPageLayout.astro
deleted file mode 100644
index fbd92a1cf..000000000
--- a/website/src/layouts/OrganismPage/DataPageLayout.astro
+++ /dev/null
@@ -1,48 +0,0 @@
----
-import type { ComponentProps } from 'astro/types';
-
-import LapisUnreachableWrapperClient from '../../components/LapisUnreachableWrapperClient';
-import { type BreadcrumbElement, Breadcrumbs } from '../Breadcrumbs';
-import AccessionsDownloadButton from './AccessionsDownloadButton.astro';
-import LastUpdatedInfo from './LastUpdatedInfo.astro';
-import type { DataOrigin } from '../../types/dataOrigins.ts';
-import BaseLayout from '../base/BaseLayout.astro';
-import DataInfo from '../base/footer/DataInfo.astro';
-
-interface Props {
- title: string;
- breadcrumbs: BreadcrumbElement[];
- dataOrigins: DataOrigin[];
- lapisUrl: string;
- downloadLinks?: ComponentProps['downloadLinks'];
- accessionDownloadFields?: string[];
-}
-
-const { title, breadcrumbs, dataOrigins, lapisUrl, downloadLinks = [], accessionDownloadFields = [] } = Astro.props;
----
-
-
-
-
-
-
-
-
-
-
-
-
- {
- downloadLinks.length > 0 && (
-
- )
- }
-
-
-
-
-
diff --git a/website/src/layouts/OrganismPage/DataPageLayout.tsx b/website/src/layouts/OrganismPage/DataPageLayout.tsx
new file mode 100644
index 000000000..969d87802
--- /dev/null
+++ b/website/src/layouts/OrganismPage/DataPageLayout.tsx
@@ -0,0 +1,50 @@
+import type { FC, PropsWithChildren } from 'react';
+
+import { LapisUnreachableWrapperClient } from '../../components/LapisUnreachableWrapperClient.tsx';
+import type { DataOrigin } from '../../types/dataOrigins.ts';
+import { type BreadcrumbElement, Breadcrumbs } from '../Breadcrumbs.tsx';
+import { AccessionsDownloadButton, type DownloadLink } from './AccessionsDownloadButton.tsx';
+import { LastUpdatedInfo } from './LastUpdatedInfo.tsx';
+import { withQueryProvider } from '../../components/subscriptions/backendApi/withQueryProvider.tsx';
+import { DataInfo } from '../base/footer/DataInfo.tsx';
+
+export type DataPageLayoutProps = PropsWithChildren<{
+ breadcrumbs: BreadcrumbElement[];
+ dataOrigins: DataOrigin[];
+ lapisUrl: string;
+ downloadLinks?: DownloadLink[];
+ accessionDownloadFields?: string[];
+}>;
+
+export const DataPageLayoutInner: FC = ({
+ breadcrumbs,
+ dataOrigins,
+ lapisUrl,
+ downloadLinks = [],
+ accessionDownloadFields = [],
+ children,
+}) => {
+ return (
+
+
+
+
+
+ {children}
+
+
+ {downloadLinks.length > 0 && (
+
+ )}
+
+
+
+
+ );
+};
+
+export const DataPageLayout = withQueryProvider(DataPageLayoutInner);
diff --git a/website/src/layouts/OrganismPage/LastUpdatedInfo.astro b/website/src/layouts/OrganismPage/LastUpdatedInfo.astro
deleted file mode 100644
index 1d29acab1..000000000
--- a/website/src/layouts/OrganismPage/LastUpdatedInfo.astro
+++ /dev/null
@@ -1,13 +0,0 @@
----
-import { getLastUpdatedDate } from '../../lapis/getLastUpdatedDate';
-
-interface Props {
- lapisUrl: string;
-}
-
-const { lapisUrl } = Astro.props;
-
-const lastUpdatedDate = await getLastUpdatedDate(lapisUrl);
----
-
-Data last updated: {lastUpdatedDate ?? 'unknown'}
diff --git a/website/src/layouts/OrganismPage/LastUpdatedInfo.tsx b/website/src/layouts/OrganismPage/LastUpdatedInfo.tsx
new file mode 100644
index 000000000..67eeeae5e
--- /dev/null
+++ b/website/src/layouts/OrganismPage/LastUpdatedInfo.tsx
@@ -0,0 +1,18 @@
+import { useQuery } from '@tanstack/react-query';
+import type { FC } from 'react';
+
+import { getLastUpdatedDate } from '../../lapis/getLastUpdatedDate';
+
+interface LastUpdatedInfoProps {
+ lapisUrl: string;
+}
+
+export const LastUpdatedInfo: FC = ({ lapisUrl }) => {
+ const { data: lastUpdatedDate } = useQuery({
+ queryKey: ['lastUpdatedDate', lapisUrl],
+ queryFn: () => getLastUpdatedDate(lapisUrl),
+ throwOnError: true,
+ });
+
+ return Data last updated: {lastUpdatedDate ?? 'unknown'};
+};
diff --git a/website/src/layouts/OrganismPage/OrganismViewPageLayout.astro b/website/src/layouts/OrganismPage/OrganismViewPageLayout.astro
deleted file mode 100644
index 11d51ab7b..000000000
--- a/website/src/layouts/OrganismPage/OrganismViewPageLayout.astro
+++ /dev/null
@@ -1,33 +0,0 @@
----
-import type { ComponentProps } from 'astro/types';
-
-import AccessionsDownloadButton from './AccessionsDownloadButton.astro';
-import DataPageLayout from './DataPageLayout.astro';
-import { getLapisUrl } from '../../config';
-import type { OrganismConstants } from '../../views/OrganismConstants';
-import { type View } from '../../views/View';
-import type { PageStateHandler } from '../../views/pageStateHandlers/PageStateHandler';
-
-interface Props {
- view: View