1"use client";
2
3
7import { useMemo } from "react";
8import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
9import { blockApi, pageApi, workspaceApi } from "@/lib/api";
10import { sortByPosition } from "@/lib/utils";
11import { useUiStore, type BulkAction } from "@/stores/ui-store";
12import type { Page } from "@/lib/types";
13
14export function usePageTree(workspaceId: string, parentId?: string | null) {
15 const effectiveParent = parentId ?? null;
16 return useQuery({
17 queryKey: ["workspaces", workspaceId, "page-tree", effectiveParent],
18 queryFn: () => workspaceApi.pageTree(workspaceId, { parentId: effectiveParent }),
19 enabled: workspaceId.length > 0
20 });
21}
22
23
24export function usePage(id: string) {
25 return useQuery({
26 queryKey: ["pages", id],
27 queryFn: () => pageApi.get(id),
28 enabled: id.length > 0
29 });
30}
31
32
36export function usePageBlocks(pageId: string, parentId: string | null = null) {
37 const query = useQuery({
38 queryKey: ["pages", pageId, "blocks", parentId ?? "top"],
39 queryFn: () => blockApi.list(pageId, { parentId, depth: 1 }),
40 enabled: pageId.length > 0
41 });
42
43 const blocks = useMemo(
44 () => (query.data ? sortByPosition(query.data) : []),
45 [query.data]
46 );
47
48 return { ...query, blocks };
49}
50
51
57export function useBacklinks(pageId: string) {
58 return useQuery({
59 queryKey: ["pages", pageId, "backlinks"],
60 queryFn: () => pageApi.backlinks(pageId),
61 enabled: pageId.length > 0,
62 staleTime: 30 * 1000
63 });
64}
65
66export function usePageVersions(pageId: string) {
67 return useQuery({
68 queryKey: ["pages", pageId, "versions"],
69 queryFn: () => pageApi.versions(pageId),
70 enabled: pageId.length > 0,
71 staleTime: 60 * 1000
72 });
73}
74
75export interface NewPageInput {
76 workspaceId?: string;
77 parentId?: string | null;
78 title?: string;
79 type?: "page" | "database" | "whiteboard";
80}
81
82export function useCreatePage(workspaceId: string) {
83 const qc = useQueryClient();
84
85 return useMutation({
86 mutationFn: (input: NewPageInput) =>
87
88
89
90
91 pageApi.create({
92 workspaceId: input.workspaceId ?? workspaceId,
93 parentId: input.parentId ?? null,
94 title: input.title ?? "",
95 type: input.type
96 }),
97 onSuccess: (page) => {
98 qc.invalidateQueries({ queryKey: ["workspaces", workspaceId, "page-tree"] });
99 qc.invalidateQueries({ queryKey: ["workspaces", workspaceId, "pages"] });
100 qc.setQueryData(["pages", page.id], page);
101 }
102 });
103}
104
105export function useUpdatePage(workspaceId: string) {
106 const qc = useQueryClient();
107 return useMutation({
108 mutationFn: (input: { id: string } & Partial<Pick<Page, "title" | "icon" | "cover" | "parentId" | "position">> & { archived?: boolean }) =>
109 pageApi.update(input.id, {
110 title: input.title,
111 icon: input.icon,
112
113
114 cover: input.cover,
115 parentId: input.parentId,
116 position: input.position,
117
118
119 ...(input.archived !== undefined ? { archived: input.archived } : {})
120 }),
121 onSuccess: (page) => {
122 qc.setQueryData(["pages", page.id], page);
123 qc.invalidateQueries({ queryKey: ["workspaces", workspaceId, "page-tree"] });
124 }
125 });
126}
127
128
132export function useDeletePage(workspaceId: string) {
133 const qc = useQueryClient();
134 return useMutation({
135 mutationFn: (id: string) => pageApi.remove(id),
136 onSuccess: (_data, id) => {
137 qc.removeQueries({ queryKey: ["pages", id, "blocks"] });
138 qc.invalidateQueries({ queryKey: ["workspaces", workspaceId, "page-tree"] });
139 qc.invalidateQueries({ queryKey: ["pages", id] });
140
141
142
143 qc.invalidateQueries({ queryKey: ["workspaces", workspaceId, "trash"] });
144 }
145 });
146}
147
148
149
154export function useDuplicatePage(workspaceId: string) {
155 const qc = useQueryClient();
156 return useMutation({
157 mutationFn: async (input: { id: string; parentId?: string | null; title?: string }) => {
158
159
160 const { id: newId } = await pageApi.duplicate(input.id, {
161 parentId: input.parentId ?? undefined,
162 title: input.title
163 });
164 return pageApi.get(newId);
165 },
166 onSuccess: (page) => {
167 qc.setQueryData(["pages", page.id], page);
168 qc.invalidateQueries({ queryKey: ["workspaces", workspaceId, "page-tree"] });
169 qc.invalidateQueries({ queryKey: ["workspaces", workspaceId, "pages"] });
170 }
171 });
172}
173
174export function useToggleFavorite() {
175 const qc = useQueryClient();
176 return useMutation({
177 mutationFn: (id: string) => pageApi.toggleFavorite(id),
178 onSuccess: (data, id) => {
179 qc.invalidateQueries({ queryKey: ["pages", id] });
180 qc.invalidateQueries({ queryKey: ["favorites"] });
181 }
182 });
183}
184
185
191export function usePageWatch(pageId: string) {
192 return useQuery({
193 queryKey: ["pages", pageId, "watch"],
194 queryFn: () => pageApi.isWatching(pageId),
195 enabled: pageId.length > 0
196 });
197}
198
199
200export function useToggleWatch() {
201 const qc = useQueryClient();
202 return useMutation({
203 mutationFn: ({ id, watching }: { id: string; watching: boolean }) =>
204 watching ? pageApi.unwatch(id) : pageApi.watch(id),
205 onSuccess: (data, { id }) => {
206 qc.setQueryData(["pages", id, "watch"], { watching: data.watching });
207 }
208 });
209}
210
211
218export function useBulkPages(workspaceId: string) {
219 const qc = useQueryClient();
220 const setBulk = useUiStore((s) => s.setBulk);
221 const clearPageSelection = useUiStore((s) => s.clearPageSelection);
222
223 return useMutation({
224 mutationFn: (input: { action: BulkAction; ids: string[] }) =>
225 pageApi.bulk(workspaceId, input.action, input.ids),
226 onSuccess: (_res, input) => {
227 setBulk(false);
228 clearPageSelection();
229
230 qc.invalidateQueries({ queryKey: ["workspaces", workspaceId, "page-tree"] });
231 qc.invalidateQueries({ queryKey: ["workspaces", workspaceId, "pages"] });
232 qc.invalidateQueries({ queryKey: ["favorites"] });
233 void input;
234 }
235 });
236}
237