diff options
Diffstat (limited to '')
| -rw-r--r-- | packages/shared-react/hooks/assets.ts | 99 | ||||
| -rw-r--r-- | packages/shared-react/hooks/bookmarks.ts | 250 | ||||
| -rw-r--r-- | packages/shared-react/hooks/highlights.ts | 93 | ||||
| -rw-r--r-- | packages/shared-react/hooks/lists.ts | 196 | ||||
| -rw-r--r-- | packages/shared-react/hooks/reader-settings.tsx | 290 | ||||
| -rw-r--r-- | packages/shared-react/hooks/rules.ts | 69 | ||||
| -rw-r--r-- | packages/shared-react/hooks/tags.ts | 203 | ||||
| -rw-r--r-- | packages/shared-react/hooks/users.ts | 51 | ||||
| -rw-r--r-- | packages/shared-react/package.json | 5 | ||||
| -rw-r--r-- | packages/shared-react/providers/trpc-provider.tsx | 18 | ||||
| -rw-r--r-- | packages/shared-react/trpc.ts | 4 |
11 files changed, 899 insertions, 379 deletions
diff --git a/packages/shared-react/hooks/assets.ts b/packages/shared-react/hooks/assets.ts index 5367e97c..8be21304 100644 --- a/packages/shared-react/hooks/assets.ts +++ b/packages/shared-react/hooks/assets.ts @@ -1,49 +1,74 @@ -import { api } from "../trpc"; +import { useMutation, useQueryClient } from "@tanstack/react-query"; + +import { useTRPC } from "../trpc"; + +type TRPCApi = ReturnType<typeof useTRPC>; export function useAttachBookmarkAsset( - ...opts: Parameters<typeof api.assets.attachAsset.useMutation> + opts?: Parameters<TRPCApi["assets"]["attachAsset"]["mutationOptions"]>[0], ) { - const apiUtils = api.useUtils(); - return api.assets.attachAsset.useMutation({ - ...opts[0], - onSuccess: (res, req, meta, context) => { - apiUtils.bookmarks.getBookmarks.invalidate(); - apiUtils.bookmarks.searchBookmarks.invalidate(); - apiUtils.bookmarks.getBookmark.invalidate({ bookmarkId: req.bookmarkId }); - apiUtils.assets.list.invalidate(); - return opts[0]?.onSuccess?.(res, req, meta, context); - }, - }); + const api = useTRPC(); + const queryClient = useQueryClient(); + return useMutation( + api.assets.attachAsset.mutationOptions({ + ...opts, + onSuccess: (res, req, meta, context) => { + queryClient.invalidateQueries(api.bookmarks.getBookmarks.pathFilter()); + queryClient.invalidateQueries( + api.bookmarks.searchBookmarks.pathFilter(), + ); + queryClient.invalidateQueries( + api.bookmarks.getBookmark.queryFilter({ bookmarkId: req.bookmarkId }), + ); + queryClient.invalidateQueries(api.assets.list.pathFilter()); + return opts?.onSuccess?.(res, req, meta, context); + }, + }), + ); } export function useReplaceBookmarkAsset( - ...opts: Parameters<typeof api.assets.replaceAsset.useMutation> + opts?: Parameters<TRPCApi["assets"]["replaceAsset"]["mutationOptions"]>[0], ) { - const apiUtils = api.useUtils(); - return api.assets.replaceAsset.useMutation({ - ...opts[0], - onSuccess: (res, req, meta, context) => { - apiUtils.bookmarks.getBookmarks.invalidate(); - apiUtils.bookmarks.searchBookmarks.invalidate(); - apiUtils.bookmarks.getBookmark.invalidate({ bookmarkId: req.bookmarkId }); - apiUtils.assets.list.invalidate(); - return opts[0]?.onSuccess?.(res, req, meta, context); - }, - }); + const api = useTRPC(); + const queryClient = useQueryClient(); + return useMutation( + api.assets.replaceAsset.mutationOptions({ + ...opts, + onSuccess: (res, req, meta, context) => { + queryClient.invalidateQueries(api.bookmarks.getBookmarks.pathFilter()); + queryClient.invalidateQueries( + api.bookmarks.searchBookmarks.pathFilter(), + ); + queryClient.invalidateQueries( + api.bookmarks.getBookmark.queryFilter({ bookmarkId: req.bookmarkId }), + ); + queryClient.invalidateQueries(api.assets.list.pathFilter()); + return opts?.onSuccess?.(res, req, meta, context); + }, + }), + ); } export function useDetachBookmarkAsset( - ...opts: Parameters<typeof api.assets.detachAsset.useMutation> + opts?: Parameters<TRPCApi["assets"]["detachAsset"]["mutationOptions"]>[0], ) { - const apiUtils = api.useUtils(); - return api.assets.detachAsset.useMutation({ - ...opts[0], - onSuccess: (res, req, meta, context) => { - apiUtils.bookmarks.getBookmarks.invalidate(); - apiUtils.bookmarks.searchBookmarks.invalidate(); - apiUtils.bookmarks.getBookmark.invalidate({ bookmarkId: req.bookmarkId }); - apiUtils.assets.list.invalidate(); - return opts[0]?.onSuccess?.(res, req, meta, context); - }, - }); + const api = useTRPC(); + const queryClient = useQueryClient(); + return useMutation( + api.assets.detachAsset.mutationOptions({ + ...opts, + onSuccess: (res, req, meta, context) => { + queryClient.invalidateQueries(api.bookmarks.getBookmarks.pathFilter()); + queryClient.invalidateQueries( + api.bookmarks.searchBookmarks.pathFilter(), + ); + queryClient.invalidateQueries( + api.bookmarks.getBookmark.queryFilter({ bookmarkId: req.bookmarkId }), + ); + queryClient.invalidateQueries(api.assets.list.pathFilter()); + return opts?.onSuccess?.(res, req, meta, context); + }, + }), + ); } diff --git a/packages/shared-react/hooks/bookmarks.ts b/packages/shared-react/hooks/bookmarks.ts index aea2d185..6ff75d7e 100644 --- a/packages/shared-react/hooks/bookmarks.ts +++ b/packages/shared-react/hooks/bookmarks.ts @@ -1,132 +1,196 @@ +import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; + import { getBookmarkRefreshInterval } from "@karakeep/shared/utils/bookmarkUtils"; -import { api } from "../trpc"; +import { useTRPC } from "../trpc"; import { useBookmarkGridContext } from "./bookmark-grid-context"; import { useAddBookmarkToList } from "./lists"; +type TRPCApi = ReturnType<typeof useTRPC>; + export function useAutoRefreshingBookmarkQuery( - input: Parameters<typeof api.bookmarks.getBookmark.useQuery>[0], + input: Parameters<TRPCApi["bookmarks"]["getBookmark"]["queryOptions"]>[0], ) { - return api.bookmarks.getBookmark.useQuery(input, { - refetchInterval: (query) => { - const data = query.state.data; - if (!data) { - return false; - } - return getBookmarkRefreshInterval(data); - }, - }); + const api = useTRPC(); + return useQuery( + api.bookmarks.getBookmark.queryOptions(input, { + refetchInterval: (query) => { + const data = query.state.data; + if (!data) { + return false; + } + return getBookmarkRefreshInterval(data); + }, + }), + ); } export function useCreateBookmark( - ...opts: Parameters<typeof api.bookmarks.createBookmark.useMutation> + opts?: Parameters< + TRPCApi["bookmarks"]["createBookmark"]["mutationOptions"] + >[0], ) { - const apiUtils = api.useUtils(); - return api.bookmarks.createBookmark.useMutation({ - ...opts[0], - onSuccess: (res, req, meta, context) => { - apiUtils.bookmarks.getBookmarks.invalidate(); - apiUtils.bookmarks.searchBookmarks.invalidate(); - apiUtils.lists.stats.invalidate(); - return opts[0]?.onSuccess?.(res, req, meta, context); - }, - }); + const api = useTRPC(); + const queryClient = useQueryClient(); + return useMutation( + api.bookmarks.createBookmark.mutationOptions({ + ...opts, + onSuccess: (res, req, meta, context) => { + queryClient.invalidateQueries(api.bookmarks.getBookmarks.pathFilter()); + queryClient.invalidateQueries( + api.bookmarks.searchBookmarks.pathFilter(), + ); + queryClient.invalidateQueries(api.lists.stats.pathFilter()); + return opts?.onSuccess?.(res, req, meta, context); + }, + }), + ); } export function useCreateBookmarkWithPostHook( - ...opts: Parameters<typeof api.bookmarks.createBookmark.useMutation> + opts?: Parameters< + TRPCApi["bookmarks"]["createBookmark"]["mutationOptions"] + >[0], ) { - const apiUtils = api.useUtils(); + const api = useTRPC(); + const queryClient = useQueryClient(); const postCreationCB = useBookmarkPostCreationHook(); - return api.bookmarks.createBookmark.useMutation({ - ...opts[0], - onSuccess: async (res, req, meta, context) => { - apiUtils.bookmarks.getBookmarks.invalidate(); - apiUtils.bookmarks.searchBookmarks.invalidate(); - await postCreationCB(res.id); - return opts[0]?.onSuccess?.(res, req, meta, context); - }, - }); + return useMutation( + api.bookmarks.createBookmark.mutationOptions({ + ...opts, + onSuccess: async (res, req, meta, context) => { + queryClient.invalidateQueries(api.bookmarks.getBookmarks.pathFilter()); + queryClient.invalidateQueries( + api.bookmarks.searchBookmarks.pathFilter(), + ); + await postCreationCB(res.id); + return opts?.onSuccess?.(res, req, meta, context); + }, + }), + ); } export function useDeleteBookmark( - ...opts: Parameters<typeof api.bookmarks.deleteBookmark.useMutation> + opts?: Parameters< + TRPCApi["bookmarks"]["deleteBookmark"]["mutationOptions"] + >[0], ) { - const apiUtils = api.useUtils(); - return api.bookmarks.deleteBookmark.useMutation({ - ...opts[0], - onSuccess: (res, req, meta, context) => { - apiUtils.bookmarks.getBookmarks.invalidate(); - apiUtils.bookmarks.searchBookmarks.invalidate(); - apiUtils.bookmarks.getBookmark.invalidate({ bookmarkId: req.bookmarkId }); - apiUtils.lists.stats.invalidate(); - return opts[0]?.onSuccess?.(res, req, meta, context); - }, - }); + const api = useTRPC(); + const queryClient = useQueryClient(); + return useMutation( + api.bookmarks.deleteBookmark.mutationOptions({ + ...opts, + onSuccess: (res, req, meta, context) => { + queryClient.invalidateQueries(api.bookmarks.getBookmarks.pathFilter()); + queryClient.invalidateQueries( + api.bookmarks.searchBookmarks.pathFilter(), + ); + queryClient.invalidateQueries( + api.bookmarks.getBookmark.queryFilter({ bookmarkId: req.bookmarkId }), + ); + queryClient.invalidateQueries(api.lists.stats.pathFilter()); + return opts?.onSuccess?.(res, req, meta, context); + }, + }), + ); } export function useUpdateBookmark( - ...opts: Parameters<typeof api.bookmarks.updateBookmark.useMutation> + opts?: Parameters< + TRPCApi["bookmarks"]["updateBookmark"]["mutationOptions"] + >[0], ) { - const apiUtils = api.useUtils(); - return api.bookmarks.updateBookmark.useMutation({ - ...opts[0], - onSuccess: (res, req, meta, context) => { - apiUtils.bookmarks.getBookmarks.invalidate(); - apiUtils.bookmarks.searchBookmarks.invalidate(); - apiUtils.bookmarks.getBookmark.invalidate({ bookmarkId: req.bookmarkId }); - apiUtils.lists.stats.invalidate(); - return opts[0]?.onSuccess?.(res, req, meta, context); - }, - }); + const api = useTRPC(); + const queryClient = useQueryClient(); + return useMutation( + api.bookmarks.updateBookmark.mutationOptions({ + ...opts, + onSuccess: (res, req, meta, context) => { + queryClient.invalidateQueries(api.bookmarks.getBookmarks.pathFilter()); + queryClient.invalidateQueries( + api.bookmarks.searchBookmarks.pathFilter(), + ); + queryClient.invalidateQueries( + api.bookmarks.getBookmark.queryFilter({ bookmarkId: req.bookmarkId }), + ); + queryClient.invalidateQueries(api.lists.stats.pathFilter()); + return opts?.onSuccess?.(res, req, meta, context); + }, + }), + ); } export function useSummarizeBookmark( - ...opts: Parameters<typeof api.bookmarks.summarizeBookmark.useMutation> + opts?: Parameters< + TRPCApi["bookmarks"]["summarizeBookmark"]["mutationOptions"] + >[0], ) { - const apiUtils = api.useUtils(); - return api.bookmarks.summarizeBookmark.useMutation({ - ...opts[0], - onSuccess: (res, req, meta, context) => { - apiUtils.bookmarks.getBookmarks.invalidate(); - apiUtils.bookmarks.searchBookmarks.invalidate(); - apiUtils.bookmarks.getBookmark.invalidate({ bookmarkId: req.bookmarkId }); - return opts[0]?.onSuccess?.(res, req, meta, context); - }, - }); + const api = useTRPC(); + const queryClient = useQueryClient(); + return useMutation( + api.bookmarks.summarizeBookmark.mutationOptions({ + ...opts, + onSuccess: (res, req, meta, context) => { + queryClient.invalidateQueries(api.bookmarks.getBookmarks.pathFilter()); + queryClient.invalidateQueries( + api.bookmarks.searchBookmarks.pathFilter(), + ); + queryClient.invalidateQueries( + api.bookmarks.getBookmark.queryFilter({ bookmarkId: req.bookmarkId }), + ); + return opts?.onSuccess?.(res, req, meta, context); + }, + }), + ); } export function useRecrawlBookmark( - ...opts: Parameters<typeof api.bookmarks.recrawlBookmark.useMutation> + opts?: Parameters< + TRPCApi["bookmarks"]["recrawlBookmark"]["mutationOptions"] + >[0], ) { - const apiUtils = api.useUtils(); - return api.bookmarks.recrawlBookmark.useMutation({ - ...opts[0], - onSuccess: (res, req, meta, context) => { - apiUtils.bookmarks.getBookmark.invalidate({ bookmarkId: req.bookmarkId }); - return opts[0]?.onSuccess?.(res, req, meta, context); - }, - }); + const api = useTRPC(); + const queryClient = useQueryClient(); + return useMutation( + api.bookmarks.recrawlBookmark.mutationOptions({ + ...opts, + onSuccess: (res, req, meta, context) => { + queryClient.invalidateQueries( + api.bookmarks.getBookmark.queryFilter({ bookmarkId: req.bookmarkId }), + ); + return opts?.onSuccess?.(res, req, meta, context); + }, + }), + ); } export function useUpdateBookmarkTags( - ...opts: Parameters<typeof api.bookmarks.updateTags.useMutation> + opts?: Parameters<TRPCApi["bookmarks"]["updateTags"]["mutationOptions"]>[0], ) { - const apiUtils = api.useUtils(); - return api.bookmarks.updateTags.useMutation({ - ...opts[0], - onSuccess: (res, req, meta, context) => { - apiUtils.bookmarks.getBookmark.invalidate({ bookmarkId: req.bookmarkId }); - - [...res.attached, ...res.detached].forEach((id) => { - apiUtils.tags.get.invalidate({ tagId: id }); - apiUtils.bookmarks.getBookmarks.invalidate({ tagId: id }); - }); - apiUtils.tags.list.invalidate(); - apiUtils.lists.stats.invalidate(); - return opts[0]?.onSuccess?.(res, req, meta, context); - }, - }); + const api = useTRPC(); + const queryClient = useQueryClient(); + return useMutation( + api.bookmarks.updateTags.mutationOptions({ + ...opts, + onSuccess: (res, req, meta, context) => { + queryClient.invalidateQueries( + api.bookmarks.getBookmark.queryFilter({ bookmarkId: req.bookmarkId }), + ); + + [...res.attached, ...res.detached].forEach((id) => { + queryClient.invalidateQueries( + api.tags.get.queryFilter({ tagId: id }), + ); + queryClient.invalidateQueries( + api.bookmarks.getBookmarks.queryFilter({ tagId: id }), + ); + }); + queryClient.invalidateQueries(api.tags.list.pathFilter()); + queryClient.invalidateQueries(api.lists.stats.pathFilter()); + return opts?.onSuccess?.(res, req, meta, context); + }, + }), + ); } /** diff --git a/packages/shared-react/hooks/highlights.ts b/packages/shared-react/hooks/highlights.ts index e642f878..3f6a6e01 100644 --- a/packages/shared-react/hooks/highlights.ts +++ b/packages/shared-react/hooks/highlights.ts @@ -1,49 +1,68 @@ -import { api } from "../trpc"; +import { useMutation, useQueryClient } from "@tanstack/react-query"; + +import { useTRPC } from "../trpc"; + +type TRPCApi = ReturnType<typeof useTRPC>; export function useCreateHighlight( - ...opts: Parameters<typeof api.highlights.create.useMutation> + opts?: Parameters<TRPCApi["highlights"]["create"]["mutationOptions"]>[0], ) { - const apiUtils = api.useUtils(); - return api.highlights.create.useMutation({ - ...opts[0], - onSuccess: (res, req, meta, context) => { - apiUtils.highlights.getForBookmark.invalidate({ - bookmarkId: req.bookmarkId, - }); - apiUtils.highlights.getAll.invalidate(); - return opts[0]?.onSuccess?.(res, req, meta, context); - }, - }); + const api = useTRPC(); + const queryClient = useQueryClient(); + return useMutation( + api.highlights.create.mutationOptions({ + ...opts, + onSuccess: (res, req, meta, context) => { + queryClient.invalidateQueries( + api.highlights.getForBookmark.queryFilter({ + bookmarkId: req.bookmarkId, + }), + ); + queryClient.invalidateQueries(api.highlights.getAll.pathFilter()); + return opts?.onSuccess?.(res, req, meta, context); + }, + }), + ); } export function useUpdateHighlight( - ...opts: Parameters<typeof api.highlights.update.useMutation> + opts?: Parameters<TRPCApi["highlights"]["update"]["mutationOptions"]>[0], ) { - const apiUtils = api.useUtils(); - return api.highlights.update.useMutation({ - ...opts[0], - onSuccess: (res, req, meta, context) => { - apiUtils.highlights.getForBookmark.invalidate({ - bookmarkId: res.bookmarkId, - }); - apiUtils.highlights.getAll.invalidate(); - return opts[0]?.onSuccess?.(res, req, meta, context); - }, - }); + const api = useTRPC(); + const queryClient = useQueryClient(); + return useMutation( + api.highlights.update.mutationOptions({ + ...opts, + onSuccess: (res, req, meta, context) => { + queryClient.invalidateQueries( + api.highlights.getForBookmark.queryFilter({ + bookmarkId: res.bookmarkId, + }), + ); + queryClient.invalidateQueries(api.highlights.getAll.pathFilter()); + return opts?.onSuccess?.(res, req, meta, context); + }, + }), + ); } export function useDeleteHighlight( - ...opts: Parameters<typeof api.highlights.delete.useMutation> + opts?: Parameters<TRPCApi["highlights"]["delete"]["mutationOptions"]>[0], ) { - const apiUtils = api.useUtils(); - return api.highlights.delete.useMutation({ - ...opts[0], - onSuccess: (res, req, meta, context) => { - apiUtils.highlights.getForBookmark.invalidate({ - bookmarkId: res.bookmarkId, - }); - apiUtils.highlights.getAll.invalidate(); - return opts[0]?.onSuccess?.(res, req, meta, context); - }, - }); + const api = useTRPC(); + const queryClient = useQueryClient(); + return useMutation( + api.highlights.delete.mutationOptions({ + ...opts, + onSuccess: (res, req, meta, context) => { + queryClient.invalidateQueries( + api.highlights.getForBookmark.queryFilter({ + bookmarkId: res.bookmarkId, + }), + ); + queryClient.invalidateQueries(api.highlights.getAll.pathFilter()); + return opts?.onSuccess?.(res, req, meta, context); + }, + }), + ); } diff --git a/packages/shared-react/hooks/lists.ts b/packages/shared-react/hooks/lists.ts index d269efe3..231e43a7 100644 --- a/packages/shared-react/hooks/lists.ts +++ b/packages/shared-react/hooks/lists.ts @@ -1,113 +1,155 @@ +import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; + import { ZBookmarkList } from "@karakeep/shared/types/lists"; import { listsToTree, ZBookmarkListRoot, } from "@karakeep/shared/utils/listUtils"; -import { api } from "../trpc"; +import { useTRPC } from "../trpc"; + +type TRPCApi = ReturnType<typeof useTRPC>; export function useCreateBookmarkList( - ...opts: Parameters<typeof api.lists.create.useMutation> + opts?: Parameters<TRPCApi["lists"]["create"]["mutationOptions"]>[0], ) { - const apiUtils = api.useUtils(); - return api.lists.create.useMutation({ - ...opts[0], - onSuccess: (res, req, meta, context) => { - apiUtils.lists.list.invalidate(); - return opts[0]?.onSuccess?.(res, req, meta, context); - }, - }); + const api = useTRPC(); + const queryClient = useQueryClient(); + return useMutation( + api.lists.create.mutationOptions({ + ...opts, + onSuccess: (res, req, meta, context) => { + queryClient.invalidateQueries(api.lists.list.pathFilter()); + return opts?.onSuccess?.(res, req, meta, context); + }, + }), + ); } export function useEditBookmarkList( - ...opts: Parameters<typeof api.lists.edit.useMutation> + opts?: Parameters<TRPCApi["lists"]["edit"]["mutationOptions"]>[0], ) { - const apiUtils = api.useUtils(); - return api.lists.edit.useMutation({ - ...opts[0], - onSuccess: (res, req, meta, context) => { - apiUtils.lists.list.invalidate(); - apiUtils.lists.get.invalidate({ listId: req.listId }); - if (res.type === "smart") { - apiUtils.bookmarks.getBookmarks.invalidate({ listId: req.listId }); - } - return opts[0]?.onSuccess?.(res, req, meta, context); - }, - }); + const api = useTRPC(); + const queryClient = useQueryClient(); + return useMutation( + api.lists.edit.mutationOptions({ + ...opts, + onSuccess: (res, req, meta, context) => { + queryClient.invalidateQueries(api.lists.list.pathFilter()); + queryClient.invalidateQueries( + api.lists.get.queryFilter({ listId: req.listId }), + ); + if (res.type === "smart") { + queryClient.invalidateQueries( + api.bookmarks.getBookmarks.queryFilter({ listId: req.listId }), + ); + } + return opts?.onSuccess?.(res, req, meta, context); + }, + }), + ); } export function useMergeLists( - ...opts: Parameters<typeof api.lists.merge.useMutation> + opts?: Parameters<TRPCApi["lists"]["merge"]["mutationOptions"]>[0], ) { - const apiUtils = api.useUtils(); - return api.lists.merge.useMutation({ - ...opts[0], - onSuccess: (res, req, meta, context) => { - apiUtils.lists.list.invalidate(); - apiUtils.bookmarks.getBookmarks.invalidate({ listId: req.targetId }); - apiUtils.lists.stats.invalidate(); - return opts[0]?.onSuccess?.(res, req, meta, context); - }, - }); + const api = useTRPC(); + const queryClient = useQueryClient(); + return useMutation( + api.lists.merge.mutationOptions({ + ...opts, + onSuccess: (res, req, meta, context) => { + queryClient.invalidateQueries(api.lists.list.pathFilter()); + queryClient.invalidateQueries( + api.bookmarks.getBookmarks.queryFilter({ listId: req.targetId }), + ); + queryClient.invalidateQueries(api.lists.stats.pathFilter()); + return opts?.onSuccess?.(res, req, meta, context); + }, + }), + ); } export function useAddBookmarkToList( - ...opts: Parameters<typeof api.lists.addToList.useMutation> + opts?: Parameters<TRPCApi["lists"]["addToList"]["mutationOptions"]>[0], ) { - const apiUtils = api.useUtils(); - return api.lists.addToList.useMutation({ - ...opts[0], - onSuccess: (res, req, meta, context) => { - apiUtils.bookmarks.getBookmarks.invalidate({ listId: req.listId }); - apiUtils.lists.getListsOfBookmark.invalidate({ - bookmarkId: req.bookmarkId, - }); - apiUtils.lists.stats.invalidate(); - return opts[0]?.onSuccess?.(res, req, meta, context); - }, - }); + const api = useTRPC(); + const queryClient = useQueryClient(); + return useMutation( + api.lists.addToList.mutationOptions({ + ...opts, + onSuccess: (res, req, meta, context) => { + queryClient.invalidateQueries( + api.bookmarks.getBookmarks.queryFilter({ listId: req.listId }), + ); + queryClient.invalidateQueries( + api.lists.getListsOfBookmark.queryFilter({ + bookmarkId: req.bookmarkId, + }), + ); + queryClient.invalidateQueries(api.lists.stats.pathFilter()); + return opts?.onSuccess?.(res, req, meta, context); + }, + }), + ); } export function useRemoveBookmarkFromList( - ...opts: Parameters<typeof api.lists.removeFromList.useMutation> + opts?: Parameters<TRPCApi["lists"]["removeFromList"]["mutationOptions"]>[0], ) { - const apiUtils = api.useUtils(); - return api.lists.removeFromList.useMutation({ - ...opts[0], - onSuccess: (res, req, meta, context) => { - apiUtils.bookmarks.getBookmarks.invalidate({ listId: req.listId }); - apiUtils.lists.getListsOfBookmark.invalidate({ - bookmarkId: req.bookmarkId, - }); - apiUtils.lists.stats.invalidate(); - return opts[0]?.onSuccess?.(res, req, meta, context); - }, - }); + const api = useTRPC(); + const queryClient = useQueryClient(); + return useMutation( + api.lists.removeFromList.mutationOptions({ + ...opts, + onSuccess: (res, req, meta, context) => { + queryClient.invalidateQueries( + api.bookmarks.getBookmarks.queryFilter({ listId: req.listId }), + ); + queryClient.invalidateQueries( + api.lists.getListsOfBookmark.queryFilter({ + bookmarkId: req.bookmarkId, + }), + ); + queryClient.invalidateQueries(api.lists.stats.pathFilter()); + return opts?.onSuccess?.(res, req, meta, context); + }, + }), + ); } export function useDeleteBookmarkList( - ...opts: Parameters<typeof api.lists.delete.useMutation> + opts?: Parameters<TRPCApi["lists"]["delete"]["mutationOptions"]>[0], ) { - const apiUtils = api.useUtils(); - return api.lists.delete.useMutation({ - ...opts[0], - onSuccess: (res, req, meta, context) => { - apiUtils.lists.list.invalidate(); - apiUtils.lists.get.invalidate({ listId: req.listId }); - return opts[0]?.onSuccess?.(res, req, meta, context); - }, - }); + const api = useTRPC(); + const queryClient = useQueryClient(); + return useMutation( + api.lists.delete.mutationOptions({ + ...opts, + onSuccess: (res, req, meta, context) => { + queryClient.invalidateQueries(api.lists.list.pathFilter()); + queryClient.invalidateQueries( + api.lists.get.queryFilter({ listId: req.listId }), + ); + return opts?.onSuccess?.(res, req, meta, context); + }, + }), + ); } export function useBookmarkLists( - ...opts: Parameters<typeof api.lists.list.useQuery> + input?: Parameters<TRPCApi["lists"]["list"]["queryOptions"]>[0], + opts?: Parameters<TRPCApi["lists"]["list"]["queryOptions"]>[1], ) { - return api.lists.list.useQuery(opts[0], { - ...opts[1], - select: (data) => { - return { data: data.lists, ...listsToTree(data.lists) }; - }, - }); + const api = useTRPC(); + return useQuery( + api.lists.list.queryOptions(input, { + ...opts, + select: (data) => { + return { data: data.lists, ...listsToTree(data.lists) }; + }, + }), + ); } export function augmentBookmarkListsWithInitialData( diff --git a/packages/shared-react/hooks/reader-settings.tsx b/packages/shared-react/hooks/reader-settings.tsx new file mode 100644 index 00000000..7258ebe9 --- /dev/null +++ b/packages/shared-react/hooks/reader-settings.tsx @@ -0,0 +1,290 @@ +"use client"; + +import { + createContext, + ReactNode, + useCallback, + useContext, + useEffect, + useMemo, + useState, +} from "react"; +import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; + +import { + READER_DEFAULTS, + ReaderSettings, + ReaderSettingsPartial, +} from "@karakeep/shared/types/readers"; + +import { useTRPC } from "../trpc"; + +export interface UseReaderSettingsOptions { + /** + * Get local overrides (device-specific settings stored locally) + */ + getLocalOverrides: () => ReaderSettingsPartial; + /** + * Save local overrides to local storage + */ + saveLocalOverrides: (overrides: ReaderSettingsPartial) => void; + /** + * Optional session overrides (for live preview in web). + * If provided, these take highest precedence. + */ + sessionOverrides?: ReaderSettingsPartial; + /** + * Callback when session overrides should be cleared (after successful server save) + */ + onClearSessionOverrides?: () => void; +} + +export function useReaderSettings(options: UseReaderSettingsOptions) { + const api = useTRPC(); + const { + getLocalOverrides, + saveLocalOverrides, + sessionOverrides = {}, + onClearSessionOverrides, + } = options; + + const [localOverrides, setLocalOverrides] = useState<ReaderSettingsPartial>( + {}, + ); + const [pendingServerSave, setPendingServerSave] = + useState<ReaderSettings | null>(null); + + const { data: serverSettings } = useQuery(api.users.settings.queryOptions()); + const queryClient = useQueryClient(); + + // Load local overrides on mount + useEffect(() => { + setLocalOverrides(getLocalOverrides()); + }, [getLocalOverrides]); + + // Clear pending state when server settings match what we saved + useEffect(() => { + if (pendingServerSave && serverSettings) { + const serverMatches = + serverSettings.readerFontSize === pendingServerSave.fontSize && + // Tolerate minor float normalization differences for lineHeight + Math.abs( + (serverSettings.readerLineHeight ?? 0) - pendingServerSave.lineHeight, + ) < 1e-6 && + serverSettings.readerFontFamily === pendingServerSave.fontFamily; + if (serverMatches) { + setPendingServerSave(null); + } + } + }, [serverSettings, pendingServerSave]); + + const { mutate: updateServerSettings, isPending: isSaving } = useMutation( + api.users.updateSettings.mutationOptions({ + onSettled: async () => { + await queryClient.refetchQueries(api.users.settings.pathFilter()); + }, + }), + ); + + // Separate mutation for saving defaults (clears local overrides on success) + const { mutate: saveServerSettings, isPending: isSavingDefaults } = + useMutation( + api.users.updateSettings.mutationOptions({ + onSuccess: () => { + // Clear local and session overrides after successful server save + setLocalOverrides({}); + saveLocalOverrides({}); + onClearSessionOverrides?.(); + }, + onError: () => { + // Clear pending state so we don't show values that failed to persist + setPendingServerSave(null); + }, + onSettled: async () => { + await queryClient.refetchQueries(api.users.settings.pathFilter()); + }, + }), + ); + + // Compute effective settings with precedence: session → local → pendingSave → server → default + const settings: ReaderSettings = useMemo( + () => ({ + fontSize: + sessionOverrides.fontSize ?? + localOverrides.fontSize ?? + pendingServerSave?.fontSize ?? + serverSettings?.readerFontSize ?? + READER_DEFAULTS.fontSize, + lineHeight: + sessionOverrides.lineHeight ?? + localOverrides.lineHeight ?? + pendingServerSave?.lineHeight ?? + serverSettings?.readerLineHeight ?? + READER_DEFAULTS.lineHeight, + fontFamily: + sessionOverrides.fontFamily ?? + localOverrides.fontFamily ?? + pendingServerSave?.fontFamily ?? + serverSettings?.readerFontFamily ?? + READER_DEFAULTS.fontFamily, + }), + [sessionOverrides, localOverrides, pendingServerSave, serverSettings], + ); + + // Get the server setting values (for UI indicators) + const serverDefaults: ReaderSettingsPartial = useMemo( + () => ({ + fontSize: serverSettings?.readerFontSize ?? undefined, + lineHeight: serverSettings?.readerLineHeight ?? undefined, + fontFamily: serverSettings?.readerFontFamily ?? undefined, + }), + [serverSettings], + ); + + // Update local override (per-device, immediate) + const updateLocal = useCallback( + (updates: ReaderSettingsPartial) => { + setLocalOverrides((prev) => { + const newOverrides = { ...prev, ...updates }; + saveLocalOverrides(newOverrides); + return newOverrides; + }); + }, + [saveLocalOverrides], + ); + + // Clear a specific local override + const clearLocal = useCallback( + (key: keyof ReaderSettings) => { + setLocalOverrides((prev) => { + const { [key]: _, ...rest } = prev; + saveLocalOverrides(rest); + return rest; + }); + }, + [saveLocalOverrides], + ); + + // Clear all local overrides + const clearAllLocal = useCallback(() => { + setLocalOverrides({}); + saveLocalOverrides({}); + }, [saveLocalOverrides]); + + // Save current effective settings as server default (syncs across devices) + const saveAsDefault = useCallback( + (settingsToSave?: ReaderSettingsPartial) => { + const toSave: ReaderSettings = { + fontSize: settingsToSave?.fontSize ?? settings.fontSize, + lineHeight: settingsToSave?.lineHeight ?? settings.lineHeight, + fontFamily: settingsToSave?.fontFamily ?? settings.fontFamily, + }; + // Set pending state to prevent flicker while server syncs + setPendingServerSave(toSave); + saveServerSettings({ + readerFontSize: toSave.fontSize, + readerLineHeight: toSave.lineHeight, + readerFontFamily: toSave.fontFamily, + }); + }, + [settings, saveServerSettings], + ); + + // Clear a specific server default (set to null) + const clearDefault = useCallback( + (key: keyof ReaderSettings) => { + const serverKeyMap = { + fontSize: "readerFontSize", + lineHeight: "readerLineHeight", + fontFamily: "readerFontFamily", + } as const; + updateServerSettings({ [serverKeyMap[key]]: null }); + }, + [updateServerSettings], + ); + + // Clear all server defaults + const clearAllDefaults = useCallback(() => { + updateServerSettings({ + readerFontSize: null, + readerLineHeight: null, + readerFontFamily: null, + }); + }, [updateServerSettings]); + + // Check if there are any local overrides + const hasLocalOverrides = Object.keys(localOverrides).length > 0; + + // Check if there are any server defaults + const hasServerDefaults = + serverSettings?.readerFontSize != null || + serverSettings?.readerLineHeight != null || + serverSettings?.readerFontFamily != null; + + return { + // Current effective settings (what should be displayed) + settings, + + // Raw values for UI indicators + localOverrides, + serverDefaults, + + // Status flags + hasLocalOverrides, + hasServerDefaults, + isSaving: isSaving || isSavingDefaults, + + // Internal state setters (for web's context-based approach) + setLocalOverrides, + + // Actions + updateLocal, + clearLocal, + clearAllLocal, + saveAsDefault, + clearDefault, + clearAllDefaults, + }; +} + +// Context for sharing reader settings state across components +export type ReaderSettingsContextValue = ReturnType<typeof useReaderSettings>; + +const ReaderSettingsContext = createContext<ReaderSettingsContextValue | null>( + null, +); + +export interface ReaderSettingsProviderProps extends UseReaderSettingsOptions { + children: ReactNode; +} + +/** + * Provider that creates a single instance of reader settings state + * and shares it across all child components. + */ +export function ReaderSettingsProvider({ + children, + ...options +}: ReaderSettingsProviderProps) { + const value = useReaderSettings(options); + + return ( + <ReaderSettingsContext.Provider value={value}> + {children} + </ReaderSettingsContext.Provider> + ); +} + +/** + * Hook to access shared reader settings from context. + * Must be used within a ReaderSettingsProvider. + */ +export function useReaderSettingsContext() { + const context = useContext(ReaderSettingsContext); + if (!context) { + throw new Error( + "useReaderSettingsContext must be used within a ReaderSettingsProvider", + ); + } + return context; +} diff --git a/packages/shared-react/hooks/rules.ts b/packages/shared-react/hooks/rules.ts index 8428f883..8bca9d69 100644 --- a/packages/shared-react/hooks/rules.ts +++ b/packages/shared-react/hooks/rules.ts @@ -1,40 +1,53 @@ -import { api } from "../trpc"; +import { useMutation, useQueryClient } from "@tanstack/react-query"; + +import { useTRPC } from "../trpc"; + +type TRPCApi = ReturnType<typeof useTRPC>; export function useCreateRule( - ...opts: Parameters<typeof api.rules.create.useMutation> + opts?: Parameters<TRPCApi["rules"]["create"]["mutationOptions"]>[0], ) { - const apiUtils = api.useUtils(); - return api.rules.create.useMutation({ - ...opts[0], - onSuccess: (res, req, meta, context) => { - apiUtils.rules.list.invalidate(); - return opts[0]?.onSuccess?.(res, req, meta, context); - }, - }); + const api = useTRPC(); + const queryClient = useQueryClient(); + return useMutation( + api.rules.create.mutationOptions({ + ...opts, + onSuccess: (res, req, meta, context) => { + queryClient.invalidateQueries(api.rules.list.pathFilter()); + return opts?.onSuccess?.(res, req, meta, context); + }, + }), + ); } export function useUpdateRule( - ...opts: Parameters<typeof api.rules.update.useMutation> + opts?: Parameters<TRPCApi["rules"]["update"]["mutationOptions"]>[0], ) { - const apiUtils = api.useUtils(); - return api.rules.update.useMutation({ - ...opts[0], - onSuccess: (res, req, meta, context) => { - apiUtils.rules.list.invalidate(); - return opts[0]?.onSuccess?.(res, req, meta, context); - }, - }); + const api = useTRPC(); + const queryClient = useQueryClient(); + return useMutation( + api.rules.update.mutationOptions({ + ...opts, + onSuccess: (res, req, meta, context) => { + queryClient.invalidateQueries(api.rules.list.pathFilter()); + return opts?.onSuccess?.(res, req, meta, context); + }, + }), + ); } export function useDeleteRule( - ...opts: Parameters<typeof api.rules.delete.useMutation> + opts?: Parameters<TRPCApi["rules"]["delete"]["mutationOptions"]>[0], ) { - const apiUtils = api.useUtils(); - return api.rules.delete.useMutation({ - ...opts[0], - onSuccess: (res, req, meta, context) => { - apiUtils.rules.list.invalidate(); - return opts[0]?.onSuccess?.(res, req, meta, context); - }, - }); + const api = useTRPC(); + const queryClient = useQueryClient(); + return useMutation( + api.rules.delete.mutationOptions({ + ...opts, + onSuccess: (res, req, meta, context) => { + queryClient.invalidateQueries(api.rules.list.pathFilter()); + return opts?.onSuccess?.(res, req, meta, context); + }, + }), + ); } diff --git a/packages/shared-react/hooks/tags.ts b/packages/shared-react/hooks/tags.ts index 617c6933..a616b88a 100644 --- a/packages/shared-react/hooks/tags.ts +++ b/packages/shared-react/hooks/tags.ts @@ -1,118 +1,155 @@ -import { keepPreviousData } from "@tanstack/react-query"; +import { + keepPreviousData, + useInfiniteQuery, + useMutation, + useQuery, + useQueryClient, +} from "@tanstack/react-query"; import { ZTagListResponse } from "@karakeep/shared/types/tags"; -import { api } from "../trpc"; +import { useTRPC } from "../trpc"; + +type TRPCApi = ReturnType<typeof useTRPC>; export function usePaginatedSearchTags( - input: Parameters<typeof api.tags.list.useInfiniteQuery>[0], + input: Parameters<TRPCApi["tags"]["list"]["infiniteQueryOptions"]>[0], ) { - return api.tags.list.useInfiniteQuery(input, { - placeholderData: keepPreviousData, - getNextPageParam: (lastPage) => lastPage.nextCursor, + const api = useTRPC(); + return useInfiniteQuery({ + ...api.tags.list.infiniteQueryOptions(input, { + placeholderData: keepPreviousData, + getNextPageParam: (lastPage) => lastPage.nextCursor, + gcTime: 60_000, + }), select: (data) => ({ tags: data.pages.flatMap((page) => page.tags), }), - gcTime: 60_000, }); } -export function useTagAutocomplete<T>(opts: { +export function useTagAutocomplete<T = ZTagListResponse>(opts: { nameContains: string; - select?: (tags: ZTagListResponse) => T; + select?: (data: ZTagListResponse) => T; + enabled?: boolean; }) { - return api.tags.list.useQuery( - { - nameContains: opts.nameContains, - limit: 50, - sortBy: opts.nameContains ? "relevance" : "usage", - }, - { - select: opts.select, - placeholderData: keepPreviousData, - gcTime: opts.nameContains?.length > 0 ? 60_000 : 3_600_000, - }, - ); + const api = useTRPC(); + return useQuery({ + ...api.tags.list.queryOptions( + { + nameContains: opts.nameContains, + limit: 50, + sortBy: opts.nameContains ? "relevance" : "usage", + }, + { + placeholderData: keepPreviousData, + gcTime: opts.nameContains?.length > 0 ? 60_000 : 3_600_000, + enabled: opts.enabled, + }, + ), + select: opts.select, + }); } export function useCreateTag( - ...opts: Parameters<typeof api.tags.create.useMutation> + opts?: Parameters<TRPCApi["tags"]["create"]["mutationOptions"]>[0], ) { - const apiUtils = api.useUtils(); - - return api.tags.create.useMutation({ - ...opts[0], - onSuccess: (res, req, meta, context) => { - apiUtils.tags.list.invalidate(); - return opts[0]?.onSuccess?.(res, req, meta, context); - }, - }); + const api = useTRPC(); + const queryClient = useQueryClient(); + + return useMutation( + api.tags.create.mutationOptions({ + ...opts, + onSuccess: (res, req, meta, context) => { + queryClient.invalidateQueries(api.tags.list.pathFilter()); + return opts?.onSuccess?.(res, req, meta, context); + }, + }), + ); } export function useUpdateTag( - ...opts: Parameters<typeof api.tags.update.useMutation> + opts?: Parameters<TRPCApi["tags"]["update"]["mutationOptions"]>[0], ) { - const apiUtils = api.useUtils(); - - return api.tags.update.useMutation({ - ...opts[0], - onSuccess: (res, req, meta, context) => { - apiUtils.tags.list.invalidate(); - apiUtils.tags.get.invalidate({ tagId: res.id }); - apiUtils.bookmarks.getBookmarks.invalidate({ tagId: res.id }); - - // TODO: Maybe we can only look at the cache and invalidate only affected bookmarks - apiUtils.bookmarks.getBookmark.invalidate(); - return opts[0]?.onSuccess?.(res, req, meta, context); - }, - }); + const api = useTRPC(); + const queryClient = useQueryClient(); + + return useMutation( + api.tags.update.mutationOptions({ + ...opts, + onSuccess: (res, req, meta, context) => { + queryClient.invalidateQueries(api.tags.list.pathFilter()); + queryClient.invalidateQueries( + api.tags.get.queryFilter({ tagId: res.id }), + ); + queryClient.invalidateQueries( + api.bookmarks.getBookmarks.queryFilter({ tagId: res.id }), + ); + + // TODO: Maybe we can only look at the cache and invalidate only affected bookmarks + queryClient.invalidateQueries(api.bookmarks.getBookmark.pathFilter()); + return opts?.onSuccess?.(res, req, meta, context); + }, + }), + ); } export function useMergeTag( - ...opts: Parameters<typeof api.tags.merge.useMutation> + opts?: Parameters<TRPCApi["tags"]["merge"]["mutationOptions"]>[0], ) { - const apiUtils = api.useUtils(); - - return api.tags.merge.useMutation({ - ...opts[0], - onSuccess: (res, req, meta, context) => { - apiUtils.tags.list.invalidate(); - [res.mergedIntoTagId, ...res.deletedTags].forEach((tagId) => { - apiUtils.tags.get.invalidate({ tagId }); - apiUtils.bookmarks.getBookmarks.invalidate({ tagId }); - }); - // TODO: Maybe we can only look at the cache and invalidate only affected bookmarks - apiUtils.bookmarks.getBookmark.invalidate(); - return opts[0]?.onSuccess?.(res, req, meta, context); - }, - }); + const api = useTRPC(); + const queryClient = useQueryClient(); + + return useMutation( + api.tags.merge.mutationOptions({ + ...opts, + onSuccess: (res, req, meta, context) => { + queryClient.invalidateQueries(api.tags.list.pathFilter()); + [res.mergedIntoTagId, ...res.deletedTags].forEach((tagId) => { + queryClient.invalidateQueries(api.tags.get.queryFilter({ tagId })); + queryClient.invalidateQueries( + api.bookmarks.getBookmarks.queryFilter({ tagId }), + ); + }); + // TODO: Maybe we can only look at the cache and invalidate only affected bookmarks + queryClient.invalidateQueries(api.bookmarks.getBookmark.pathFilter()); + return opts?.onSuccess?.(res, req, meta, context); + }, + }), + ); } export function useDeleteTag( - ...opts: Parameters<typeof api.tags.delete.useMutation> + opts?: Parameters<TRPCApi["tags"]["delete"]["mutationOptions"]>[0], ) { - const apiUtils = api.useUtils(); - - return api.tags.delete.useMutation({ - ...opts[0], - onSuccess: (res, req, meta, context) => { - apiUtils.tags.list.invalidate(); - apiUtils.bookmarks.getBookmark.invalidate(); - return opts[0]?.onSuccess?.(res, req, meta, context); - }, - }); + const api = useTRPC(); + const queryClient = useQueryClient(); + + return useMutation( + api.tags.delete.mutationOptions({ + ...opts, + onSuccess: (res, req, meta, context) => { + queryClient.invalidateQueries(api.tags.list.pathFilter()); + queryClient.invalidateQueries(api.bookmarks.getBookmark.pathFilter()); + return opts?.onSuccess?.(res, req, meta, context); + }, + }), + ); } export function useDeleteUnusedTags( - ...opts: Parameters<typeof api.tags.deleteUnused.useMutation> + opts?: Parameters<TRPCApi["tags"]["deleteUnused"]["mutationOptions"]>[0], ) { - const apiUtils = api.useUtils(); - - return api.tags.deleteUnused.useMutation({ - ...opts[0], - onSuccess: (res, req, meta, context) => { - apiUtils.tags.list.invalidate(); - return opts[0]?.onSuccess?.(res, req, meta, context); - }, - }); + const api = useTRPC(); + const queryClient = useQueryClient(); + + return useMutation( + api.tags.deleteUnused.mutationOptions({ + ...opts, + onSuccess: (res, req, meta, context) => { + queryClient.invalidateQueries(api.tags.list.pathFilter()); + return opts?.onSuccess?.(res, req, meta, context); + }, + }), + ); } diff --git a/packages/shared-react/hooks/users.ts b/packages/shared-react/hooks/users.ts index eecde3f1..221681a4 100644 --- a/packages/shared-react/hooks/users.ts +++ b/packages/shared-react/hooks/users.ts @@ -1,24 +1,49 @@ -import { api } from "../trpc"; +import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; + +import { useTRPC } from "../trpc"; + +type TRPCApi = ReturnType<typeof useTRPC>; export function useUpdateUserSettings( - ...opts: Parameters<typeof api.users.updateSettings.useMutation> + opts?: Parameters<TRPCApi["users"]["updateSettings"]["mutationOptions"]>[0], +) { + const api = useTRPC(); + const queryClient = useQueryClient(); + return useMutation( + api.users.updateSettings.mutationOptions({ + ...opts, + onSuccess: (res, req, meta, context) => { + queryClient.invalidateQueries(api.users.settings.pathFilter()); + return opts?.onSuccess?.(res, req, meta, context); + }, + }), + ); +} + +export function useUpdateUserAvatar( + opts?: Parameters<TRPCApi["users"]["updateAvatar"]["mutationOptions"]>[0], ) { - const apiUtils = api.useUtils(); - return api.users.updateSettings.useMutation({ - ...opts[0], - onSuccess: (res, req, meta, context) => { - apiUtils.users.settings.invalidate(); - return opts[0]?.onSuccess?.(res, req, meta, context); - }, - }); + const api = useTRPC(); + const queryClient = useQueryClient(); + return useMutation( + api.users.updateAvatar.mutationOptions({ + ...opts, + onSuccess: (res, req, meta, context) => { + queryClient.invalidateQueries(api.users.whoami.pathFilter()); + return opts?.onSuccess?.(res, req, meta, context); + }, + }), + ); } export function useDeleteAccount( - ...opts: Parameters<typeof api.users.deleteAccount.useMutation> + opts?: Parameters<TRPCApi["users"]["deleteAccount"]["mutationOptions"]>[0], ) { - return api.users.deleteAccount.useMutation(opts[0]); + const api = useTRPC(); + return useMutation(api.users.deleteAccount.mutationOptions(opts)); } export function useWhoAmI() { - return api.users.whoami.useQuery(); + const api = useTRPC(); + return useQuery(api.users.whoami.queryOptions()); } diff --git a/packages/shared-react/package.json b/packages/shared-react/package.json index 1c8a2f85..ce29a24e 100644 --- a/packages/shared-react/package.json +++ b/packages/shared-react/package.json @@ -8,7 +8,8 @@ "@karakeep/shared": "workspace:^0.1.0", "@karakeep/trpc": "workspace:^0.1.0", "@tanstack/react-query": "5.90.2", - "@trpc/client": "^11.4.3", + "@trpc/client": "^11.9.0", + "@trpc/tanstack-react-query": "^11.9.0", "superjson": "^2.2.1" }, "devDependencies": { @@ -16,7 +17,7 @@ "@karakeep/tsconfig": "workspace:^0.1.0" }, "peerDependencies": { - "react": "^19.0.0", + "react": "^19.2.1", "react-native": "0.79.5" }, "peerDependenciesMeta": { diff --git a/packages/shared-react/providers/trpc-provider.tsx b/packages/shared-react/providers/trpc-provider.tsx index 696bf195..2c41aa11 100644 --- a/packages/shared-react/providers/trpc-provider.tsx +++ b/packages/shared-react/providers/trpc-provider.tsx @@ -1,9 +1,11 @@ import { useMemo } from "react"; import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; -import { httpBatchLink } from "@trpc/client"; +import { createTRPCClient, httpBatchLink } from "@trpc/client"; import superjson from "superjson"; -import { api } from "../trpc"; +import type { AppRouter } from "@karakeep/trpc/routers/_app"; + +import { TRPCProvider } from "../trpc"; interface Settings { apiKey?: string; @@ -12,7 +14,7 @@ interface Settings { } function getTRPCClient(settings: Settings) { - return api.createClient({ + return createTRPCClient<AppRouter>({ links: [ httpBatchLink({ url: `${settings.address}/api/trpc`, @@ -31,7 +33,7 @@ function getTRPCClient(settings: Settings) { }); } -export function TRPCProvider({ +export function TRPCSettingsProvider({ settings, children, }: { @@ -42,8 +44,10 @@ export function TRPCProvider({ const trpcClient = useMemo(() => getTRPCClient(settings), [settings]); return ( - <api.Provider client={trpcClient} queryClient={queryClient}> - <QueryClientProvider client={queryClient}>{children}</QueryClientProvider> - </api.Provider> + <QueryClientProvider client={queryClient}> + <TRPCProvider trpcClient={trpcClient} queryClient={queryClient}> + {children} + </TRPCProvider> + </QueryClientProvider> ); } diff --git a/packages/shared-react/trpc.ts b/packages/shared-react/trpc.ts index 1478684f..2a36b257 100644 --- a/packages/shared-react/trpc.ts +++ b/packages/shared-react/trpc.ts @@ -1,7 +1,7 @@ "use client"; -import { createTRPCReact } from "@trpc/react-query"; +import { createTRPCContext } from "@trpc/tanstack-react-query"; import type { AppRouter } from "@karakeep/trpc/routers/_app"; -export const api = createTRPCReact<AppRouter>(); +export const { TRPCProvider, useTRPC } = createTRPCContext<AppRouter>(); |
