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); };