feat: switch to cursor pagination (#1884)

* add cursor pagination function

* support custom order modifier
* refactor returned object

* feat(db): migrate paginated endpoints to cursor-based pagination

* sync

* support hasPrevPage boolean

* feat(client): migrate pagination from offset to cursor-based

* support beforeCursor/prevCursor

* wrap search results in items array for API consistency
This commit is contained in:
Philip Okugbe
2026-01-30 19:28:54 +00:00
committed by GitHub
parent 96ed98619f
commit 78b1c1a453
49 changed files with 792 additions and 341 deletions
@@ -18,7 +18,6 @@ import {
IconFileDescription,
IconSearch,
IconCheck,
IconSparkles,
} from "@tabler/icons-react";
import { useTranslation } from "react-i18next";
import { useDebouncedValue } from "@mantine/hooks";
@@ -26,7 +25,7 @@ import { useGetSpacesQuery } from "@/features/space/queries/space-query";
import { useLicense } from "@/ee/hooks/use-license";
import classes from "./search-spotlight-filters.module.css";
import { isCloud } from "@/lib/config.ts";
import { useAtom } from "jotai/index";
import { useAtom } from "jotai";
import { workspaceAtom } from "@/features/user/atoms/current-user-atom.ts";
interface SearchSpotlightFiltersProps {
@@ -53,7 +52,6 @@ export function SearchSpotlightFilters({
const [workspace] = useAtom(workspaceAtom);
const { data: spacesData } = useGetSpacesQuery({
page: 1,
limit: 100,
query: debouncedSpaceQuery,
});
@@ -265,7 +263,9 @@ export function SearchSpotlightFilters({
contentType !== option.value &&
handleFilterChange("contentType", option.value)
}
disabled={option.disabled || (isAiMode && option.value === "attachment")}
disabled={
option.disabled || (isAiMode && option.value === "attachment")
}
>
<Group flex="1" gap="xs">
<div>
@@ -275,11 +275,13 @@ export function SearchSpotlightFilters({
{t("Enterprise")}
</Badge>
)}
{!option.disabled && isAiMode && option.value === "attachment" && (
<Text size="xs" mt={4}>
{t("Ask AI not available for attachments")}
</Text>
)}
{!option.disabled &&
isAiMode &&
option.value === "attachment" && (
<Text size="xs" mt={4}>
{t("Ask AI not available for attachments")}
</Text>
)}
</div>
{contentType === option.value && <IconCheck size={20} />}
</Group>
@@ -10,8 +10,8 @@ import {
export async function searchPage(
params: IPageSearchParams,
): Promise<IPageSearch[]> {
const req = await api.post<IPageSearch[]>("/search", params);
return req.data;
const req = await api.post<{ items: IPageSearch[] }>("/search", params);
return req.data.items;
}
export async function searchSuggestions(
@@ -24,13 +24,13 @@ export async function searchSuggestions(
export async function searchShare(
params: IPageSearchParams,
): Promise<IPageSearch[]> {
const req = await api.post<IPageSearch[]>("/search/share-search", params);
return req.data;
const req = await api.post<{ items: IPageSearch[] }>("/search/share-search", params);
return req.data.items;
}
export async function searchAttachments(
params: IPageSearchParams,
): Promise<IAttachmentSearch[]> {
const req = await api.post<IAttachmentSearch[]>("/search-attachments", params);
return req.data;
const req = await api.post<{ items: IAttachmentSearch[] }>("/search-attachments", params);
return req.data.items;
}