From 4d62d7caea963fc250b3cd22b1d7547ddc4d8f24 Mon Sep 17 00:00:00 2001 From: Marco Beretta <81851188+berry-13@users.noreply.github.com> Date: Wed, 8 Jan 2025 23:27:40 +0100 Subject: [PATCH] =?UTF-8?q?=F0=9F=94=84=20refactor:=20Update=20shared=20li?= =?UTF-8?q?nks=20pagination=20and=20response=20structure;=20replace=20page?= =?UTF-8?q?Number=20with=20cursor=20for=20improved=20data=20fetching?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/models/Share.js | 52 ++- api/server/routes/share.js | 12 +- .../Nav/SettingsTabs/Data/SharedLinks.tsx | 263 +++++++------- client/src/components/ui/DataTable.tsx | 341 ++++++++---------- client/src/components/ui/PageScroller.tsx | 128 ------- client/src/components/ui/index.ts | 1 - client/src/data-provider/queries.ts | 39 +- packages/data-provider/src/api-endpoints.ts | 6 +- packages/data-provider/src/data-service.ts | 14 +- packages/data-provider/src/types/queries.ts | 18 +- 10 files changed, 340 insertions(+), 534 deletions(-) delete mode 100644 client/src/components/ui/PageScroller.tsx diff --git a/api/models/Share.js b/api/models/Share.js index ddf452aa049..7f0cd231207 100644 --- a/api/models/Share.js +++ b/api/models/Share.js @@ -95,56 +95,54 @@ async function getSharedMessages(shareId) { } } -async function getSharedLinks( - user, - pageNumber = 1, - pageSize = 25, - isPublic = true, - sortBy = 'createdAt', - sortDirection = 'desc', - search = '', -) { +async function getSharedLinks(user, pageParam, pageSize, isPublic, sortBy, sortDirection, search) { try { const query = { user, isPublic }; + if (pageParam) { + if (sortDirection === 'desc') { + query[sortBy] = { $lt: pageParam }; + } else { + query[sortBy] = { $gt: pageParam }; + } + } + if (search) { const searchResults = await Conversation.meiliSearch(search, { attributesToHighlight: ['title'], }); const conversationIds = searchResults.hits.map((hit) => hit.id); - query['conversation'] = { $in: conversationIds }; } const sort = {}; sort[sortBy] = sortDirection === 'desc' ? -1 : 1; - const [totalConvos, sharedLinks] = await Promise.all([ - SharedLink.countDocuments(query).exec(), - SharedLink.find(query) - .sort(sort) - .skip((pageNumber - 1) * pageSize) - .limit(pageSize) - .select('-__v -user') - .lean() - .exec(), - ]); + const sharedLinks = await SharedLink.find(query) + .sort(sort) + .limit(pageSize + 1) + .select('-__v -user') + .lean() + .exec(); + + const hasNextPage = sharedLinks.length > pageSize; + const links = sharedLinks.slice(0, pageSize); + + const nextCursor = hasNextPage ? links[links.length - 1][sortBy] : undefined; return { - sharedLinks: sharedLinks.map((link) => ({ + links: links.map((link) => ({ shareId: link.shareId, - title: link.conversation?.title || 'Untitled', + title: link?.title || 'Untitled', isPublic: link.isPublic, createdAt: link.createdAt, })), - totalCount: totalConvos, - pages: Math.ceil((totalConvos || 1) / pageSize), - pageNumber, - pageSize, + nextCursor, + hasNextPage, }; } catch (error) { - logger.error('[getShareByPage] Error getting shares', { + logger.error('[getSharedLinks] Error getting shares', { error: error.message, user, }); diff --git a/api/server/routes/share.js b/api/server/routes/share.js index f2bbc4ec108..8cbcf0ee86f 100644 --- a/api/server/routes/share.js +++ b/api/server/routes/share.js @@ -47,7 +47,7 @@ if (allowSharedLinks) { router.get('/', requireJwtAuth, async (req, res) => { try { const params = { - pageNumber: Math.max(1, parseInt(req.query.pageNumber) || 1), + pageParam: req.query.cursor, pageSize: Math.max(1, parseInt(req.query.pageSize) || 10), isPublic: isEnabled(req.query.isPublic), sortBy: ['createdAt', 'title'].includes(req.query.sortBy) ? req.query.sortBy : 'createdAt', @@ -59,7 +59,7 @@ router.get('/', requireJwtAuth, async (req, res) => { const result = await getSharedLinks( req.user.id, - params.pageNumber, + params.pageParam, params.pageSize, params.isPublic, params.sortBy, @@ -68,11 +68,9 @@ router.get('/', requireJwtAuth, async (req, res) => { ); res.status(200).send({ - links: result.sharedLinks, - totalCount: result.totalCount, - pageNumber: result.pageNumber, - pageSize: result.pageSize, - pages: result.pages, + links: result.links, + nextCursor: result.nextCursor, + hasNextPage: result.hasNextPage, }); } catch (error) { console.error('Error getting shared links:', error); diff --git a/client/src/components/Nav/SettingsTabs/Data/SharedLinks.tsx b/client/src/components/Nav/SettingsTabs/Data/SharedLinks.tsx index 42ff0a08b28..fc37de16ae4 100644 --- a/client/src/components/Nav/SettingsTabs/Data/SharedLinks.tsx +++ b/client/src/components/Nav/SettingsTabs/Data/SharedLinks.tsx @@ -1,7 +1,7 @@ -import { useCallback, useState } from 'react'; +import { useCallback, useState, useMemo } from 'react'; import { Link } from 'react-router-dom'; import { TrashIcon } from 'lucide-react'; -import type { SharedLinkItem, SharedLinkListParams } from 'librechat-data-provider'; +import type { SharedLinkItem, SharedLinksListParams } from 'librechat-data-provider'; import { useDeleteSharedLinkMutation, useSharedLinksQuery } from '~/data-provider'; import { OGDialog, OGDialogTrigger, Checkbox, Button } from '~/components/ui'; import OGDialogTemplate from '~/components/ui/OGDialogTemplate'; @@ -11,75 +11,42 @@ import { NotificationSeverity } from '~/common'; import { useToastContext } from '~/Providers'; import { formatDate } from '~/utils'; -interface PaginatedResponse { - data: T[]; - total: number; - totalPages: number; - totalItems: number; +interface TableRow extends SharedLinkItem { + id?: string; } +const PAGE_SIZE = 25; + +const DEFAULT_PARAMS: SharedLinksListParams = { + pageSize: PAGE_SIZE, + isPublic: true, + sortBy: 'createdAt', + sortDirection: 'desc', + search: '', +}; + export default function SharedLinks() { + const [isOpen, setIsOpen] = useState(false); const localize = useLocalize(); const isSmallScreen = useMediaQuery('(max-width: 768px)'); const { showToast } = useToastContext(); - const [queryParams, setQueryParams] = useState({ - pageNumber: 1, - pageSize: 10, - isPublic: true, - sortBy: 'createdAt', - sortDirection: 'asc', - }); - - const query = useSharedLinksQuery(queryParams, { - enabled: false, - }); - - const fetchData = useCallback( - async ({ - page, - filterValue, - sortBy, - sortDirection, - }: { - page: number; - filterValue?: string; - sortBy?: string; - sortDirection?: 'asc' | 'desc'; - }): Promise> => { - const newParams: SharedLinkListParams = { - pageNumber: page + 1, - pageSize: 10, - isPublic: true, - sortBy: (sortBy === 'title' ? 'title' : 'createdAt') as 'createdAt' | 'title', - sortDirection: sortDirection ?? 'asc', - search: filterValue, - }; - setQueryParams(newParams); + const { data, fetchNextPage, hasNextPage, isFetchingNextPage, refetch, isLoading } = + useSharedLinksQuery(DEFAULT_PARAMS, { + enabled: isOpen, + }); - const result = await query.refetch(); + const allLinks = useMemo(() => { + if (!data?.pages) { + return []; + } + return data.pages.flatMap((page) => page.links); + }, [data?.pages]); - if (!result.data?.pages || result.data.pages.length === 0) { - return { - data: [], - total: 0, - totalPages: 0, - totalItems: 0, - }; - } - - const data = result.data.pages[0]; - return { - data: data.links.map((link) => ({ id: link.shareId, ...link })), - total: data.totalCount, - totalPages: data.pages, - totalItems: data.totalCount, - }; + const deleteMutation = useDeleteSharedLinkMutation({ + onSuccess: () => { + refetch(); }, - [], - ); - - const mutation = useDeleteSharedLinkMutation({ onError: () => { showToast({ message: localize('com_ui_share_delete_error'), @@ -89,98 +56,112 @@ export default function SharedLinks() { }); const handleDelete = useCallback( - async (selectedRows: Array<{ id?: string }>) => { + async (selectedRows: TableRow[]) => { + const validRows = selectedRows.filter( + (row) => typeof row.id === 'string' && row.id.length > 0, + ); + try { - for (const row of selectedRows) { - if (typeof row.id === 'string' && row.id.length > 0) { - await mutation.mutateAsync({ shareId: row.id }); - } - } + await Promise.all( + validRows.map((row) => deleteMutation.mutateAsync({ shareId: row.id as string })), + ); } catch (error) { - console.error('Failed to delete shared link:', error); + console.error('Failed to delete shared links:', error); + showToast({ + message: localize('com_ui_bulk_delete_error'), + severity: NotificationSeverity.ERROR, + }); } }, - [mutation], + [deleteMutation, showToast, localize], ); - const columns = [ - { - id: 'select', - header: ({ table }) => ( -
- table.toggleAllPageRowsSelected(Boolean(value))} - /> -
- ), - cell: ({ row }) => ( -
- row.toggleSelected(Boolean(value))} - /> -
- ), - meta: { - size: '30px', + const handleFetchNextPage = useCallback(async () => { + if (!hasNextPage || isFetchingNextPage) { + console.warn('No more pages to fetch'); + return; + } + + await fetchNextPage(); + }, [fetchNextPage, hasNextPage, isFetchingNextPage]); + + const columns = useMemo( + () => [ + { + id: 'select', + header: ({ table }) => ( +
+ table.toggleAllPageRowsSelected(Boolean(value))} + /> +
+ ), + cell: ({ row }) => ( +
+ row.toggleSelected(Boolean(value))} + /> +
+ ), + meta: { size: '50px' }, }, - }, - { - accessorKey: 'title', - header: 'Name', - cell: ({ row }) => ( - - {row.original.title} - - ), - meta: { - size: '65%', - mobileSize: '70%', + { + accessorKey: 'title', + header: 'Name', + cell: ({ row }) => ( + + {row.original.title} + + ), + meta: { + size: '65%', + mobileSize: '70%', + }, }, - }, - { - accessorKey: 'createdAt', - header: 'Date', - cell: ({ row }) => formatDate(row.original.createdAt?.toString() ?? '', isSmallScreen), - meta: { - size: '20%', - mobileSize: '15%', + { + accessorKey: 'createdAt', + header: 'Date', + cell: ({ row }) => formatDate(row.original.createdAt?.toString() ?? '', isSmallScreen), + meta: { + size: '20%', + mobileSize: '15%', + }, }, - }, - { - accessorKey: 'actions', - header: 'Actions', - meta: { - size: '15%', - mobileSize: '15%', + { + accessorKey: 'actions', + header: 'Actions', + meta: { + size: '15%', + mobileSize: '15%', + }, + cell: ({ row }) => ( + + ), }, - cell: ({ row }) => ( - - ), - }, - ]; + ], + [isSmallScreen, handleDelete, localize], + ); return (
{localize('com_nav_shared_links')}
- - query.refetch()}> + + setIsOpen(true)}> @@ -192,9 +173,13 @@ export default function SharedLinks() { main={ } /> diff --git a/client/src/components/ui/DataTable.tsx b/client/src/components/ui/DataTable.tsx index eee2a936471..8648085cb9a 100644 --- a/client/src/components/ui/DataTable.tsx +++ b/client/src/components/ui/DataTable.tsx @@ -1,4 +1,4 @@ -import React, { useState, useEffect } from 'react'; +import React, { useState, useCallback, useEffect, useRef } from 'react'; import { ListFilter } from 'lucide-react'; import { flexRender, @@ -6,12 +6,10 @@ import { getFilteredRowModel, getSortedRowModel, useReactTable, -} from '@tanstack/react-table'; -import type { ColumnDef, SortingState, - VisibilityState, ColumnFiltersState, + VisibilityState, } from '@tanstack/react-table'; import { Button, @@ -27,12 +25,11 @@ import { DropdownMenuContent, DropdownMenuTrigger, } from './'; -import useLocalize from '~/hooks/useLocalize'; import { TrashIcon, Spinner } from '~/components/svg'; -import { useMediaQuery } from '~/hooks'; +import { useLocalize, useMediaQuery } from '~/hooks'; import { cn } from '~/utils'; -type AugmentedColumnDef = ColumnDef & { +type TableColumn = ColumnDef & { meta?: { size?: string | number; mobileSize?: string | number; @@ -40,153 +37,151 @@ type AugmentedColumnDef = ColumnDef & { }; }; -interface PaginatedResponse { - data: TData[]; - totalPages: number; - totalItems: number; -} - interface DataTableProps { - columns: ColumnDef[]; - fetchData: (options: { - page: number; - filterValue?: string; - sortBy?: string; - sortDirection?: 'asc' | 'desc'; - }) => Promise>; + columns: TableColumn[]; + data: TData[]; onDelete?: (selectedRows: TData[]) => Promise; filterColumn?: string; defaultSort?: SortingState; columnVisibilityMap?: Record; className?: string; pageSize?: number; + isFetchingNextPage?: boolean; + hasNextPage?: boolean; + fetchNextPage?: (options?: unknown) => Promise; +} + +function getColumnStyle( + column: TableColumn, + isSmallScreen: boolean, +): React.CSSProperties { + return { + width: isSmallScreen ? column.meta?.mobileSize : column.meta?.size, + minWidth: column.meta?.minWidth, + maxWidth: column.meta?.size, + }; } export default function DataTable({ columns, - fetchData, + data, onDelete, filterColumn, defaultSort = [], columnVisibilityMap = {}, className = '', - pageSize = 10, + isFetchingNextPage = false, + hasNextPage = false, + fetchNextPage, }: DataTableProps) { const localize = useLocalize(); - const [isDeleting, setIsDeleting] = useState(false); - const [isLoading, setIsLoading] = useState(false); - const [rowSelection, setRowSelection] = useState({}); - const [sorting, setSorting] = useState(defaultSort); const isSmallScreen = useMediaQuery('(max-width: 768px)'); - const [columnFilters, setColumnFilters] = useState([]); - const [columnVisibility, setColumnVisibility] = useState({}); + const scrollRef = useRef(null); - const [data, setData] = useState([]); - const [totalPages, setTotalPages] = useState(0); - const [totalItems, setTotalItems] = useState(0); - const [currentPage, setCurrentPage] = useState(0); + const [tableState, setTableState] = useState({ + isDeleting: false, + rowSelection: {}, + sorting: defaultSort as SortingState, + columnFilters: [] as ColumnFiltersState, + columnVisibility: {} as VisibilityState, + }); const table = useReactTable({ data, columns, - pageCount: totalPages, - onSortingChange: setSorting, getCoreRowModel: getCoreRowModel(), getSortedRowModel: getSortedRowModel(), - onColumnFiltersChange: setColumnFilters, getFilteredRowModel: getFilteredRowModel(), - onColumnVisibilityChange: setColumnVisibility, - manualPagination: true, - onRowSelectionChange: setRowSelection, state: { - sorting, - columnFilters, - columnVisibility, - rowSelection, - pagination: { - pageIndex: currentPage, - pageSize, - }, + sorting: tableState.sorting, + columnFilters: tableState.columnFilters, + columnVisibility: tableState.columnVisibility, + rowSelection: tableState.rowSelection, }, + onSortingChange: (sorting) => + setTableState((prev) => ({ ...prev, sorting: sorting as SortingState })), + onColumnFiltersChange: (filters) => + setTableState((prev) => ({ ...prev, columnFilters: filters as ColumnFiltersState })), + onColumnVisibilityChange: (visibility) => + setTableState((prev) => ({ ...prev, columnVisibility: visibility as VisibilityState })), + onRowSelectionChange: (selection) => + setTableState((prev) => ({ ...prev, rowSelection: selection })), }); useEffect(() => { - const loadData = async () => { - setIsLoading(true); - try { - const sortColumn = sorting[0]; - const filterValue = - filterColumn && table.getColumn(filterColumn) - ? (table.getColumn(filterColumn)?.getFilterValue() as string) - : undefined; + const div = scrollRef.current; + if (!div) { + return; + } - const response = await fetchData({ - page: currentPage, - filterValue, - sortBy: sortColumn.id, - sortDirection: sortColumn.desc ? 'desc' : 'asc', - }); + const onScroll = () => { + if (!hasNextPage || isFetchingNextPage) { + return; + } - setData(response.data); - setTotalPages(response.totalPages); - setTotalItems(response.totalItems); - } catch (error) { - console.error('Error fetching data:', error); - } finally { - setIsLoading(false); + const bottom = div.scrollHeight - div.scrollTop <= div.clientHeight * 1.5; + if (bottom) { + fetchNextPage?.(); } }; - loadData(); - }, [currentPage, sorting, columnFilters, filterColumn, table, fetchData]); + div.addEventListener('scroll', onScroll); + return () => div.removeEventListener('scroll', onScroll); + }, [hasNextPage, isFetchingNextPage, fetchNextPage]); - const handleDelete = async () => { + const handleDelete = useCallback(async () => { if (!onDelete) { return; } - setIsDeleting(true); + setTableState((prev) => ({ ...prev, isDeleting: true })); const itemsToDelete = table.getFilteredSelectedRowModel().rows.map((row) => row.original); - await onDelete(itemsToDelete); - setIsDeleting(false); - setRowSelection({}); - const response = await fetchData({ page: currentPage }); - setData(response.data); - setTotalPages(response.totalPages); - setTotalItems(response.totalItems); - }; + try { + await onDelete(itemsToDelete); + } finally { + setTableState((prev) => ({ ...prev, isDeleting: false, rowSelection: {} })); + } + }, [onDelete, table]); + + const selectedRowsCount = table.getFilteredSelectedRowModel().rows.length; + + const DeleteButton = useCallback(() => { + if (!onDelete) { + return null; + } + + return ( + + ); + }, [onDelete, handleDelete, selectedRowsCount, tableState.isDeleting, isSmallScreen, localize]); return (
- {onDelete && ( - - )} - + {filterColumn && table.getColumn(filterColumn) && ( table.getColumn(filterColumn)?.setFilterValue(event.target.value)} className="flex-1 text-sm" /> )} -
-
- {isLoading && ( -
- -
+
{table.getHeaderGroups().map((headerGroup) => ( - {headerGroup.headers.map((header) => { - const columnDef = header.column.columnDef as AugmentedColumnDef; - const style: React.CSSProperties = { - width: isSmallScreen ? columnDef.meta?.mobileSize : columnDef.meta?.size, - minWidth: columnDef.meta?.minWidth, - }; - - return ( - - {header.isPlaceholder - ? null - : flexRender(header.column.columnDef.header, header.getContext())} - - ); - })} + {headerGroup.headers.map((header) => ( + , + isSmallScreen, + )} + > + {header.isPlaceholder + ? null + : flexRender(header.column.columnDef.header, header.getContext())} + + ))} ))} - {data.length ? ( - table.getRowModel().rows.map((row) => ( - - {row.getVisibleCells().map((cell) => { - const columnDef = cell.column.columnDef as AugmentedColumnDef; - const style: React.CSSProperties = { - maxWidth: columnDef.meta?.size, - }; + {table.getRowModel().rows.map((row) => ( + + {row.getVisibleCells().map((cell) => ( + , + isSmallScreen, + )} + > + {flexRender(cell.column.columnDef.cell, cell.getContext())} + + ))} + + ))} - return ( - - {flexRender(cell.column.columnDef.cell, cell.getContext())} - - ); - })} - - )) - ) : ( - - - {localize('com_files_no_results')} + {/* Infinite scroll loading indicator */} + {(isFetchingNextPage || hasNextPage) && ( + + +
+ {isFetchingNextPage ? ( + + ) : ( + hasNextPage &&
+ )} +
)}
- -
-
- - {localize( - 'com_files_number_selected', - `${table.getFilteredSelectedRowModel().rows.length}`, - `${totalItems}`, - )} - - - {`${table.getFilteredSelectedRowModel().rows.length}/${totalItems}`} - -
-
- {localize('com_ui_page')} - {currentPage + 1} - / - {totalPages} -
- - -
); } diff --git a/client/src/components/ui/PageScroller.tsx b/client/src/components/ui/PageScroller.tsx deleted file mode 100644 index 88dd9b51a71..00000000000 --- a/client/src/components/ui/PageScroller.tsx +++ /dev/null @@ -1,128 +0,0 @@ -import React, { useMemo, useCallback, memo } from 'react'; -import { ChevronLeft, ChevronRight, ChevronsLeft, ChevronsRight } from 'lucide-react'; -import { Button } from './Button'; - -interface PageButtonProps { - onClick: () => void; - disabled: boolean; - ariaLabel: string; - children: React.ReactNode; -} - -const PageButton = memo(({ onClick, disabled, ariaLabel, children }) => ( - -)); - -PageButton.displayName = 'PageButton'; - -interface PageScrollerProps { - currentPage?: number; - totalPages?: number; - onPageChange: (page: number) => void; - showSkipButtons?: boolean; - className?: string; - disabled?: boolean; - ariaLabel?: string; - pageSize?: number; -} - -const PageScroller: React.FC = ({ - currentPage = 1, - totalPages = 1, - onPageChange, - showSkipButtons = false, - className = '', - disabled = false, - ariaLabel = 'Page navigation', - pageSize = 10, -}) => { - const sanitizedCurrentPage = useMemo( - () => Math.max(1, Math.min(Math.floor(currentPage), totalPages)), - [currentPage, totalPages], - ); - const sanitizedTotalPages = useMemo(() => Math.max(1, Math.floor(totalPages)), [totalPages]); - - const handlePageChange = useCallback( - (newPage: number) => { - if ( - !disabled && - newPage >= 1 && - newPage <= sanitizedTotalPages && - newPage !== sanitizedCurrentPage - ) { - onPageChange(newPage); - } - }, - [disabled, sanitizedTotalPages, sanitizedCurrentPage, onPageChange], - ); - - return ( - - ); -}; - -export default memo(PageScroller); diff --git a/client/src/components/ui/index.ts b/client/src/components/ui/index.ts index 7e05c7dc078..e97efd6b3ad 100644 --- a/client/src/components/ui/index.ts +++ b/client/src/components/ui/index.ts @@ -27,7 +27,6 @@ export * from './Pagination'; export { default as Combobox } from './Combobox'; export { default as Dropdown } from './Dropdown'; export { default as FileUpload } from './FileUpload'; -export { default as PageScroller } from './PageScroller'; export { default as DropdownPopup } from './DropdownPopup'; export { default as DelayedRender } from './DelayedRender'; export { default as ThemeSelector } from './ThemeSelector'; diff --git a/client/src/data-provider/queries.ts b/client/src/data-provider/queries.ts index 409b17baada..ceb8e46cd93 100644 --- a/client/src/data-provider/queries.ts +++ b/client/src/data-provider/queries.ts @@ -24,7 +24,7 @@ import type { AssistantDocument, TEndpointsConfig, TCheckUserKeyResponse, - SharedLinkListParams, + SharedLinksListParams, SharedLinksResponse, } from 'librechat-data-provider'; import { findPageForConversation } from '~/utils'; @@ -140,28 +140,29 @@ export const useConversationsInfiniteQuery = ( }; export const useSharedLinksQuery = ( - params?: SharedLinkListParams, + params: SharedLinksListParams, config?: UseInfiniteQueryOptions, ) => { - return useInfiniteQuery( - [QueryKeys.sharedLinks], - () => + const { pageSize, isPublic, search, sortBy, sortDirection } = params; + + return useInfiniteQuery({ + queryKey: [QueryKeys.sharedLinks, { pageSize, isPublic, search, sortBy, sortDirection }], + queryFn: ({ pageParam }) => dataService.listSharedLinks({ - ...params, - pageNumber: params?.pageNumber ?? 10, - pageSize: params?.pageSize ?? 10, - isPublic: params?.isPublic ?? true, - search: params?.search ?? '', - sortBy: params?.sortBy ?? 'createdAt', - sortDirection: params?.sortDirection ?? 'desc', + cursor: pageParam?.toString(), + pageSize, + isPublic, + search, + sortBy, + sortDirection, }), - { - refetchOnWindowFocus: false, - refetchOnReconnect: false, - refetchOnMount: false, - ...config, - }, - ); + getNextPageParam: (lastPage) => lastPage.nextCursor, + + keepPreviousData: true, + staleTime: 5 * 60 * 1000, // 5 minutes + cacheTime: 30 * 60 * 1000, // 30 minutes + ...config, + }); }; export const useConversationTagsQuery = ( diff --git a/packages/data-provider/src/api-endpoints.ts b/packages/data-provider/src/api-endpoints.ts index 6527f2efb9c..bf6b780bdc2 100644 --- a/packages/data-provider/src/api-endpoints.ts +++ b/packages/data-provider/src/api-endpoints.ts @@ -16,16 +16,16 @@ const shareRoot = '/api/share'; export const shareMessages = (shareId: string) => `${shareRoot}/${shareId}`; export const getSharedLink = (conversationId: string) => `${shareRoot}/link/${conversationId}`; export const getSharedLinks = ( - pageNumber: number, pageSize: number, isPublic: boolean, sortBy: 'title' | 'createdAt', sortDirection: 'asc' | 'desc', search?: string, + cursor?: string, ) => - `${shareRoot}?pageNumber=${pageNumber}&pageSize=${pageSize}&isPublic=${isPublic}&sortBy=${sortBy}&sortDirection=${sortDirection}${ + `${shareRoot}?pageSize=${pageSize}&isPublic=${isPublic}&sortBy=${sortBy}&sortDirection=${sortDirection}${ search ? `&search=${search}` : '' - }`; + }${cursor ? `&cursor=${cursor}` : ''}`; export const createSharedLink = (conversationId: string) => `${shareRoot}/${conversationId}`; export const updateSharedLink = (shareId: string) => `${shareRoot}/${shareId}`; diff --git a/packages/data-provider/src/data-service.ts b/packages/data-provider/src/data-service.ts index 85f8bd7ea52..026606f4c57 100644 --- a/packages/data-provider/src/data-service.ts +++ b/packages/data-provider/src/data-service.ts @@ -41,17 +41,13 @@ export function getSharedMessages(shareId: string): Promise => { - const pageNumber = params?.pageNumber ?? 1; - const pageSize = params?.pageSize ?? 10; - const isPublic = params?.isPublic ?? true; - const sortBy = params?.sortBy ?? 'createdAt'; - const sortDirection = params?.sortDirection ?? 'desc'; - const search = params?.search; + const { pageSize, isPublic, sortBy, sortDirection, search, cursor } = params; + return request.get( - endpoints.getSharedLinks(pageNumber, pageSize, isPublic, sortBy, sortDirection, search), + endpoints.getSharedLinks(pageSize, isPublic, sortBy, sortDirection, search, cursor), ); }; diff --git a/packages/data-provider/src/types/queries.ts b/packages/data-provider/src/types/queries.ts index 83f94cc5e05..4a4f3eeaf22 100644 --- a/packages/data-provider/src/types/queries.ts +++ b/packages/data-provider/src/types/queries.ts @@ -42,14 +42,14 @@ export type SharedMessagesResponse = Omit & { messages: s.TMessage[]; }; -export type SharedLinkListParams = { - pageNumber: number; +export interface SharedLinksListParams { pageSize: number; isPublic: boolean; - sortBy: 'createdAt' | 'title'; + sortBy: 'title' | 'createdAt'; sortDirection: 'asc' | 'desc'; search?: string; -}; + cursor?: string; +} export type SharedLinkItem = { shareId: string; @@ -58,13 +58,11 @@ export type SharedLinkItem = { createdAt: Date; }; -export type SharedLinksResponse = { +export interface SharedLinksResponse { links: SharedLinkItem[]; - totalCount: number; - pages: number; - pageNumber: number; - pageSize: number; -}; + nextCursor: string | null; + hasNextPage: boolean; +} // Type for the response from the conversation list API export type SharedLinkListResponse = {