From 96ecc73b172b6e8ba9818573290dec19c6d60bbf Mon Sep 17 00:00:00 2001 From: Simon Backx Date: Thu, 21 Sep 2023 17:49:41 +0200 Subject: [PATCH] Added show all recommendations instead of pagination (#18276) fixes https://github.com/TryGhost/Product/issues/3923 --- .../src/admin-x-ds/global/Table.tsx | 23 +++++++++++- .../src/api/recommendations.ts | 20 ++++++++-- .../settings/site/Recommendations.tsx | 37 ++++++++++++++++--- .../recommendations/RecommendationList.tsx | 9 +++-- .../admin-x-settings/src/utils/apiRequests.ts | 2 +- 5 files changed, 76 insertions(+), 15 deletions(-) diff --git a/apps/admin-x-settings/src/admin-x-ds/global/Table.tsx b/apps/admin-x-settings/src/admin-x-ds/global/Table.tsx index cafe24c14a..8c05a742e0 100644 --- a/apps/admin-x-settings/src/admin-x-ds/global/Table.tsx +++ b/apps/admin-x-settings/src/admin-x-ds/global/Table.tsx @@ -8,6 +8,11 @@ import clsx from 'clsx'; import {LoadingIndicator} from './LoadingIndicator'; import {PaginationData} from '../../hooks/usePagination'; +export interface ShowMoreData { + hasMore: boolean; + loadMore: () => void; +} + interface TableProps { /** * If the table is the primary content on a page (e.g. Members table) then you can set a pagetitle to be consistent @@ -21,6 +26,7 @@ interface TableProps { className?: string; isLoading?: boolean; pagination?: PaginationData; + showMore?: ShowMoreData; } const OptionalPagination = ({pagination}: {pagination?: PaginationData}) => { @@ -31,7 +37,19 @@ const OptionalPagination = ({pagination}: {pagination?: PaginationData}) => { return ; }; -const Table: React.FC = ({header, children, borderTop, hint, hintSeparator, pageTitle, className, pagination, isLoading}) => { +const OptionalShowMore = ({showMore}: {showMore?: ShowMoreData}) => { + if (!showMore || !showMore.hasMore) { + return null; + } + + return ( +
+ +
+ ); +}; + +const Table: React.FC = ({header, children, borderTop, hint, hintSeparator, pageTitle, className, pagination, showMore, isLoading}) => { const tableClasses = clsx( (borderTop || pageTitle) && 'border-t border-grey-300', 'w-full', @@ -109,12 +127,13 @@ const Table: React.FC = ({header, children, borderTop, hint, hintSep {isLoading && } - {(hint || pagination) && + {(hint || pagination || showMore) &&
{(hintSeparator || pagination) && }
{hint ?? ' '} +
} diff --git a/apps/admin-x-settings/src/api/recommendations.ts b/apps/admin-x-settings/src/api/recommendations.ts index 8c09f809bd..81207b1e19 100644 --- a/apps/admin-x-settings/src/api/recommendations.ts +++ b/apps/admin-x-settings/src/api/recommendations.ts @@ -1,4 +1,5 @@ -import {Meta, apiUrl, createMutation, createPaginatedQuery, useFetchApi} from '../utils/apiRequests'; +import {InfiniteData} from '@tanstack/react-query'; +import {Meta, apiUrl, createInfiniteQuery, createMutation, useFetchApi} from '../utils/apiRequests'; export type Recommendation = { id: string @@ -28,10 +29,23 @@ export interface RecommendationDeleteResponseType {} const dataType = 'RecommendationResponseType'; -export const useBrowseRecommendations = createPaginatedQuery({ +export const useBrowseRecommendations = createInfiniteQuery({ dataType, path: '/recommendations/', - defaultSearchParams: {} + returnData: (originalData) => { + const {pages} = originalData as InfiniteData; + let recommendations = pages.flatMap(page => page.recommendations); + + // Remove duplicates + recommendations = recommendations.filter((recommendation, index) => { + return recommendations.findIndex(({id}) => id === recommendation.id) === index; + }); + + return { + recommendations, + meta: pages[pages.length - 1].meta + }; + } }); export const useDeleteRecommendation = createMutation({ diff --git a/apps/admin-x-settings/src/components/settings/site/Recommendations.tsx b/apps/admin-x-settings/src/components/settings/site/Recommendations.tsx index 5b01cf6853..a51ff267c6 100644 --- a/apps/admin-x-settings/src/components/settings/site/Recommendations.tsx +++ b/apps/admin-x-settings/src/components/settings/site/Recommendations.tsx @@ -7,6 +7,7 @@ import SettingGroup from '../../../admin-x-ds/settings/SettingGroup'; import TabView from '../../../admin-x-ds/global/TabView'; import useRouting from '../../../hooks/useRouting'; import useSettingGroup from '../../../hooks/useSettingGroup'; +import {ShowMoreData} from '../../../admin-x-ds/global/Table'; import {useBrowseRecommendations} from '../../../api/recommendations'; import {withErrorBoundary} from '../../../admin-x-ds/global/ErrorBoundary'; @@ -17,11 +18,37 @@ const Recommendations: React.FC<{ keywords: string[] }> = ({keywords}) => { handleSave } = useSettingGroup(); - const {pagination, data: {recommendations} = {}, isLoading} = useBrowseRecommendations({ + const {data: {recommendations, meta} = {}, isLoading, hasNextPage, fetchNextPage} = useBrowseRecommendations({ searchParams: { - include: 'count.clicks,count.subscribers' - } + include: 'count.clicks,count.subscribers', + limit: '5' + }, + + // We first load 5, then load 100 at a time (= show all, but without using the dangerous 'all' limit) + getNextPageParams: (lastPage, otherParams) => { + if (!lastPage.meta) { + return; + } + const {limit, page, pages} = lastPage.meta.pagination; + if (page >= pages) { + return; + } + + const newPage = limit < 100 ? 1 : (page + 1); + + return { + ...otherParams, + page: newPage.toString(), + limit: '100' + }; + }, + keepPreviousData: true }); + + const showMore: ShowMoreData = { + hasMore: !!hasNextPage, + loadMore: fetchNextPage + }; const [selectedTab, setSelectedTab] = useState('your-recommendations'); const {updateRoute} = useRouting(); @@ -41,7 +68,7 @@ const Recommendations: React.FC<{ keywords: string[] }> = ({keywords}) => { { id: 'your-recommendations', title: 'Your recommendations', - contents: () + contents: () }, { id: 'recommending-you', @@ -51,7 +78,7 @@ const Recommendations: React.FC<{ keywords: string[] }> = ({keywords}) => { ]; const groupDescription = ( - <>Share favorite sites with your audience after they subscribe. {(pagination && pagination.total && pagination.total > 0) && Preview} + <>Share favorite sites with your audience after they subscribe. {(!!meta && !!meta.pagination.total && meta.pagination.total > 0) && Preview} ); return ( diff --git a/apps/admin-x-settings/src/components/settings/site/recommendations/RecommendationList.tsx b/apps/admin-x-settings/src/components/settings/site/recommendations/RecommendationList.tsx index 6918d72647..e2bd7eb5f7 100644 --- a/apps/admin-x-settings/src/components/settings/site/recommendations/RecommendationList.tsx +++ b/apps/admin-x-settings/src/components/settings/site/recommendations/RecommendationList.tsx @@ -1,7 +1,7 @@ import NoValueLabel from '../../../../admin-x-ds/global/NoValueLabel'; import React from 'react'; import RecommendationIcon from './RecommendationIcon'; -import Table from '../../../../admin-x-ds/global/Table'; +import Table, {ShowMoreData} from '../../../../admin-x-ds/global/Table'; import TableCell from '../../../../admin-x-ds/global/TableCell'; // import TableHead from '../../../../admin-x-ds/global/TableHead'; import EditRecommendationModal from './EditRecommendationModal'; @@ -13,7 +13,8 @@ import {Recommendation} from '../../../../api/recommendations'; interface RecommendationListProps { recommendations: Recommendation[], - pagination: PaginationData, + pagination?: PaginationData, + showMore?: ShowMoreData, isLoading: boolean } @@ -64,9 +65,9 @@ const RecommendationItem: React.FC<{recommendation: Recommendation}> = ({recomme // TODO: Remove if we decide we don't need headers // const tableHeader = (<>SiteConversions from you); -const RecommendationList: React.FC = ({recommendations, pagination, isLoading}) => { +const RecommendationList: React.FC = ({recommendations, pagination, showMore, isLoading}) => { if (isLoading || recommendations.length) { - return Shown randomized to your readers} isLoading={isLoading} pagination={pagination} hintSeparator> + return
Shown randomized to your readers} isLoading={isLoading} pagination={pagination} showMore={showMore} hintSeparator> {recommendations && recommendations.map(recommendation => )}
; } else { diff --git a/apps/admin-x-settings/src/utils/apiRequests.ts b/apps/admin-x-settings/src/utils/apiRequests.ts index 6c7c5544f9..8e31e86ee6 100644 --- a/apps/admin-x-settings/src/utils/apiRequests.ts +++ b/apps/admin-x-settings/src/utils/apiRequests.ts @@ -154,7 +154,7 @@ type InfiniteQueryOptions = Omit, 'retu type InfiniteQueryHookOptions = UseInfiniteQueryOptions & { searchParams?: Record; - getNextPageParams: (data: ResponseData, params: Record) => Record; + getNextPageParams: (data: ResponseData, params: Record) => Record|undefined; }; export const createInfiniteQuery = (options: InfiniteQueryOptions) => ({searchParams, getNextPageParams, ...query}: InfiniteQueryHookOptions) => {