diff --git a/next-env.d.ts b/next-env.d.ts
index eb67037a8..2652229d9 100644
--- a/next-env.d.ts
+++ b/next-env.d.ts
@@ -1,6 +1,6 @@
///
///
-import './dist/dev/types/routes.d.ts';
+import './dist/types/routes.d.ts';
// NOTE: This file should not be edited
// see https://nextjs.org/docs/pages/api-reference/config/typescript for more information.
diff --git a/src/components/Feedbacks/EmptyStatePanel.test.tsx b/src/components/Feedbacks/EmptyStatePanel.test.tsx
new file mode 100644
index 000000000..1caf4b7a8
--- /dev/null
+++ b/src/components/Feedbacks/EmptyStatePanel.test.tsx
@@ -0,0 +1,55 @@
+import { render, screen } from '@/test-utils';
+import { describe, expect, test } from 'vitest';
+import { EmptyStatePanel } from './EmptyStatePanel';
+
+describe('EmptyStatePanel', () => {
+ test('renders title and description', () => {
+ render();
+
+ expect(screen.getByRole('region')).toBeInTheDocument();
+ expect(screen.getByText('No citations yet')).toBeInTheDocument();
+ expect(screen.getByText('Papers that cite this work will appear here.')).toBeInTheDocument();
+ });
+
+ test('renders primary action when provided', () => {
+ render(
+ ,
+ );
+
+ const link = screen.getByRole('link', { name: 'Show in search results' });
+ expect(link).toBeInTheDocument();
+ expect(link).toHaveAttribute('href', '/search?q=citations(bibcode:test)');
+ });
+
+ test('renders secondary action when provided', () => {
+ render(
+ ,
+ );
+
+ const link = screen.getByRole('link', { name: 'Back to Abstract' });
+ expect(link).toBeInTheDocument();
+ expect(link).toHaveAttribute('href', '/abs/test/abstract');
+ });
+
+ test('renders both actions when provided', () => {
+ render(
+ ,
+ );
+
+ expect(screen.getByRole('link', { name: 'Show in search results' })).toBeInTheDocument();
+ expect(screen.getByRole('link', { name: 'Back to Abstract' })).toBeInTheDocument();
+ });
+});
diff --git a/src/components/Feedbacks/EmptyStatePanel.tsx b/src/components/Feedbacks/EmptyStatePanel.tsx
new file mode 100644
index 000000000..e26a4589a
--- /dev/null
+++ b/src/components/Feedbacks/EmptyStatePanel.tsx
@@ -0,0 +1,60 @@
+import { Box, Button, Heading, Text, VStack, useColorModeValue } from '@chakra-ui/react';
+import { SimpleLink } from '@/components/SimpleLink';
+import { ReactElement } from 'react';
+
+export interface EmptyStatePanelAction {
+ label: string;
+ href: string;
+}
+
+export interface EmptyStatePanelProps {
+ title: string;
+ description: string;
+ primaryAction?: EmptyStatePanelAction;
+ secondaryAction?: EmptyStatePanelAction;
+}
+
+export const EmptyStatePanel = ({
+ title,
+ description,
+ primaryAction,
+ secondaryAction,
+}: EmptyStatePanelProps): ReactElement => {
+ const cardBg = useColorModeValue('white', 'gray.800');
+ const borderColor = useColorModeValue('gray.200', 'gray.700');
+
+ return (
+
+
+
+ {title}
+
+ {description}
+ {(primaryAction || secondaryAction) && (
+
+ {primaryAction && (
+
+ )}
+ {secondaryAction && (
+
+ )}
+
+ )}
+
+
+ );
+};
diff --git a/src/components/Feedbacks/index.ts b/src/components/Feedbacks/index.ts
index c2159ef84..a3e03cfa4 100644
--- a/src/components/Feedbacks/index.ts
+++ b/src/components/Feedbacks/index.ts
@@ -1,3 +1,4 @@
export * from './CustomInfoMessage';
+export * from './EmptyStatePanel';
export * from './LoadingMessage';
export * from './StandardAlertMessage';
diff --git a/src/pages/abs/[id]/citations.tsx b/src/pages/abs/[id]/citations.tsx
index 83e4c1d2f..105c2dd42 100644
--- a/src/pages/abs/[id]/citations.tsx
+++ b/src/pages/abs/[id]/citations.tsx
@@ -1,63 +1,63 @@
-import { Alert, AlertIcon } from '@chakra-ui/react';
-import { AbstractRefList } from '@/components/AbstractRefList';
-import { AbsLayout } from '@/components/Layout/AbsLayout';
-import { useGetAbstractParams } from '@/lib/useGetAbstractParams';
import { NextPage } from 'next';
import { useRouter } from 'next/router';
-import { path } from 'ramda';
-import { ItemsSkeleton } from '@/components/ResultList/ItemsSkeleton';
-import { useGetAbstract, useGetCitations } from '@/api/search/search';
-import { IDocsEntity } from '@/api/search/types';
import { getCitationsParams } from '@/api/search/models';
+import { useGetAbstract, useGetCitations } from '@/api/search/search';
+import { AbstractRefList } from '@/components/AbstractRefList';
+import { EmptyStatePanel, StandardAlertMessage } from '@/components/Feedbacks';
+import { AbsLayout } from '@/components/Layout';
+import { ItemsSkeleton } from '@/components/ResultList';
import { createAbsGetServerSideProps } from '@/lib/serverside/absCanonicalization';
-import { NumPerPageType } from '@/types';
+import { useGetAbstractParams } from '@/lib/useGetAbstractParams';
+import { parseAPIError } from '@/utils/common/parseAPIError';
const CitationsPage: NextPage = () => {
const router = useRouter();
- const {
- data: abstractDoc,
- error: abstractError,
- isLoading: absLoading,
- isFetching: absFetching,
- } = useGetAbstract({ id: router.query.id as string });
- const doc = path(['docs', 0], abstractDoc);
+ const id = router.query.id as string;
const pageIndex = router.query.p ? parseInt(router.query.p as string) - 1 : 0;
+
+ const { data: abstractDoc, error: abstractError } = useGetAbstract({ id });
+ const doc = abstractDoc?.docs?.[0];
+
const { getParams, onPageChange, onPageSizeChange } = useGetAbstractParams(doc?.bibcode);
const { rows } = getParams();
- // get the primary response from server (or cache)
const {
data,
isSuccess,
error: citationsError,
- isLoading: citLoading,
- isFetching: citFetching,
- } = useGetCitations({ ...getParams(), start: pageIndex * rows });
+ isLoading,
+ isFetching,
+ } = useGetCitations({
+ ...getParams(),
+ start: pageIndex * rows,
+ });
- const isLoading = absLoading || absFetching || citLoading || citFetching;
+ const hasError = abstractError || citationsError;
+ const isEmpty = isSuccess && !isFetching && (!data?.docs || data.docs.length === 0);
const citationsParams = getCitationsParams(doc?.bibcode, 0, rows);
- const handlePageSizeChange = (n: NumPerPageType) => {
- onPageSizeChange(n);
- };
-
return (
- {isLoading ? : null}
- {(abstractError || citationsError) && (
-
-
- {abstractError?.message || citationsError?.message}
-
+ {isLoading || isFetching ? : null}
+ {hasError && }
+ {isEmpty && (
+
)}
- {isSuccess && (
+ {isSuccess && !isEmpty && (
)}
@@ -68,26 +68,3 @@ const CitationsPage: NextPage = () => {
export default CitationsPage;
export const getServerSideProps = createAbsGetServerSideProps('citations');
-// export const getServerSideProps: GetServerSideProps = composeNextGSSP(async (ctx) => {
-// try {
-// const { id } = ctx.params as { id: string };
-// const queryClient = new QueryClient();
-// await queryClient.prefetchQuery({
-// queryKey: searchKeys.citations({ bibcode: id, start: 0 }),
-// queryFn: fetchSearch,
-// meta: { params: getCitationsParams(id, 0) },
-// });
-// return {
-// props: {
-// dehydratedState: dehydrate(queryClient),
-// },
-// };
-// } catch (err) {
-// logger.error({ err, url: ctx.resolvedUrl }, 'Error fetching details');
-// return {
-// props: {
-// pageError: parseAPIError(err),
-// },
-// };
-// }
-// });
diff --git a/src/pages/abs/[id]/coreads.tsx b/src/pages/abs/[id]/coreads.tsx
index 5bf66a45e..b74b54f8a 100644
--- a/src/pages/abs/[id]/coreads.tsx
+++ b/src/pages/abs/[id]/coreads.tsx
@@ -1,22 +1,23 @@
import { NextPage } from 'next';
import { useRouter } from 'next/router';
-import { useGetAbstractParams } from '@/lib/useGetAbstractParams';
+import { getCoreadsParams } from '@/api/search/models';
+import { useGetAbstract, useGetCoreads } from '@/api/search/search';
+import { AbstractRefList } from '@/components/AbstractRefList';
+import { EmptyStatePanel, StandardAlertMessage } from '@/components/Feedbacks';
import { AbsLayout } from '@/components/Layout';
import { ItemsSkeleton } from '@/components/ResultList';
-import { StandardAlertMessage } from '@/components/Feedbacks';
-import { parseAPIError } from '@/utils/common/parseAPIError';
-import { AbstractRefList } from '@/components/AbstractRefList';
-import { useGetAbstract, useGetCoreads } from '@/api/search/search';
-import { getCoreadsParams } from '@/api/search/models';
import { createAbsGetServerSideProps } from '@/lib/serverside/absCanonicalization';
-import { NumPerPageType } from '@/types';
+import { useGetAbstractParams } from '@/lib/useGetAbstractParams';
+import { parseAPIError } from '@/utils/common/parseAPIError';
const CoreadsPage: NextPage = () => {
const router = useRouter();
- const { data: abstractDoc } = useGetAbstract({ id: router.query.id as string });
- const doc = abstractDoc?.docs?.[0];
+ const id = router.query.id as string;
const pageIndex = router.query.p ? parseInt(router.query.p as string) - 1 : 0;
+ const { data: abstractDoc } = useGetAbstract({ id });
+ const doc = abstractDoc?.docs?.[0];
+
const { getParams, onPageChange, onPageSizeChange } = useGetAbstractParams(doc?.bibcode);
const { rows } = getParams();
@@ -24,27 +25,35 @@ const CoreadsPage: NextPage = () => {
...getParams(),
start: pageIndex * rows,
});
- const coreadsParams = getCoreadsParams(doc?.bibcode, 0, rows);
- const handlePageSizeChange = (n: NumPerPageType) => {
- onPageSizeChange(n);
- };
+ const isEmpty = isSuccess && !isFetching && (!data?.docs || data.docs.length === 0);
+ const coreadsParams = getCoreadsParams(doc?.bibcode, 0, rows);
return (
{isLoading || isFetching ? : null}
- {isError ? : null}
- {isSuccess ? (
+ {isError && }
+ {isEmpty && (
+
+ )}
+ {isSuccess && !isEmpty && (
- ) : null}
+ )}
);
};
@@ -52,26 +61,3 @@ const CoreadsPage: NextPage = () => {
export default CoreadsPage;
export const getServerSideProps = createAbsGetServerSideProps('coreads');
-// export const getServerSideProps: GetServerSideProps = composeNextGSSP(async (ctx) => {
-// try {
-// const { id } = ctx.params as { id: string };
-// const queryClient = new QueryClient();
-// await queryClient.prefetchQuery({
-// queryKey: searchKeys.coreads({ bibcode: id, start: 0 }),
-// queryFn: fetchSearch,
-// meta: { params: getCoreadsParams(id, 0) },
-// });
-// return {
-// props: {
-// dehydratedState: dehydrate(queryClient),
-// },
-// };
-// } catch (err) {
-// logger.error({ err, url: ctx.resolvedUrl }, 'Error fetching details');
-// return {
-// props: {
-// pageError: parseAPIError(err),
-// },
-// };
-// }
-// });
diff --git a/src/pages/abs/[id]/credits.tsx b/src/pages/abs/[id]/credits.tsx
index bb06f056e..b36655811 100644
--- a/src/pages/abs/[id]/credits.tsx
+++ b/src/pages/abs/[id]/credits.tsx
@@ -1,70 +1,65 @@
+import { NextPage } from 'next';
+import { useRouter } from 'next/router';
import { getCreditsParams } from '@/api/search/models';
import { useGetAbstract, useGetCredits } from '@/api/search/search';
-import { IDocsEntity } from '@/api/search/types';
import { AbstractRefList } from '@/components/AbstractRefList';
+import { EmptyStatePanel, StandardAlertMessage } from '@/components/Feedbacks';
import { AbsLayout } from '@/components/Layout';
import { ItemsSkeleton } from '@/components/ResultList';
-import { useGetAbstractParams } from '@/lib/useGetAbstractParams';
import { createAbsGetServerSideProps } from '@/lib/serverside/absCanonicalization';
-import { Alert, AlertIcon } from '@chakra-ui/react';
-import { NextPage } from 'next';
-import { useRouter } from 'next/router';
-import { path } from 'ramda';
-import { NumPerPageType } from '@/types';
+import { useGetAbstractParams } from '@/lib/useGetAbstractParams';
+import { parseAPIError } from '@/utils/common/parseAPIError';
const CreditsPage: NextPage = () => {
const router = useRouter();
- const {
- data: abstractDoc,
- error: abstractError,
- isLoading: absLoading,
- isFetching: absFetching,
- } = useGetAbstract({ id: router.query.id as string });
- const doc = path(['docs', 0], abstractDoc);
+ const id = router.query.id as string;
const pageIndex = router.query.p ? parseInt(router.query.p as string) - 1 : 0;
+
+ const { data: abstractDoc, error: abstractError } = useGetAbstract({ id });
+ const doc = abstractDoc?.docs?.[0];
+
const { getParams, onPageChange, onPageSizeChange } = useGetAbstractParams(doc?.bibcode);
const { rows } = getParams();
- // get the primary response from server (or cache)
const {
data,
isSuccess,
error: creditsError,
- isLoading: creditsLoading,
- isFetching: creditsFetching,
- } = useGetCredits({ ...getParams(), start: pageIndex * rows });
+ isLoading,
+ isFetching,
+ } = useGetCredits({
+ ...getParams(),
+ start: pageIndex * rows,
+ });
- const isLoading = absLoading || absFetching || creditsLoading || creditsFetching;
+ const hasError = abstractError || creditsError;
+ const isEmpty = isSuccess && !isFetching && (!data?.docs || data.docs.length === 0);
const creditsParams = getCreditsParams(doc?.bibcode, 0, rows);
- const handlePageSizeChange = (n: NumPerPageType) => {
- onPageSizeChange(n);
- };
-
return (
- {isLoading ? (
-
- ) : (
- <>
- {(abstractError || creditsError) && (
-
-
- {abstractError?.message || creditsError?.message}
-
- )}
- {isSuccess && (
-
- )}
- >
+ {isLoading || isFetching ? : null}
+ {hasError && }
+ {isEmpty && (
+
+ )}
+ {isSuccess && !isEmpty && (
+
)}
);
diff --git a/src/pages/abs/[id]/mentions.tsx b/src/pages/abs/[id]/mentions.tsx
index b0e1992e2..d5b6b736a 100644
--- a/src/pages/abs/[id]/mentions.tsx
+++ b/src/pages/abs/[id]/mentions.tsx
@@ -1,70 +1,65 @@
+import { NextPage } from 'next';
+import { useRouter } from 'next/router';
import { getMentionsParams } from '@/api/search/models';
import { useGetAbstract, useGetMentions } from '@/api/search/search';
-import { IDocsEntity } from '@/api/search/types';
import { AbstractRefList } from '@/components/AbstractRefList';
+import { EmptyStatePanel, StandardAlertMessage } from '@/components/Feedbacks';
import { AbsLayout } from '@/components/Layout';
import { ItemsSkeleton } from '@/components/ResultList';
-import { useGetAbstractParams } from '@/lib/useGetAbstractParams';
import { createAbsGetServerSideProps } from '@/lib/serverside/absCanonicalization';
-import { Alert, AlertIcon } from '@chakra-ui/react';
-import { NextPage } from 'next';
-import { useRouter } from 'next/router';
-import { path } from 'ramda';
-import { NumPerPageType } from '@/types';
+import { useGetAbstractParams } from '@/lib/useGetAbstractParams';
+import { parseAPIError } from '@/utils/common/parseAPIError';
const MentionsPage: NextPage = () => {
const router = useRouter();
- const {
- data: abstractDoc,
- error: abstractError,
- isLoading: absLoading,
- isFetching: absFetching,
- } = useGetAbstract({ id: router.query.id as string });
- const doc = path(['docs', 0], abstractDoc);
+ const id = router.query.id as string;
const pageIndex = router.query.p ? parseInt(router.query.p as string) - 1 : 0;
+
+ const { data: abstractDoc, error: abstractError } = useGetAbstract({ id });
+ const doc = abstractDoc?.docs?.[0];
+
const { getParams, onPageChange, onPageSizeChange } = useGetAbstractParams(doc?.bibcode);
const { rows } = getParams();
- // get the primary response from server (or cache)
const {
data,
isSuccess,
error: mentionsError,
- isLoading: mentionsLoading,
- isFetching: mentionsFetching,
- } = useGetMentions({ ...getParams(), start: pageIndex * rows });
+ isLoading,
+ isFetching,
+ } = useGetMentions({
+ ...getParams(),
+ start: pageIndex * rows,
+ });
- const isLoading = absLoading || absFetching || mentionsLoading || mentionsFetching;
+ const hasError = abstractError || mentionsError;
+ const isEmpty = isSuccess && !isFetching && (!data?.docs || data.docs.length === 0);
const mentionsParams = getMentionsParams(doc?.bibcode, 0, rows);
- const handlePageSizeChange = (n: NumPerPageType) => {
- onPageSizeChange(n);
- };
-
return (
-
- {isLoading ? (
-
- ) : (
- <>
- {(abstractError || mentionsError) && (
-
-
- {abstractError?.message || mentionsError?.message}
-
- )}
- {isSuccess && (
-
- )}
- >
+
+ {isLoading || isFetching ? : null}
+ {hasError && }
+ {isEmpty && (
+
+ )}
+ {isSuccess && !isEmpty && (
+
)}
);
diff --git a/src/pages/abs/[id]/references.tsx b/src/pages/abs/[id]/references.tsx
index fd39ef050..0ed5a036f 100644
--- a/src/pages/abs/[id]/references.tsx
+++ b/src/pages/abs/[id]/references.tsx
@@ -1,62 +1,63 @@
-import { Alert, AlertIcon } from '@chakra-ui/react';
-import { AbstractRefList } from '@/components/AbstractRefList';
-import { AbsLayout } from '@/components/Layout/AbsLayout';
-import { useGetAbstractParams } from '@/lib/useGetAbstractParams';
import { NextPage } from 'next';
import { useRouter } from 'next/router';
-import { path } from 'ramda';
-import { ItemsSkeleton } from '@/components/ResultList/ItemsSkeleton';
-import { useGetAbstract, useGetReferences } from '@/api/search/search';
-import { IDocsEntity } from '@/api/search/types';
import { getReferencesParams } from '@/api/search/models';
+import { useGetAbstract, useGetReferences } from '@/api/search/search';
+import { AbstractRefList } from '@/components/AbstractRefList';
+import { EmptyStatePanel, StandardAlertMessage } from '@/components/Feedbacks';
+import { AbsLayout } from '@/components/Layout';
+import { ItemsSkeleton } from '@/components/ResultList';
import { createAbsGetServerSideProps } from '@/lib/serverside/absCanonicalization';
-import { NumPerPageType } from '@/types';
+import { useGetAbstractParams } from '@/lib/useGetAbstractParams';
+import { parseAPIError } from '@/utils/common/parseAPIError';
const ReferencesPage: NextPage = () => {
const router = useRouter();
- const {
- data: abstractDoc,
- error: abstractError,
- isLoading: absLoading,
- isFetching: absFetching,
- } = useGetAbstract({ id: router.query.id as string });
- const doc = path(['docs', 0], abstractDoc);
+ const id = router.query.id as string;
const pageIndex = router.query.p ? parseInt(router.query.p as string) - 1 : 0;
+
+ const { data: abstractDoc, error: abstractError } = useGetAbstract({ id });
+ const doc = abstractDoc?.docs?.[0];
+
const { getParams, onPageChange, onPageSizeChange } = useGetAbstractParams(doc?.bibcode);
const { rows } = getParams();
const {
data,
isSuccess,
- isLoading: refLoading,
- isFetching: refFetching,
+ isLoading,
+ isFetching,
error: referencesError,
- } = useGetReferences({ ...getParams(), start: pageIndex * rows });
+ } = useGetReferences({
+ ...getParams(),
+ start: pageIndex * rows,
+ });
- const isLoading = refLoading || refFetching || absLoading || absFetching;
+ const hasError = abstractError || referencesError;
+ const isEmpty = isSuccess && !isFetching && (!data?.docs || data.docs.length === 0);
const referencesParams = getReferencesParams(doc?.bibcode, 0, rows);
- const handlePageSizeChange = (n: NumPerPageType) => {
- onPageSizeChange(n);
- };
-
return (
- {isLoading ? : null}
- {(abstractError || referencesError) && (
-
-
- {abstractError?.message || referencesError?.message}
-
+ {isLoading || isFetching ? : null}
+ {hasError && }
+ {isEmpty && (
+
)}
- {isSuccess && (
+ {isSuccess && !isEmpty && (
)}
@@ -67,26 +68,3 @@ const ReferencesPage: NextPage = () => {
export default ReferencesPage;
export const getServerSideProps = createAbsGetServerSideProps('references');
-// export const getServerSideProps: GetServerSideProps = composeNextGSSP(async (ctx) => {
-// try {
-// const { id } = ctx.params as { id: string };
-// const queryClient = new QueryClient();
-// await queryClient.prefetchQuery({
-// queryKey: searchKeys.references({ bibcode: id, start: 0 }),
-// queryFn: fetchSearch,
-// meta: { params: getReferencesParams(id, 0) },
-// });
-// return {
-// props: {
-// dehydratedState: dehydrate(queryClient),
-// },
-// };
-// } catch (err) {
-// logger.error({ err, url: ctx.resolvedUrl }, 'Error fetching details');
-// return {
-// props: {
-// pageError: parseAPIError(err),
-// },
-// };
-// }
-// });
diff --git a/src/pages/abs/[id]/similar.tsx b/src/pages/abs/[id]/similar.tsx
index 917f06ca6..bc36d5629 100644
--- a/src/pages/abs/[id]/similar.tsx
+++ b/src/pages/abs/[id]/similar.tsx
@@ -1,24 +1,23 @@
import { NextPage } from 'next';
import { useRouter } from 'next/router';
-
-import { path } from 'ramda';
-import { useGetAbstractParams } from '@/lib/useGetAbstractParams';
+import { getSimilarParams } from '@/api/search/models';
+import { useGetAbstract, useGetSimilar } from '@/api/search/search';
+import { AbstractRefList } from '@/components/AbstractRefList';
+import { EmptyStatePanel, StandardAlertMessage } from '@/components/Feedbacks';
import { AbsLayout } from '@/components/Layout';
import { ItemsSkeleton } from '@/components/ResultList';
-import { StandardAlertMessage } from '@/components/Feedbacks';
-import { parseAPIError } from '@/utils/common/parseAPIError';
-import { AbstractRefList } from '@/components/AbstractRefList';
-import { useGetAbstract, useGetSimilar } from '@/api/search/search';
-import { IDocsEntity } from '@/api/search/types';
-import { getSimilarParams } from '@/api/search/models';
import { createAbsGetServerSideProps } from '@/lib/serverside/absCanonicalization';
-import { NumPerPageType } from '@/types';
+import { useGetAbstractParams } from '@/lib/useGetAbstractParams';
+import { parseAPIError } from '@/utils/common/parseAPIError';
const SimilarPage: NextPage = () => {
const router = useRouter();
- const { data: abstractResult } = useGetAbstract({ id: router.query.id as string });
- const doc = path(['docs', 0], abstractResult);
+ const id = router.query.id as string;
const pageIndex = router.query.p ? parseInt(router.query.p as string) - 1 : 0;
+
+ const { data: abstractDoc } = useGetAbstract({ id });
+ const doc = abstractDoc?.docs?.[0];
+
const { getParams, onPageChange, onPageSizeChange } = useGetAbstractParams(doc?.bibcode);
const { rows } = getParams();
@@ -27,27 +26,34 @@ const SimilarPage: NextPage = () => {
start: pageIndex * rows,
});
- const handlePageSizeChange = (n: NumPerPageType) => {
- onPageSizeChange(n);
- };
-
+ const isEmpty = isSuccess && !isFetching && (!data?.docs || data.docs.length === 0);
const similarParams = getSimilarParams(doc?.bibcode, 0, rows);
return (
{isLoading || isFetching ? : null}
- {isError ? : null}
- {isSuccess ? (
+ {isError && }
+ {isEmpty && (
+
+ )}
+ {isSuccess && !isEmpty && (
- ) : null}
+ )}
);
};
@@ -55,26 +61,3 @@ const SimilarPage: NextPage = () => {
export default SimilarPage;
export const getServerSideProps = createAbsGetServerSideProps('similar');
-// export const getServerSideProps: GetServerSideProps = composeNextGSSP(async (ctx) => {
-// try {
-// const { id } = ctx.params as { id: string };
-// const queryClient = new QueryClient();
-// await queryClient.prefetchQuery({
-// queryKey: searchKeys.similar({ bibcode: id, start: 0 }),
-// queryFn: fetchSearch,
-// meta: { params: getSimilarParams(id, 0) },
-// });
-// return {
-// props: {
-// dehydratedState: dehydrate(queryClient),
-// },
-// };
-// } catch (err) {
-// logger.error({ err, url: ctx.resolvedUrl }, 'Error fetching details');
-// return {
-// props: {
-// pageError: parseAPIError(err),
-// },
-// };
-// }
-// });
diff --git a/src/pages/abs/[id]/toc.tsx b/src/pages/abs/[id]/toc.tsx
index 49a5921f6..90ee8ddaf 100644
--- a/src/pages/abs/[id]/toc.tsx
+++ b/src/pages/abs/[id]/toc.tsx
@@ -1,23 +1,21 @@
-import { AbstractRefList } from '@/components/AbstractRefList';
-import { AbsLayout } from '@/components/Layout/AbsLayout';
-import { useGetAbstractParams } from '@/lib/useGetAbstractParams';
import { NextPage } from 'next';
-import { useMemo } from 'react';
import { useRouter } from 'next/router';
-import { path } from 'ramda';
-import { ItemsSkeleton } from '@/components/ResultList/ItemsSkeleton';
-import { parseAPIError } from '@/utils/common/parseAPIError';
-import { StandardAlertMessage } from '@/components/Feedbacks';
-import { useGetAbstract, useGetToc } from '@/api/search/search';
-import { IDocsEntity } from '@/api/search/types';
import { getTocParams } from '@/api/search/models';
+import { useGetAbstract, useGetToc } from '@/api/search/search';
+import { AbstractRefList } from '@/components/AbstractRefList';
+import { EmptyStatePanel, StandardAlertMessage } from '@/components/Feedbacks';
+import { AbsLayout } from '@/components/Layout';
+import { ItemsSkeleton } from '@/components/ResultList';
import { createAbsGetServerSideProps } from '@/lib/serverside/absCanonicalization';
-import { NumPerPageType } from '@/types';
+import { useGetAbstractParams } from '@/lib/useGetAbstractParams';
+import { parseAPIError } from '@/utils/common/parseAPIError';
const VolumePage: NextPage = () => {
const router = useRouter();
- const { data: abstractResult } = useGetAbstract({ id: router.query.id as string });
- const doc = path(['docs', 0], abstractResult);
+ const id = router.query.id as string;
+
+ const { data: abstractDoc } = useGetAbstract({ id });
+ const doc = abstractDoc?.docs?.[0];
const { getParams, onPageChange, onPageSizeChange } = useGetAbstractParams(doc?.bibcode);
const { rows } = getParams();
@@ -26,31 +24,34 @@ const VolumePage: NextPage = () => {
enabled: !!getParams && !!doc?.bibcode,
});
- const handlePageSizeChange = (n: NumPerPageType) => {
- onPageSizeChange(n);
- };
-
- const tocParams = useMemo(() => {
- if (doc?.bibcode) {
- return getTocParams(doc.bibcode, 0, rows);
- }
- }, [doc, rows]);
+ const isEmpty = isSuccess && !isFetching && (!data?.docs || data.docs.length === 0);
+ const tocParams = doc?.bibcode ? getTocParams(doc.bibcode, 0, rows) : undefined;
return (
{isLoading || isFetching ? : null}
- {isError ? : null}
- {isSuccess ? (
+ {isError && }
+ {isEmpty && (
+
+ )}
+ {isSuccess && !isEmpty && (
- ) : null}
+ )}
);
};
@@ -58,26 +59,3 @@ const VolumePage: NextPage = () => {
export default VolumePage;
export const getServerSideProps = createAbsGetServerSideProps('toc');
-// export const getServerSideProps: GetServerSideProps = composeNextGSSP(async (ctx) => {
-// try {
-// const { id } = ctx.params as { id: string };
-// const queryClient = new QueryClient();
-// await queryClient.prefetchQuery({
-// queryKey: searchKeys.toc({ bibcode: id, start: 0 }),
-// queryFn: fetchSearch,
-// meta: { params: getTocParams(id, 0) },
-// });
-// return {
-// props: {
-// dehydratedState: dehydrate(queryClient),
-// },
-// };
-// } catch (err) {
-// logger.error({ err, url: ctx.resolvedUrl }, 'Error fetching details');
-// return {
-// props: {
-// pageError: parseAPIError(err),
-// },
-// };
-// }
-// });
diff --git a/src/utils/logging.ts b/src/utils/logging.ts
index d0d9248ed..fc1307162 100644
--- a/src/utils/logging.ts
+++ b/src/utils/logging.ts
@@ -8,7 +8,9 @@
* @returns First 12 characters of SHA-256 hash (undefined if no username)
*/
export const getUserLogId = async (username?: string): Promise => {
- if (!username) return undefined;
+ if (!username) {
+ return undefined;
+ }
const buffer = await globalThis.crypto.subtle.digest('SHA-256', Buffer.from(username, 'utf-8'));
const hash = Array.from(new Uint8Array(buffer))
@@ -26,7 +28,9 @@ export const getUserLogId = async (username?: string): Promise {
- if (!value) return '';
+ if (!value) {
+ return '';
+ }
// Remove control characters (newlines, tabs, null bytes, ANSI codes)
// This prevents log injection while preserving the full header value for tracing
@@ -39,11 +43,8 @@ export const sanitizeHeaderValue = (value: string | null): string => {
* @returns Record with sanitized values
*/
export const sanitizeHeaders = (headers: Record): Record => {
- return Object.entries(headers).reduce(
- (acc, [key, value]) => {
- acc[key] = sanitizeHeaderValue(value);
- return acc;
- },
- {} as Record,
- );
+ return Object.entries(headers).reduce((acc, [key, value]) => {
+ acc[key] = sanitizeHeaderValue(value);
+ return acc;
+ }, {} as Record);
};