diff --git a/src/app/[orgId]/loading.tsx b/src/app/[orgId]/loading.tsx new file mode 100644 index 000000000..0f7ea0d31 --- /dev/null +++ b/src/app/[orgId]/loading.tsx @@ -0,0 +1,9 @@ +import { Loader2 } from "lucide-react"; + +export default function OrgPageLoading() { + return ( +
+ +
+ ); +} diff --git a/src/app/[orgId]/page.tsx b/src/app/[orgId]/page.tsx index 0c3ad6547..b76b77b7b 100644 --- a/src/app/[orgId]/page.tsx +++ b/src/app/[orgId]/page.tsx @@ -2,8 +2,8 @@ import { Layout } from "@app/components/Layout"; import ResourceLauncher from "@app/components/resource-launcher/ResourceLauncher"; import { internal } from "@app/lib/api"; import { authCookieHeader } from "@app/lib/api/cookies"; +import { fetchLauncherPageData } from "@app/lib/launcherServerData"; import { verifySession } from "@app/lib/auth/verifySession"; -import { pullEnv } from "@app/lib/pullEnv"; import UserProvider from "@app/providers/UserProvider"; import { ListUserOrgsResponse } from "@server/routers/org"; import { GetOrgOverviewResponse } from "@server/routers/org/getOrgOverview"; @@ -13,8 +13,11 @@ import { cache } from "react"; type OrgPageProps = { params: Promise<{ orgId: string }>; + searchParams: Promise>; }; +export const dynamic = "force-dynamic"; + export default async function OrgPage(props: OrgPageProps) { const params = await props.params; const orgId = params.orgId; @@ -55,6 +58,15 @@ export default async function OrgPage(props: OrgPageProps) { const isAdminOrOwner = Boolean(overview?.isAdmin || overview?.isOwner); + const searchParams = new URLSearchParams(await props.searchParams); + const launcherData = overview + ? await fetchLauncherPageData( + orgId, + searchParams, + await authCookieHeader() + ) + : null; + return ( - {overview ? ( - + {overview && launcherData ? ( + ) : null} diff --git a/src/components/resource-launcher/LauncherGroupList.tsx b/src/components/resource-launcher/LauncherGroupList.tsx index 854241cdd..c90c3a7cc 100644 --- a/src/components/resource-launcher/LauncherGroupList.tsx +++ b/src/components/resource-launcher/LauncherGroupList.tsx @@ -1,159 +1,80 @@ "use client"; import type { LauncherActiveViewId } from "@app/lib/launcherLocalStorage"; -import { readLauncherGroupOpen } from "@app/lib/launcherLocalStorage"; +import type { LauncherGroupResources } from "@app/lib/launcherServerData"; import { launcherQueries } from "@app/lib/queries"; -import type { LauncherViewConfig } from "@server/routers/launcher/types"; -import { useInfiniteQuery, useQueryClient } from "@tanstack/react-query"; +import type { + LauncherGroup, + LauncherViewConfig +} from "@server/routers/launcher/types"; +import { useInfiniteQuery } from "@tanstack/react-query"; import { Loader2 } from "lucide-react"; -import { useEffect, useMemo, useRef, useState } from "react"; +import { useEffect, useMemo, useRef } from "react"; import { LauncherGroupSection } from "./LauncherGroupSection"; type LauncherGroupListProps = { orgId: string; activeViewId: LauncherActiveViewId; config: LauncherViewConfig; - searchQuery: string; -}; - -function buildResourceFilters( - config: LauncherViewConfig, - searchQuery: string, - groupKey: string -) { - return { - query: searchQuery, - groupBy: config.groupBy, - groupKey, - siteIds: config.siteIds, - labelIds: config.labelIds, - sort_by: config.sortBy, - order: config.order, - pageSize: 20 + initialGroups: LauncherGroup[]; + groupsPagination: { + total: number; + page: number; + pageSize: number; }; -} + resourcesByGroupKey: Record; +}; export function LauncherGroupList({ orgId, activeViewId, config, - searchQuery + initialGroups, + groupsPagination, + resourcesByGroupKey }: LauncherGroupListProps) { - const queryClient = useQueryClient(); const loadMoreRef = useRef(null); - const [isPrefetching, setIsPrefetching] = useState(false); - const prefetchBatchKeyRef = useRef(null); - const { data, fetchNextPage, hasNextPage, isFetchingNextPage, isLoading } = + const groupFilters = useMemo( + () => ({ + query: config.query, + groupBy: config.groupBy, + siteIds: config.siteIds, + labelIds: config.labelIds, + sort_by: config.sortBy, + order: config.order, + pageSize: 20 + }), + [ + config.groupBy, + config.labelIds, + config.order, + config.query, + config.siteIds, + config.sortBy + ] + ); + + const { data, fetchNextPage, hasNextPage, isFetchingNextPage } = useInfiniteQuery({ - ...launcherQueries.groups(orgId, { - query: searchQuery, - groupBy: config.groupBy, - siteIds: config.siteIds, - labelIds: config.labelIds, - sort_by: config.sortBy, - order: config.order, - pageSize: 20 - }) + ...launcherQueries.groups(orgId, groupFilters), + initialData: { + pages: [ + { + groups: initialGroups, + pagination: groupsPagination + } + ], + pageParams: [1] + }, + refetchOnMount: false }); const groups = data?.pages.flatMap((page) => page.groups) ?? []; - const batchKey = useMemo( - () => - JSON.stringify({ - activeViewId, - searchQuery, - groupBy: config.groupBy, - siteIds: config.siteIds, - labelIds: config.labelIds, - sortBy: config.sortBy, - order: config.order - }), - [ - activeViewId, - config.groupBy, - config.labelIds, - config.order, - config.siteIds, - config.sortBy, - searchQuery - ] - ); - - const openGroupKeys = useMemo( - () => - groups - .filter((group) => - readLauncherGroupOpen( - orgId, - activeViewId, - config.groupBy, - group.groupKey, - true - ) - ) - .map((group) => group.groupKey), - [activeViewId, config.groupBy, groups, orgId] - ); - - useEffect(() => { - if (isLoading) { - return; - } - - if (openGroupKeys.length === 0) { - prefetchBatchKeyRef.current = batchKey; - setIsPrefetching(false); - return; - } - - if (prefetchBatchKeyRef.current === batchKey) { - return; - } - - let cancelled = false; - setIsPrefetching(true); - - void Promise.all( - openGroupKeys.map((groupKey) => - queryClient.prefetchInfiniteQuery( - launcherQueries.resources( - orgId, - buildResourceFilters(config, searchQuery, groupKey) - ) - ) - ) - ).finally(() => { - if (!cancelled) { - prefetchBatchKeyRef.current = batchKey; - setIsPrefetching(false); - } - }); - - return () => { - cancelled = true; - }; - }, [ - batchKey, - config, - isLoading, - openGroupKeys, - orgId, - queryClient, - searchQuery - ]); - - const isBatchPending = prefetchBatchKeyRef.current !== batchKey; - const isBodyLoading = - isLoading || - (isBatchPending && - openGroupKeys.length > 0 && - (isPrefetching || !isLoading)); - useEffect(() => { const node = loadMoreRef.current; - if (!node || !hasNextPage || isBodyLoading) { + if (!node || !hasNextPage) { return; } @@ -168,28 +89,25 @@ export function LauncherGroupList({ observer.observe(node); return () => observer.disconnect(); - }, [fetchNextPage, hasNextPage, isBodyLoading, isFetchingNextPage]); - - if (isBodyLoading) { - return ( -
- -
- ); - } + }, [fetchNextPage, hasNextPage, isFetchingNextPage]); return (
- {groups.map((group) => ( - - ))} + {groups.map((group) => { + const groupResources = resourcesByGroupKey[group.groupKey]; + + return ( + + ); + })}
{isFetchingNextPage ? (
diff --git a/src/components/resource-launcher/LauncherGroupSection.tsx b/src/components/resource-launcher/LauncherGroupSection.tsx index 0f174bd86..8c32d9074 100644 --- a/src/components/resource-launcher/LauncherGroupSection.tsx +++ b/src/components/resource-launcher/LauncherGroupSection.tsx @@ -13,6 +13,7 @@ import { import { launcherQueries } from "@app/lib/queries"; import type { LauncherGroup, + LauncherResource, LauncherViewConfig } from "@server/routers/launcher/types"; import { useInfiniteQuery } from "@tanstack/react-query"; @@ -28,7 +29,12 @@ type LauncherGroupSectionProps = { activeViewId: LauncherActiveViewId; group: LauncherGroup; config: LauncherViewConfig; - searchQuery: string; + initialResources?: LauncherResource[]; + initialResourcesPagination?: { + total: number; + page: number; + pageSize: number; + }; defaultOpen?: boolean; }; @@ -37,7 +43,8 @@ export function LauncherGroupSection({ activeViewId, group, config, - searchQuery, + initialResources, + initialResourcesPagination, defaultOpen = true }: LauncherGroupSectionProps) { const t = useTranslations(); @@ -75,10 +82,12 @@ export function LauncherGroupSection({ ); }; + const hasInitialResources = initialResources !== undefined; + const { data, fetchNextPage, hasNextPage, isFetchingNextPage, isLoading } = useInfiniteQuery({ ...launcherQueries.resources(orgId, { - query: searchQuery, + query: config.query, groupBy: config.groupBy, groupKey: group.groupKey, siteIds: config.siteIds, @@ -87,14 +96,33 @@ export function LauncherGroupSection({ order: config.order, pageSize: 20 }), - enabled: isOpen + enabled: isOpen, + refetchOnMount: false, + ...(hasInitialResources + ? { + initialData: { + pages: [ + { + resources: initialResources, + pagination: initialResourcesPagination ?? { + total: initialResources.length, + page: 1, + pageSize: 20 + } + } + ], + pageParams: [1] + } + } + : {}) }); const resources = data?.pages.flatMap((page) => page.resources) ?? []; + const showInitialLoader = isLoading && resources.length === 0; useEffect(() => { const node = loadMoreRef.current; - if (!node || !hasNextPage) { + if (!node || !hasNextPage || !isOpen) { return; } @@ -109,7 +137,7 @@ export function LauncherGroupSection({ observer.observe(node); return () => observer.disconnect(); - }, [fetchNextPage, hasNextPage, isFetchingNextPage]); + }, [fetchNextPage, hasNextPage, isFetchingNextPage, isOpen]); const groupTitle = group.groupKey === "unlabeled" @@ -129,7 +157,7 @@ export function LauncherGroupSection({ /> - {isLoading ? ( + {showInitialLoader ? (
diff --git a/src/components/resource-launcher/ResourceLauncher.tsx b/src/components/resource-launcher/ResourceLauncher.tsx index de73b7035..b09dea773 100644 --- a/src/components/resource-launcher/ResourceLauncher.tsx +++ b/src/components/resource-launcher/ResourceLauncher.tsx @@ -14,30 +14,31 @@ import { CheckboxWithLabel } from "@app/components/ui/checkbox"; import { Input } from "@app/components/ui/input"; import { Label } from "@app/components/ui/label"; import { createApiClient, formatAxiosError } from "@app/lib/api"; +import { useNavigationContext } from "@app/hooks/useNavigationContext"; import { readLauncherLastView, writeLauncherLastView, type LauncherActiveViewId } from "@app/lib/launcherLocalStorage"; +import type { LauncherGroupResources } from "@app/lib/launcherServerData"; import { buildLauncherPath, getLauncherUrlBaseConfig, isLauncherConfigEqual, - resolveLauncherStateFromUrl, + parseLauncherUrlState, serializeLauncherUrlState } from "@app/lib/launcherUrlState"; -import { launcherQueries } from "@app/lib/queries"; import { useToast } from "@app/hooks/useToast"; import { useEnvContext } from "@app/hooks/useEnvContext"; -import { - defaultLauncherViewConfig, - type LauncherViewConfig, - type LauncherViewRecord +import type { + LauncherGroup, + LauncherViewConfig, + LauncherViewRecord } from "@server/routers/launcher/types"; -import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; +import { useMutation } from "@tanstack/react-query"; import { Search } from "lucide-react"; import { useTranslations } from "next-intl"; -import { useRouter, useSearchParams } from "next/navigation"; +import { useRouter } from "next/navigation"; import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { useDebouncedCallback } from "use-debounce"; import type { Selectedsite } from "@app/components/site-selector"; @@ -52,33 +53,39 @@ import SettingsSectionTitle from "@app/components/SettingsSectionTitle"; type ResourceLauncherProps = { orgId: string; isAdmin: boolean; + views: LauncherViewRecord[]; + activeViewId: LauncherActiveViewId; + config: LauncherViewConfig; + savedConfig: LauncherViewConfig; + groups: LauncherGroup[]; + groupsPagination: { + total: number; + page: number; + pageSize: number; + }; + resourcesByGroupKey: Record; }; export default function ResourceLauncher({ orgId, - isAdmin + isAdmin, + views, + activeViewId, + config, + savedConfig, + groups, + groupsPagination, + resourcesByGroupKey }: ResourceLauncherProps) { const t = useTranslations(); const { toast } = useToast(); const { env } = useEnvContext(); - const queryClient = useQueryClient(); const api = createApiClient({ env }); const router = useRouter(); - const searchParams = useSearchParams(); - - const [activeViewId, setActiveViewId] = - useState("default"); + const { navigate, isNavigating, searchParams } = useNavigationContext(); const hasRestoredLastView = useRef(false); - const isApplyingUrlRef = useRef(false); - const [config, setConfig] = useState( - defaultLauncherViewConfig - ); - const [savedConfig, setSavedConfig] = useState( - defaultLauncherViewConfig - ); - const [searchInput, setSearchInput] = useState(""); - const [searchQuery, setSearchQuery] = useState(""); + const [searchInput, setSearchInput] = useState(config.query); const [saveDialogOpen, setSaveDialogOpen] = useState(false); const [newViewName, setNewViewName] = useState(""); const [saveOrgWide, setSaveOrgWide] = useState(false); @@ -90,68 +97,66 @@ export default function ResourceLauncher({ const activeViewIdRef = useRef(activeViewId); activeViewIdRef.current = activeViewId; - const { data: views = [], isLoading: viewsLoading } = useQuery( - launcherQueries.views(orgId) - ); + useEffect(() => { + setSearchInput(config.query); + }, [config.query]); - const syncUrl = useCallback( + useEffect(() => { + if (hasRestoredLastView.current) { + return; + } + hasRestoredLastView.current = true; + + const parsed = parseLauncherUrlState(searchParams); + if (parsed.hasAnyLauncherParams) { + return; + } + + const lastView = readLauncherLastView(orgId); + if (lastView === null || lastView === activeViewId) { + return; + } + + const isValid = + lastView === "default" || + views.some((view) => view.viewId === lastView); + if (!isValid) { + return; + } + + const baseConfig = getLauncherUrlBaseConfig(lastView, views); + const params = serializeLauncherUrlState({ + viewId: lastView, + config: baseConfig + }); + navigate({ searchParams: params, replace: true }); + }, [activeViewId, navigate, orgId, searchParams, views]); + + const navigateToConfig = useCallback( (viewId: LauncherActiveViewId, nextConfig: LauncherViewConfig) => { - if (isApplyingUrlRef.current) { - return; - } - const params = serializeLauncherUrlState({ viewId, config: nextConfig }); - const path = buildLauncherPath(orgId, params); - router.replace(path, { scroll: false }); + navigate({ searchParams: params }); }, - [orgId, router] + [navigate] ); - const debouncedSyncSearch = useDebouncedCallback( + const debouncedNavigateSearch = useDebouncedCallback( (viewId: LauncherActiveViewId, query: string) => { - const nextConfig = { ...configRef.current, query }; - setSearchQuery(query); - syncUrl(viewId, nextConfig); + navigateToConfig(viewId, { ...configRef.current, query }); }, 300 ); - useEffect(() => { - if (viewsLoading) { - return; - } - - let fallbackViewId: LauncherActiveViewId | null = null; - if (!hasRestoredLastView.current) { - hasRestoredLastView.current = true; - fallbackViewId = readLauncherLastView(orgId); - } - - isApplyingUrlRef.current = true; - const resolved = resolveLauncherStateFromUrl( - new URLSearchParams(searchParams), - views, - fallbackViewId - ); - - setActiveViewId(resolved.activeViewId); - setConfig(resolved.config); - setSavedConfig(resolved.savedConfig); - setSearchInput(resolved.config.query); - setSearchQuery(resolved.config.query); - isApplyingUrlRef.current = false; - }, [orgId, searchParams, views, viewsLoading]); - const selectView = useCallback( (viewId: LauncherActiveViewId) => { writeLauncherLastView(orgId, viewId); const baseConfig = getLauncherUrlBaseConfig(viewId, views); - syncUrl(viewId, baseConfig); + navigateToConfig(viewId, baseConfig); }, - [orgId, syncUrl, views] + [navigateToConfig, orgId, views] ); const activeSavedView = useMemo( @@ -186,12 +191,6 @@ export default function ResourceLauncher({ [config.labelIds] ); - const invalidateLauncher = () => { - void queryClient.invalidateQueries({ - queryKey: ["ORG", orgId, "LAUNCHER"] - }); - }; - const createViewMutation = useMutation({ mutationFn: async (payload: { name: string; @@ -202,23 +201,13 @@ export default function ResourceLauncher({ return res.data.data as LauncherViewRecord; }, onSuccess: (view) => { - invalidateLauncher(); writeLauncherLastView(orgId, view.viewId); - - isApplyingUrlRef.current = true; - setActiveViewId(view.viewId); - setConfig(view.config); - setSavedConfig(view.config); - setSearchInput(view.config.query); - setSearchQuery(view.config.query); - isApplyingUrlRef.current = false; - const params = serializeLauncherUrlState({ viewId: view.viewId, config: view.config }); - router.replace(buildLauncherPath(orgId, params), { scroll: false }); - + navigate({ searchParams: params, replace: true }); + router.refresh(); setSaveDialogOpen(false); setNewViewName(""); toast({ @@ -253,22 +242,12 @@ export default function ResourceLauncher({ return res.data.data as LauncherViewRecord; }, onSuccess: (view) => { - invalidateLauncher(); - - isApplyingUrlRef.current = true; - setActiveViewId(view.viewId); - setConfig(view.config); - setSavedConfig(view.config); - setSearchInput(view.config.query); - setSearchQuery(view.config.query); - isApplyingUrlRef.current = false; - const params = serializeLauncherUrlState({ viewId: view.viewId, config: view.config }); - router.replace(buildLauncherPath(orgId, params), { scroll: false }); - + navigate({ searchParams: params, replace: true }); + router.refresh(); toast({ title: t("resourceLauncherViewSaved"), description: t("resourceLauncherViewSavedDescription") @@ -291,8 +270,13 @@ export default function ResourceLauncher({ await api.delete(`/org/${orgId}/launcher/views/${viewId}`); }, onSuccess: () => { - invalidateLauncher(); - selectView("default"); + writeLauncherLastView(orgId, "default"); + const params = serializeLauncherUrlState({ + viewId: "default", + config: getLauncherUrlBaseConfig("default", views) + }); + navigate({ searchParams: params, replace: true }); + router.refresh(); toast({ title: t("resourceLauncherViewDeleted"), description: t("resourceLauncherViewDeletedDescription") @@ -317,9 +301,9 @@ export default function ResourceLauncher({ ...patch, query: searchInputRef.current }; - syncUrl(activeViewIdRef.current, nextConfig); + navigateToConfig(activeViewIdRef.current, nextConfig); }, - [syncUrl] + [navigateToConfig] ); const handleSaveToCurrent = () => { @@ -370,7 +354,7 @@ export default function ResourceLauncher({ }; return ( -
+
{ const value = event.target.value; setSearchInput(value); - debouncedSyncSearch( + debouncedNavigateSearch( activeViewIdRef.current, value ); @@ -397,16 +381,14 @@ export default function ResourceLauncher({ className="pl-8" />
- {!viewsLoading ? ( - ({ - viewId: view.viewId, - name: view.name - }))} - onSelectView={selectView} - /> - ) : null} + ({ + viewId: view.viewId, + name: view.name + }))} + onSelectView={selectView} + />
diff --git a/src/lib/launcherSearchParams.ts b/src/lib/launcherSearchParams.ts new file mode 100644 index 000000000..acaccd06f --- /dev/null +++ b/src/lib/launcherSearchParams.ts @@ -0,0 +1,43 @@ +import type { LauncherListQuery } from "@server/routers/launcher/types"; + +export type LauncherQueryFilters = { + query?: string; + groupBy?: LauncherListQuery["groupBy"]; + groupKey?: string; + siteIds?: number[]; + labelIds?: number[]; + sort_by?: LauncherListQuery["sort_by"]; + order?: LauncherListQuery["order"]; + pageSize?: number; +}; + +export function buildLauncherSearchParams( + filters: LauncherQueryFilters, + page: number +) { + const sp = new URLSearchParams(); + sp.set("page", String(page)); + sp.set("pageSize", String(filters.pageSize ?? 20)); + if (filters.query) { + sp.set("query", filters.query); + } + if (filters.groupBy) { + sp.set("groupBy", filters.groupBy); + } + if (filters.groupKey) { + sp.set("groupKey", filters.groupKey); + } + if (filters.siteIds?.length) { + sp.set("siteIds", filters.siteIds.join(",")); + } + if (filters.labelIds?.length) { + sp.set("labelIds", filters.labelIds.join(",")); + } + if (filters.sort_by) { + sp.set("sort_by", filters.sort_by); + } + if (filters.order) { + sp.set("order", filters.order); + } + return sp; +} diff --git a/src/lib/launcherServerData.ts b/src/lib/launcherServerData.ts new file mode 100644 index 000000000..da8fb1846 --- /dev/null +++ b/src/lib/launcherServerData.ts @@ -0,0 +1,128 @@ +import { internal } from "@app/lib/api"; +import type { LauncherActiveViewId } from "@app/lib/launcherLocalStorage"; +import { resolveLauncherStateFromUrl } from "@app/lib/launcherUrlState"; +import { buildLauncherSearchParams } from "@app/lib/launcherSearchParams"; +import type { + LauncherGroup, + LauncherResource, + LauncherViewConfig, + LauncherViewRecord, + ListLauncherGroupsResponse, + ListLauncherResourcesResponse, + ListLauncherViewsResponse +} from "@server/routers/launcher/types"; +import { AxiosResponse } from "axios"; + +export type LauncherGroupResources = { + resources: LauncherResource[]; + pagination: { + total: number; + page: number; + pageSize: number; + }; +}; + +export type LauncherPageData = { + views: LauncherViewRecord[]; + activeViewId: LauncherActiveViewId; + config: LauncherViewConfig; + savedConfig: LauncherViewConfig; + groups: LauncherGroup[]; + groupsPagination: { + total: number; + page: number; + pageSize: number; + }; + resourcesByGroupKey: Record; +}; + +const emptyResources: LauncherGroupResources = { + resources: [], + pagination: { total: 0, page: 1, pageSize: 20 } +}; + +export async function fetchLauncherPageData( + orgId: string, + searchParams: URLSearchParams, + cookieHeader: Awaited< + ReturnType + > +): Promise { + let views: LauncherViewRecord[] = []; + try { + const viewsRes = await internal.get< + AxiosResponse + >(`/org/${orgId}/launcher/views`, cookieHeader); + views = viewsRes.data.data.views; + } catch (e) {} + + const { activeViewId, config, savedConfig } = resolveLauncherStateFromUrl( + searchParams, + views, + null + ); + + const groupFilters = { + query: config.query, + groupBy: config.groupBy, + siteIds: config.siteIds, + labelIds: config.labelIds, + sort_by: config.sortBy, + order: config.order, + pageSize: 20 + }; + + let groups: LauncherGroup[] = []; + let groupsPagination: LauncherPageData["groupsPagination"] = { + total: 0, + page: 1, + pageSize: 20 + }; + + try { + const sp = buildLauncherSearchParams(groupFilters, 1); + const groupsRes = await internal.get< + AxiosResponse + >(`/org/${orgId}/launcher/groups?${sp.toString()}`, cookieHeader); + groups = groupsRes.data.data.groups; + groupsPagination = groupsRes.data.data.pagination; + } catch (e) {} + + const resourcesByGroupKey: Record = {}; + + await Promise.all( + groups.map(async (group) => { + try { + const sp = buildLauncherSearchParams( + { + ...groupFilters, + groupKey: group.groupKey + }, + 1 + ); + const res = await internal.get< + AxiosResponse + >( + `/org/${orgId}/launcher/resources?${sp.toString()}`, + cookieHeader + ); + resourcesByGroupKey[group.groupKey] = { + resources: res.data.data.resources, + pagination: res.data.data.pagination + }; + } catch (e) { + resourcesByGroupKey[group.groupKey] = emptyResources; + } + }) + ); + + return { + views, + activeViewId, + config, + savedConfig, + groups, + groupsPagination, + resourcesByGroupKey + }; +} diff --git a/src/lib/queries.ts b/src/lib/queries.ts index 4d1cdc75c..c5d613942 100644 --- a/src/lib/queries.ts +++ b/src/lib/queries.ts @@ -53,6 +53,11 @@ import type { LauncherListQuery, LauncherViewConfig } from "@server/routers/launcher/types"; +import type { LauncherQueryFilters } from "@app/lib/launcherSearchParams"; +import { buildLauncherSearchParams } from "@app/lib/launcherSearchParams"; + +export type { LauncherQueryFilters } from "@app/lib/launcherSearchParams"; +export { buildLauncherSearchParams } from "@app/lib/launcherSearchParams"; export type ProductUpdate = { link: string | null; @@ -1174,45 +1179,6 @@ export const domainQueries = { }) }; -export type LauncherQueryFilters = { - query?: string; - groupBy?: LauncherListQuery["groupBy"]; - groupKey?: string; - siteIds?: number[]; - labelIds?: number[]; - sort_by?: LauncherListQuery["sort_by"]; - order?: LauncherListQuery["order"]; - pageSize?: number; -}; - -function launcherSearchParams(filters: LauncherQueryFilters, page: number) { - const sp = new URLSearchParams(); - sp.set("page", String(page)); - sp.set("pageSize", String(filters.pageSize ?? 20)); - if (filters.query) { - sp.set("query", filters.query); - } - if (filters.groupBy) { - sp.set("groupBy", filters.groupBy); - } - if (filters.groupKey) { - sp.set("groupKey", filters.groupKey); - } - if (filters.siteIds?.length) { - sp.set("siteIds", filters.siteIds.join(",")); - } - if (filters.labelIds?.length) { - sp.set("labelIds", filters.labelIds.join(",")); - } - if (filters.sort_by) { - sp.set("sort_by", filters.sort_by); - } - if (filters.order) { - sp.set("order", filters.order); - } - return sp; -} - export const launcherQueries = { views: (orgId: string) => queryOptions({ @@ -1228,7 +1194,7 @@ export const launcherQueries = { infiniteQueryOptions({ queryKey: ["ORG", orgId, "LAUNCHER", "GROUPS", filters] as const, queryFn: async ({ pageParam = 1, signal, meta }) => { - const sp = launcherSearchParams(filters, pageParam); + const sp = buildLauncherSearchParams(filters, pageParam); const res = await meta!.api.get< AxiosResponse >(`/org/${orgId}/launcher/groups?${sp.toString()}`, { signal }); @@ -1249,7 +1215,7 @@ export const launcherQueries = { infiniteQueryOptions({ queryKey: ["ORG", orgId, "LAUNCHER", "RESOURCES", filters] as const, queryFn: async ({ pageParam = 1, signal, meta }) => { - const sp = launcherSearchParams(filters, pageParam); + const sp = buildLauncherSearchParams(filters, pageParam); const res = await meta!.api.get< AxiosResponse >(`/org/${orgId}/launcher/resources?${sp.toString()}`, {