import { useInfiniteQuery, useMutation, useQuery, useQueryClient, UseQueryResult, InfiniteData, } from "@tanstack/react-query"; import { useAtom, useStore } from "jotai"; import { getTemplates, getTemplateById, createTemplate, updateTemplate, deleteTemplate, useTemplate, } from "@/ee/template/services/template-service.ts"; import { ITemplate } from "@/ee/template/types/template.types"; import { IPagination } from "@/lib/types.ts"; import { notifications } from "@mantine/notifications"; import { useTranslation } from "react-i18next"; import { invalidateOnCreatePage } from "@/features/page/queries/page-query.ts"; import { treeDataAtom } from "@/features/page/tree/atoms/tree-data-atom.ts"; import { treeModel } from "@/features/page/tree/model/tree-model"; import { SpaceTreeNode } from "@/features/page/tree/types.ts"; import { IPage } from "@/features/page/types/page.types.ts"; import { useQueryEmit } from "@/features/websocket/use-query-emit.ts"; export function useGetTemplatesQuery(params?: { spaceId?: string }) { const { spaceId } = params ?? {}; return useInfiniteQuery({ queryKey: ["templates", { spaceId }], queryFn: ({ pageParam }) => getTemplates({ spaceId, cursor: pageParam, limit: 30 }), initialPageParam: undefined as string | undefined, getNextPageParam: (lastPage) => lastPage.meta.hasNextPage ? lastPage.meta.nextCursor : undefined, }); } export function useGetTemplateByIdQuery( templateId: string, ): UseQueryResult { return useQuery({ queryKey: ["template", templateId], queryFn: () => getTemplateById(templateId), enabled: !!templateId, }); } export function useCreateTemplateMutation() { const queryClient = useQueryClient(); const { t } = useTranslation(); return useMutation>({ mutationFn: (data) => createTemplate(data), onSuccess: (newTemplate) => { queryClient.setQueriesData>>( { queryKey: ["templates"] }, (old) => { if (!old) return old; const firstPage = old.pages[0]; return { ...old, pages: [ { ...firstPage, items: [newTemplate, ...firstPage.items] }, ...old.pages.slice(1), ], }; }, ); notifications.show({ message: t("Template created successfully") }); }, onError: (error) => { const errorMessage = error["response"]?.data?.message; notifications.show({ message: errorMessage || t("Failed to create template"), color: "red", }); }, }); } export function useUpdateTemplateMutation() { const queryClient = useQueryClient(); const { t } = useTranslation(); return useMutation< ITemplate, Error, Partial & { templateId: string } >({ mutationFn: (data) => updateTemplate(data), onSuccess: (updatedTemplate) => { queryClient.setQueriesData>>( { queryKey: ["templates"] }, (old) => { if (!old) return old; return { ...old, pages: old.pages.map((page) => ({ ...page, items: page.items.map((item) => item.id === updatedTemplate.id ? updatedTemplate : item, ), })), }; }, ); queryClient.setQueryData( ["template", updatedTemplate.id], updatedTemplate, ); }, onError: (error) => { const errorMessage = error["response"]?.data?.message; notifications.show({ message: errorMessage || t("Failed to update template"), color: "red", }); }, }); } export function useDeleteTemplateMutation() { const queryClient = useQueryClient(); const { t } = useTranslation(); return useMutation({ mutationFn: (templateId) => deleteTemplate(templateId), onSuccess: (_data, templateId) => { queryClient.setQueriesData>>( { queryKey: ["templates"] }, (old) => { if (!old) return old; return { ...old, pages: old.pages.map((page) => ({ ...page, items: page.items.filter((item) => item.id !== templateId), })), }; }, ); notifications.show({ message: t("Template deleted") }); }, onError: (error) => { const errorMessage = error["response"]?.data?.message; notifications.show({ message: errorMessage || t("Failed to delete template"), color: "red", }); }, }); } export function useUseTemplateMutation() { const { t } = useTranslation(); const [, setTreeData] = useAtom(treeDataAtom); const store = useStore(); const emit = useQueryEmit(); return useMutation< IPage, Error, { templateId: string; spaceId: string; parentPageId?: string } >({ mutationFn: (data) => useTemplate(data), onSuccess: (page) => { // React Query sidebar-pages cache update (same path useCreatePageMutation takes). invalidateOnCreatePage(page); const parentId = page.parentPageId ?? null; const newNode: SpaceTreeNode = { id: page.id, slugId: page.slugId, name: page.title, icon: page.icon, position: page.position, spaceId: page.spaceId, parentPageId: page.parentPageId, hasChildren: false, children: [], }; // Only mutate the tree atom and broadcast if it currently represents // this space. Cross-space template-use (e.g., from the gallery picking // a different space) lets the target space's clients pick up the new // page on their next React Query refetch (focus, navigation, etc.). // Without this guard we'd both pollute the local tree and send a wrong // `index` to remote clients in the target space. const current = store.get(treeDataAtom); const treeIsForThisSpace = current[0]?.spaceId === page.spaceId; if (!treeIsForThisSpace) return; const lastIndex = parentId === null ? current.length : (treeModel.find(current, parentId)?.children?.length ?? 0); setTreeData((prev) => treeModel.insert(prev, parentId, newNode, lastIndex), ); setTimeout(() => { emit({ operation: "addTreeNode", spaceId: page.spaceId, payload: { parentId, index: lastIndex, data: newNode, }, }); }, 50); }, onError: (error) => { const errorMessage = error["response"]?.data?.message; notifications.show({ message: errorMessage || t("Failed to create page from template"), color: "red", }); }, }); }