mirror of
https://github.com/TryGhost/Ghost.git
synced 2024-12-27 21:03:29 +03:00
Added show all recommendations instead of pagination (#18276)
fixes https://github.com/TryGhost/Product/issues/3923
This commit is contained in:
parent
65c4553467
commit
96ecc73b17
@ -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 <Pagination {...pagination}/>;
|
||||
};
|
||||
|
||||
const Table: React.FC<TableProps> = ({header, children, borderTop, hint, hintSeparator, pageTitle, className, pagination, isLoading}) => {
|
||||
const OptionalShowMore = ({showMore}: {showMore?: ShowMoreData}) => {
|
||||
if (!showMore || !showMore.hasMore) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={`mt-1 flex items-center gap-2 text-xs text-grey-700`}>
|
||||
<button type='button' onClick={showMore.loadMore}>Show all</button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const Table: React.FC<TableProps> = ({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<TableProps> = ({header, children, borderTop, hint, hintSep
|
||||
|
||||
{isLoading && <LoadingIndicator delay={200} size='lg' style={loadingStyle} />}
|
||||
|
||||
{(hint || pagination) &&
|
||||
{(hint || pagination || showMore) &&
|
||||
<div className="-mt-px">
|
||||
{(hintSeparator || pagination) && <Separator />}
|
||||
<div className="flex flex-col-reverse items-start justify-between gap-1 pt-2 md:flex-row md:items-center md:gap-0 md:pt-0">
|
||||
<Hint>{hint ?? ' '}</Hint>
|
||||
<OptionalPagination pagination={pagination} />
|
||||
<OptionalShowMore showMore={showMore} />
|
||||
</div>
|
||||
</div>}
|
||||
</div>
|
||||
|
@ -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<RecommendationResponseType>({
|
||||
export const useBrowseRecommendations = createInfiniteQuery<RecommendationResponseType>({
|
||||
dataType,
|
||||
path: '/recommendations/',
|
||||
defaultSearchParams: {}
|
||||
returnData: (originalData) => {
|
||||
const {pages} = originalData as InfiniteData<RecommendationResponseType>;
|
||||
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<RecommendationDeleteResponseType, Recommendation>({
|
||||
|
@ -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: (<RecommendationList isLoading={isLoading} pagination={pagination} recommendations={recommendations ?? []}/>)
|
||||
contents: (<RecommendationList isLoading={isLoading} recommendations={recommendations ?? []} showMore={showMore}/>)
|
||||
},
|
||||
{
|
||||
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) && <Link href={recommendationsURL} target='_blank'>Preview</Link>}</>
|
||||
<>Share favorite sites with your audience after they subscribe. {(!!meta && !!meta.pagination.total && meta.pagination.total > 0) && <Link href={recommendationsURL} target='_blank'>Preview</Link>}</>
|
||||
);
|
||||
|
||||
return (
|
||||
|
@ -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 = (<><TableHead>Site</TableHead><TableHead>Conversions from you</TableHead></>);
|
||||
|
||||
const RecommendationList: React.FC<RecommendationListProps> = ({recommendations, pagination, isLoading}) => {
|
||||
const RecommendationList: React.FC<RecommendationListProps> = ({recommendations, pagination, showMore, isLoading}) => {
|
||||
if (isLoading || recommendations.length) {
|
||||
return <Table hint={<span>Shown randomized to your readers</span>} isLoading={isLoading} pagination={pagination} hintSeparator>
|
||||
return <Table hint={<span>Shown randomized to your readers</span>} isLoading={isLoading} pagination={pagination} showMore={showMore} hintSeparator>
|
||||
{recommendations && recommendations.map(recommendation => <RecommendationItem key={recommendation.id} recommendation={recommendation} />)}
|
||||
</Table>;
|
||||
} else {
|
||||
|
@ -154,7 +154,7 @@ type InfiniteQueryOptions<ResponseData> = Omit<QueryOptions<ResponseData>, 'retu
|
||||
|
||||
type InfiniteQueryHookOptions<ResponseData> = UseInfiniteQueryOptions<ResponseData> & {
|
||||
searchParams?: Record<string, string>;
|
||||
getNextPageParams: (data: ResponseData, params: Record<string, string>) => Record<string, string>;
|
||||
getNextPageParams: (data: ResponseData, params: Record<string, string>) => Record<string, string>|undefined;
|
||||
};
|
||||
|
||||
export const createInfiniteQuery = <ResponseData>(options: InfiniteQueryOptions<ResponseData>) => ({searchParams, getNextPageParams, ...query}: InfiniteQueryHookOptions<ResponseData>) => {
|
||||
|
Loading…
Reference in New Issue
Block a user