From 65f6e83f11c82b0ec762e11f3392a80e614ee69a Mon Sep 17 00:00:00 2001
From: Mohamed Bassem
Date: Sun, 1 Feb 2026 12:29:54 +0000
Subject: refactor: migrate trpc to the new react query integration mode
(#2438)
* refactor: migrate trpc to the new react query integration mode
* more fixes
* more migrations
* upgrade trpc client
---
apps/browser-extension/package.json | 6 +-
apps/browser-extension/src/OptionsPage.tsx | 22 +-
apps/browser-extension/src/SavePage.tsx | 38 +-
apps/browser-extension/src/SignInPage.tsx | 32 +-
.../src/components/BookmarkLists.tsx | 8 +-
.../src/components/ListsSelector.tsx | 12 +-
.../src/components/TagsSelector.tsx | 6 +-
apps/browser-extension/src/utils/providers.tsx | 6 +-
apps/browser-extension/src/utils/trpc.ts | 4 +-
apps/cli/package.json | 4 +-
apps/mobile/app/dashboard/(tabs)/highlights.tsx | 22 +-
apps/mobile/app/dashboard/(tabs)/lists.tsx | 12 +-
apps/mobile/app/dashboard/(tabs)/settings.tsx | 6 +-
apps/mobile/app/dashboard/(tabs)/tags.tsx | 8 +-
.../app/dashboard/bookmarks/[slug]/index.tsx | 14 +-
.../dashboard/bookmarks/[slug]/manage_lists.tsx | 22 +-
.../app/dashboard/bookmarks/[slug]/manage_tags.tsx | 38 +-
apps/mobile/app/dashboard/lists/[slug]/edit.tsx | 12 +-
apps/mobile/app/dashboard/lists/[slug]/index.tsx | 31 +-
apps/mobile/app/dashboard/search.tsx | 33 +-
apps/mobile/app/dashboard/tags/[slug].tsx | 10 +-
apps/mobile/app/sharing.tsx | 25 +-
apps/mobile/app/signin.tsx | 58 +--
apps/mobile/components/bookmarks/BookmarkCard.tsx | 32 +-
.../components/bookmarks/BookmarkLinkPreview.tsx | 14 +-
.../components/bookmarks/UpdatingBookmarkList.tsx | 24 +-
.../mobile/components/highlights/HighlightCard.tsx | 20 +-
apps/mobile/lib/providers.tsx | 6 +-
apps/mobile/lib/session.ts | 8 +-
apps/mobile/lib/trpc.ts | 8 +-
apps/mobile/lib/upload.ts | 17 +-
apps/web/app/check-email/page.tsx | 26 +-
apps/web/app/reader/[bookmarkId]/page.tsx | 20 +-
apps/web/app/settings/assets/page.tsx | 20 +-
apps/web/app/settings/broken-links/page.tsx | 16 +-
apps/web/app/settings/rules/page.tsx | 14 +-
apps/web/app/settings/stats/page.tsx | 12 +-
apps/web/app/verify-email/page.tsx | 58 +--
apps/web/components/admin/AddUserDialog.tsx | 430 +++++++++++----------
apps/web/components/admin/AdminNotices.tsx | 6 +-
apps/web/components/admin/BackgroundJobs.tsx | 131 ++++---
apps/web/components/admin/BasicStats.tsx | 13 +-
apps/web/components/admin/BookmarkDebugger.tsx | 116 +++---
apps/web/components/admin/CreateInviteDialog.tsx | 44 ++-
apps/web/components/admin/InvitesList.tsx | 30 +-
apps/web/components/admin/ResetPasswordDialog.tsx | 294 +++++++-------
apps/web/components/admin/ServiceConnections.tsx | 12 +-
apps/web/components/admin/UpdateUserDialog.tsx | 48 +--
apps/web/components/admin/UserList.tsx | 27 +-
.../dashboard/bookmarks/BookmarkCard.tsx | 32 +-
.../bookmarks/BookmarkLayoutAdaptingCard.tsx | 22 +-
.../dashboard/bookmarks/BulkTagModal.tsx | 12 +-
.../dashboard/bookmarks/EditBookmarkDialog.tsx | 11 +-
.../dashboard/bookmarks/ManageListsModal.tsx | 11 +-
.../components/dashboard/bookmarks/TagsEditor.tsx | 12 +-
.../dashboard/bookmarks/UpdatableBookmarksGrid.tsx | 28 +-
.../action-buttons/ArchiveBookmarkButton.tsx | 20 +-
.../dashboard/cleanups/TagDuplicationDetention.tsx | 16 +-
.../components/dashboard/feeds/FeedSelector.tsx | 12 +-
.../dashboard/highlights/AllHighlights.tsx | 48 ++-
.../dashboard/lists/CollapsibleBookmarkLists.tsx | 13 +-
.../lists/LeaveListConfirmationDialog.tsx | 54 +--
apps/web/components/dashboard/lists/ListHeader.tsx | 38 +-
.../dashboard/lists/ManageCollaboratorsModal.tsx | 154 ++++----
.../dashboard/lists/PendingInvitationsCard.tsx | 83 ++--
apps/web/components/dashboard/lists/RssLink.tsx | 33 +-
.../dashboard/preview/BookmarkPreview.tsx | 32 +-
.../components/dashboard/preview/HighlightsBox.tsx | 9 +-
.../components/dashboard/preview/ReaderView.tsx | 19 +-
.../dashboard/search/useSearchAutocomplete.ts | 12 +-
.../sidebar/InvitationNotificationBadge.tsx | 11 +-
.../components/dashboard/tags/TagAutocomplete.tsx | 11 +-
apps/web/components/invite/InviteAcceptForm.tsx | 10 +-
.../components/public/lists/PublicBookmarkGrid.tsx | 28 +-
apps/web/components/settings/AISettings.tsx | 44 ++-
apps/web/components/settings/AddApiKey.tsx | 30 +-
apps/web/components/settings/BackupSettings.tsx | 47 ++-
apps/web/components/settings/ChangePassword.tsx | 41 +-
apps/web/components/settings/DeleteApiKey.tsx | 18 +-
apps/web/components/settings/FeedSettings.tsx | 85 ++--
apps/web/components/settings/RegenerateApiKey.tsx | 32 +-
.../components/settings/SubscriptionSettings.tsx | 31 +-
apps/web/components/settings/WebhookSettings.tsx | 56 +--
apps/web/components/signin/ForgotPasswordForm.tsx | 8 +-
apps/web/components/signin/ResetPasswordForm.tsx | 8 +-
apps/web/components/signup/SignUpForm.tsx | 6 +-
apps/web/components/subscription/QuotaProgress.tsx | 9 +-
apps/web/components/utils/ValidAccountCheck.tsx | 22 +-
apps/web/components/wrapped/WrappedModal.tsx | 20 +-
apps/web/lib/hooks/bookmark-search.ts | 29 +-
apps/web/lib/hooks/useBookmarkImport.ts | 12 +-
apps/web/lib/hooks/useImportSessions.ts | 105 ++---
apps/web/lib/providers.tsx | 15 +-
apps/web/lib/trpc.tsx | 8 +-
apps/web/lib/userSettings.tsx | 12 +-
apps/web/package.json | 6 +-
96 files changed, 1845 insertions(+), 1405 deletions(-)
(limited to 'apps')
diff --git a/apps/browser-extension/package.json b/apps/browser-extension/package.json
index d2a699c3..575bb8b7 100644
--- a/apps/browser-extension/package.json
+++ b/apps/browser-extension/package.json
@@ -24,9 +24,9 @@
"@tanstack/query-async-storage-persister": "5.90.2",
"@tanstack/react-query": "5.90.2",
"@tanstack/react-query-persist-client": "5.90.2",
- "@trpc/client": "^11.4.3",
- "@trpc/react-query": "^11.4.3",
- "@trpc/server": "^11.4.3",
+ "@trpc/client": "^11.9.0",
+ "@trpc/server": "^11.9.0",
+ "@trpc/tanstack-react-query": "^11.9.0",
"@uidotdev/usehooks": "^2.4.1",
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.0",
diff --git a/apps/browser-extension/src/OptionsPage.tsx b/apps/browser-extension/src/OptionsPage.tsx
index cac32eff..1b1dc8b6 100644
--- a/apps/browser-extension/src/OptionsPage.tsx
+++ b/apps/browser-extension/src/OptionsPage.tsx
@@ -1,4 +1,5 @@
import React, { useEffect } from "react";
+import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { useNavigate } from "react-router-dom";
import { Button } from "./components/ui/button";
@@ -17,27 +18,32 @@ import usePluginSettings, {
DEFAULT_BADGE_CACHE_EXPIRE_MS,
} from "./utils/settings";
import { useTheme } from "./utils/ThemeProvider";
-import { api } from "./utils/trpc";
+import { useTRPC } from "./utils/trpc";
export default function OptionsPage() {
+ const api = useTRPC();
+ const queryClient = useQueryClient();
const navigate = useNavigate();
const { settings, setSettings } = usePluginSettings();
const { setTheme, theme } = useTheme();
- const { data: whoami, error: whoAmIError } = api.users.whoami.useQuery(
- undefined,
- {
+ const { data: whoami, error: whoAmIError } = useQuery(
+ api.users.whoami.queryOptions(undefined, {
enabled: settings.address != "",
- },
+ }),
);
- const { mutate: deleteKey } = api.apiKeys.revoke.useMutation();
+ const { mutate: deleteKey } = useMutation(
+ api.apiKeys.revoke.mutationOptions(),
+ );
- const invalidateWhoami = api.useUtils().users.whoami.refetch;
+ const invalidateWhoami = () => {
+ queryClient.refetchQueries(api.users.whoami.queryFilter());
+ };
useEffect(() => {
invalidateWhoami();
- }, [settings, invalidateWhoami]);
+ }, [settings]);
let loggedInMessage: React.ReactNode;
if (whoAmIError) {
diff --git a/apps/browser-extension/src/SavePage.tsx b/apps/browser-extension/src/SavePage.tsx
index b4b9ce95..5f55e164 100644
--- a/apps/browser-extension/src/SavePage.tsx
+++ b/apps/browser-extension/src/SavePage.tsx
@@ -1,4 +1,5 @@
import { useEffect, useState } from "react";
+import { useMutation } from "@tanstack/react-query";
import { Navigate } from "react-router-dom";
import {
@@ -9,33 +10,36 @@ import {
import { NEW_BOOKMARK_REQUEST_KEY_NAME } from "./background/protocol";
import Spinner from "./Spinner";
-import { api } from "./utils/trpc";
+import { useTRPC } from "./utils/trpc";
import { MessageType } from "./utils/type";
import { isHttpUrl } from "./utils/url";
export default function SavePage() {
+ const api = useTRPC();
const [error, setError] = useState(undefined);
const {
data,
mutate: createBookmark,
status,
- } = api.bookmarks.createBookmark.useMutation({
- onError: (e) => {
- setError("Something went wrong: " + e.message);
- },
- onSuccess: async () => {
- // After successful creation, update badge cache and notify background
- const [currentTab] = await chrome.tabs.query({
- active: true,
- lastFocusedWindow: true,
- });
- await chrome.runtime.sendMessage({
- type: MessageType.BOOKMARK_REFRESH_BADGE,
- currentTab: currentTab,
- });
- },
- });
+ } = useMutation(
+ api.bookmarks.createBookmark.mutationOptions({
+ onError: (e) => {
+ setError("Something went wrong: " + e.message);
+ },
+ onSuccess: async () => {
+ // After successful creation, update badge cache and notify background
+ const [currentTab] = await chrome.tabs.query({
+ active: true,
+ lastFocusedWindow: true,
+ });
+ await chrome.runtime.sendMessage({
+ type: MessageType.BOOKMARK_REFRESH_BADGE,
+ currentTab: currentTab,
+ });
+ },
+ }),
+ );
useEffect(() => {
async function getNewBookmarkRequestFromBackgroundScriptIfAny(): Promise {
const { [NEW_BOOKMARK_REQUEST_KEY_NAME]: req } =
diff --git a/apps/browser-extension/src/SignInPage.tsx b/apps/browser-extension/src/SignInPage.tsx
index 6cf8b35d..8a7229b6 100644
--- a/apps/browser-extension/src/SignInPage.tsx
+++ b/apps/browser-extension/src/SignInPage.tsx
@@ -1,11 +1,12 @@
import { useState } from "react";
+import { useMutation } from "@tanstack/react-query";
import { useNavigate } from "react-router-dom";
import { Button } from "./components/ui/button";
import { Input } from "./components/ui/input";
import Logo from "./Logo";
import usePluginSettings from "./utils/settings";
-import { api } from "./utils/trpc";
+import { useTRPC } from "./utils/trpc";
const enum LoginState {
NONE = "NONE",
@@ -14,6 +15,7 @@ const enum LoginState {
}
export default function SignInPage() {
+ const api = useTRPC();
const navigate = useNavigate();
const { settings, setSettings } = usePluginSettings();
@@ -21,23 +23,27 @@ export default function SignInPage() {
mutate: login,
error: usernamePasswordError,
isPending: userNamePasswordRequestIsPending,
- } = api.apiKeys.exchange.useMutation({
- onSuccess: (resp) => {
- setSettings((s) => ({ ...s, apiKey: resp.key, apiKeyId: resp.id }));
- navigate("/options");
- },
- });
+ } = useMutation(
+ api.apiKeys.exchange.mutationOptions({
+ onSuccess: (resp) => {
+ setSettings((s) => ({ ...s, apiKey: resp.key, apiKeyId: resp.id }));
+ navigate("/options");
+ },
+ }),
+ );
const {
mutate: validateApiKey,
error: apiKeyValidationError,
isPending: apiKeyValueRequestIsPending,
- } = api.apiKeys.validate.useMutation({
- onSuccess: () => {
- setSettings((s) => ({ ...s, apiKey: apiKeyFormData.apiKey }));
- navigate("/options");
- },
- });
+ } = useMutation(
+ api.apiKeys.validate.mutationOptions({
+ onSuccess: () => {
+ setSettings((s) => ({ ...s, apiKey: apiKeyFormData.apiKey }));
+ navigate("/options");
+ },
+ }),
+ );
const [lastLoginAttemptSource, setLastLoginAttemptSource] =
useState(LoginState.NONE);
diff --git a/apps/browser-extension/src/components/BookmarkLists.tsx b/apps/browser-extension/src/components/BookmarkLists.tsx
index 1d70d257..8debef5c 100644
--- a/apps/browser-extension/src/components/BookmarkLists.tsx
+++ b/apps/browser-extension/src/components/BookmarkLists.tsx
@@ -1,3 +1,4 @@
+import { useQuery } from "@tanstack/react-query";
import { X } from "lucide-react";
import {
@@ -5,15 +6,18 @@ import {
useRemoveBookmarkFromList,
} from "@karakeep/shared-react/hooks/lists";
-import { api } from "../utils/trpc";
+import { useTRPC } from "../utils/trpc";
import { Button } from "./ui/button";
export default function BookmarkLists({ bookmarkId }: { bookmarkId: string }) {
+ const api = useTRPC();
const { data: allLists } = useBookmarkLists();
const { mutate: deleteFromList } = useRemoveBookmarkFromList();
- const { data: lists } = api.lists.getListsOfBookmark.useQuery({ bookmarkId });
+ const { data: lists } = useQuery(
+ api.lists.getListsOfBookmark.queryOptions({ bookmarkId }),
+ );
if (!lists || !allLists) {
return null;
}
diff --git a/apps/browser-extension/src/components/ListsSelector.tsx b/apps/browser-extension/src/components/ListsSelector.tsx
index 86c151d1..b27e866a 100644
--- a/apps/browser-extension/src/components/ListsSelector.tsx
+++ b/apps/browser-extension/src/components/ListsSelector.tsx
@@ -1,4 +1,5 @@
import * as React from "react";
+import { useQuery } from "@tanstack/react-query";
import { useSet } from "@uidotdev/usehooks";
import { Check, ChevronsUpDown } from "lucide-react";
@@ -9,7 +10,7 @@ import {
} from "@karakeep/shared-react/hooks/lists";
import { cn } from "../utils/css";
-import { api } from "../utils/trpc";
+import { useTRPC } from "../utils/trpc";
import { Button } from "./ui/button";
import {
Command,
@@ -23,14 +24,17 @@ import { DynamicPopoverContent } from "./ui/dynamic-popover";
import { Popover, PopoverTrigger } from "./ui/popover";
export function ListsSelector({ bookmarkId }: { bookmarkId: string }) {
+ const api = useTRPC();
const currentlyUpdating = useSet();
const [open, setOpen] = React.useState(false);
const { mutate: addToList } = useAddBookmarkToList();
const { mutate: removeFromList } = useRemoveBookmarkFromList();
- const { data: existingLists } = api.lists.getListsOfBookmark.useQuery({
- bookmarkId,
- });
+ const { data: existingLists } = useQuery(
+ api.lists.getListsOfBookmark.queryOptions({
+ bookmarkId,
+ }),
+ );
const { data: allLists } = useBookmarkLists();
diff --git a/apps/browser-extension/src/components/TagsSelector.tsx b/apps/browser-extension/src/components/TagsSelector.tsx
index ce404ac8..30cdcafc 100644
--- a/apps/browser-extension/src/components/TagsSelector.tsx
+++ b/apps/browser-extension/src/components/TagsSelector.tsx
@@ -1,4 +1,5 @@
import * as React from "react";
+import { useQuery } from "@tanstack/react-query";
import { useSet } from "@uidotdev/usehooks";
import { Check, ChevronsUpDown, Plus } from "lucide-react";
@@ -8,7 +9,7 @@ import {
} from "@karakeep/shared-react/hooks/bookmarks";
import { cn } from "../utils/css";
-import { api } from "../utils/trpc";
+import { useTRPC } from "../utils/trpc";
import { Button } from "./ui/button";
import {
Command,
@@ -22,7 +23,8 @@ import { DynamicPopoverContent } from "./ui/dynamic-popover";
import { Popover, PopoverTrigger } from "./ui/popover";
export function TagsSelector({ bookmarkId }: { bookmarkId: string }) {
- const { data: allTags } = api.tags.list.useQuery({});
+ const api = useTRPC();
+ const { data: allTags } = useQuery(api.tags.list.queryOptions({}));
const { data: bookmark } = useAutoRefreshingBookmarkQuery({ bookmarkId });
const existingTagIds = new Set(bookmark?.tags.map((t) => t.id) ?? []);
diff --git a/apps/browser-extension/src/utils/providers.tsx b/apps/browser-extension/src/utils/providers.tsx
index 86489d6d..4c09084d 100644
--- a/apps/browser-extension/src/utils/providers.tsx
+++ b/apps/browser-extension/src/utils/providers.tsx
@@ -1,4 +1,4 @@
-import { TRPCProvider } from "@karakeep/shared-react/providers/trpc-provider";
+import { TRPCSettingsProvider } from "@karakeep/shared-react/providers/trpc-provider";
import usePluginSettings from "./settings";
import { ThemeProvider } from "./ThemeProvider";
@@ -7,8 +7,8 @@ export function Providers({ children }: { children: React.ReactNode }) {
const { settings } = usePluginSettings();
return (
-
+
{children}
-
+
);
}
diff --git a/apps/browser-extension/src/utils/trpc.ts b/apps/browser-extension/src/utils/trpc.ts
index b3215d9d..73fe68c5 100644
--- a/apps/browser-extension/src/utils/trpc.ts
+++ b/apps/browser-extension/src/utils/trpc.ts
@@ -1,7 +1,7 @@
import { QueryClient } from "@tanstack/react-query";
import { persistQueryClient } from "@tanstack/react-query-persist-client";
import { createTRPCClient, httpBatchLink } from "@trpc/client";
-import { createTRPCReact } from "@trpc/react-query";
+import { createTRPCContext } from "@trpc/tanstack-react-query";
import superjson from "superjson";
import type { AppRouter } from "@karakeep/trpc/routers/_app";
@@ -9,7 +9,7 @@ import type { AppRouter } from "@karakeep/trpc/routers/_app";
import { getPluginSettings } from "./settings";
import { createChromeStorage } from "./storagePersister";
-export const api = createTRPCReact();
+export const { TRPCProvider, useTRPC } = createTRPCContext();
let apiClient: ReturnType> | null = null;
let queryClient: QueryClient | null = null;
diff --git a/apps/cli/package.json b/apps/cli/package.json
index ca085997..04c72f81 100644
--- a/apps/cli/package.json
+++ b/apps/cli/package.json
@@ -20,8 +20,8 @@
"@karakeep/shared": "workspace:^0.1.0",
"@karakeep/trpc": "workspace:^0.1.0",
"@karakeep/tsconfig": "workspace:^0.1.0",
- "@trpc/client": "^11.4.3",
- "@trpc/server": "^11.4.3",
+ "@trpc/client": "^11.9.0",
+ "@trpc/server": "^11.9.0",
"@tsconfig/node22": "^22.0.0",
"chalk": "^5.3.0",
"commander": "^12.0.0",
diff --git a/apps/mobile/app/dashboard/(tabs)/highlights.tsx b/apps/mobile/app/dashboard/(tabs)/highlights.tsx
index 7879081b..8a0a8ae3 100644
--- a/apps/mobile/app/dashboard/(tabs)/highlights.tsx
+++ b/apps/mobile/app/dashboard/(tabs)/highlights.tsx
@@ -4,11 +4,13 @@ import HighlightList from "@/components/highlights/HighlightList";
import CustomSafeAreaView from "@/components/ui/CustomSafeAreaView";
import FullPageSpinner from "@/components/ui/FullPageSpinner";
import PageTitle from "@/components/ui/PageTitle";
+import { useInfiniteQuery, useQueryClient } from "@tanstack/react-query";
-import { api } from "@karakeep/shared-react/trpc";
+import { useTRPC } from "@karakeep/shared-react/trpc";
export default function Highlights() {
- const apiUtils = api.useUtils();
+ const api = useTRPC();
+ const queryClient = useQueryClient();
const {
data,
isPending,
@@ -17,12 +19,14 @@ export default function Highlights() {
fetchNextPage,
isFetchingNextPage,
refetch,
- } = api.highlights.getAll.useInfiniteQuery(
- {},
- {
- initialCursor: null,
- getNextPageParam: (lastPage) => lastPage.nextCursor,
- },
+ } = useInfiniteQuery(
+ api.highlights.getAll.infiniteQueryOptions(
+ {},
+ {
+ initialCursor: null,
+ getNextPageParam: (lastPage) => lastPage.nextCursor,
+ },
+ ),
);
if (error) {
@@ -34,7 +38,7 @@ export default function Highlights() {
}
const onRefresh = () => {
- apiUtils.highlights.getAll.invalidate();
+ queryClient.invalidateQueries(api.highlights.getAll.pathFilter());
};
return (
diff --git a/apps/mobile/app/dashboard/(tabs)/lists.tsx b/apps/mobile/app/dashboard/(tabs)/lists.tsx
index 45c23a28..5719c67c 100644
--- a/apps/mobile/app/dashboard/(tabs)/lists.tsx
+++ b/apps/mobile/app/dashboard/(tabs)/lists.tsx
@@ -8,9 +8,10 @@ import CustomSafeAreaView from "@/components/ui/CustomSafeAreaView";
import FullPageSpinner from "@/components/ui/FullPageSpinner";
import PageTitle from "@/components/ui/PageTitle";
import { Text } from "@/components/ui/Text";
-import { api } from "@/lib/trpc";
+import { useTRPC } from "@/lib/trpc";
import { useColorScheme } from "@/lib/useColorScheme";
import { condProps } from "@/lib/utils";
+import { useQuery, useQueryClient } from "@tanstack/react-query";
import { Plus } from "lucide-react-native";
import { useBookmarkLists } from "@karakeep/shared-react/hooks/lists";
@@ -84,8 +85,9 @@ export default function Lists() {
const [showChildrenOf, setShowChildrenOf] = useState>(
{},
);
- const apiUtils = api.useUtils();
- const { data: listStats } = api.lists.stats.useQuery();
+ const api = useTRPC();
+ const queryClient = useQueryClient();
+ const { data: listStats } = useQuery(api.lists.stats.queryOptions());
// Check if there are any shared lists
const hasSharedLists = useMemo(() => {
@@ -116,8 +118,8 @@ export default function Lists() {
}
const onRefresh = () => {
- apiUtils.lists.list.invalidate();
- apiUtils.lists.stats.invalidate();
+ queryClient.invalidateQueries(api.lists.list.pathFilter());
+ queryClient.invalidateQueries(api.lists.stats.pathFilter());
};
const links: ListLink[] = [
diff --git a/apps/mobile/app/dashboard/(tabs)/settings.tsx b/apps/mobile/app/dashboard/(tabs)/settings.tsx
index 106baec5..2610aa37 100644
--- a/apps/mobile/app/dashboard/(tabs)/settings.tsx
+++ b/apps/mobile/app/dashboard/(tabs)/settings.tsx
@@ -13,7 +13,8 @@ import { Text } from "@/components/ui/Text";
import { useServerVersion } from "@/lib/hooks";
import { useSession } from "@/lib/session";
import useAppSettings from "@/lib/settings";
-import { api } from "@/lib/trpc";
+import { useTRPC } from "@/lib/trpc";
+import { useQuery } from "@tanstack/react-query";
export default function Dashboard() {
const { logout } = useSession();
@@ -22,6 +23,7 @@ export default function Dashboard() {
setSettings,
isLoading: isSettingsLoading,
} = useAppSettings();
+ const api = useTRPC();
const imageQuality = useSharedValue(0);
const imageQualityMin = useSharedValue(0);
@@ -31,7 +33,7 @@ export default function Dashboard() {
imageQuality.value = settings.imageQuality * 100;
}, [settings]);
- const { data, error } = api.users.whoami.useQuery();
+ const { data, error } = useQuery(api.users.whoami.queryOptions());
const {
data: serverVersion,
isLoading: isServerVersionLoading,
diff --git a/apps/mobile/app/dashboard/(tabs)/tags.tsx b/apps/mobile/app/dashboard/(tabs)/tags.tsx
index c0ac2d49..470ff3f3 100644
--- a/apps/mobile/app/dashboard/(tabs)/tags.tsx
+++ b/apps/mobile/app/dashboard/(tabs)/tags.tsx
@@ -8,7 +8,8 @@ import FullPageSpinner from "@/components/ui/FullPageSpinner";
import PageTitle from "@/components/ui/PageTitle";
import { SearchInput } from "@/components/ui/SearchInput";
import { Text } from "@/components/ui/Text";
-import { api } from "@/lib/trpc";
+import { useTRPC } from "@/lib/trpc";
+import { useQueryClient } from "@tanstack/react-query";
import { usePaginatedSearchTags } from "@karakeep/shared-react/hooks/tags";
import { useDebounce } from "@karakeep/shared-react/hooks/use-debounce";
@@ -23,7 +24,8 @@ interface TagItem {
export default function Tags() {
const [refreshing, setRefreshing] = useState(false);
const [searchQuery, setSearchQuery] = useState("");
- const apiUtils = api.useUtils();
+ const api = useTRPC();
+ const queryClient = useQueryClient();
// Debounce search query to avoid too many API calls
const debouncedSearch = useDebounce(searchQuery, 300);
@@ -56,7 +58,7 @@ export default function Tags() {
}
const onRefresh = () => {
- apiUtils.tags.list.invalidate();
+ queryClient.invalidateQueries(api.tags.list.pathFilter());
};
const tags: TagItem[] = data.tags.map((tag) => ({
diff --git a/apps/mobile/app/dashboard/bookmarks/[slug]/index.tsx b/apps/mobile/app/dashboard/bookmarks/[slug]/index.tsx
index 8fd04115..567ac605 100644
--- a/apps/mobile/app/dashboard/bookmarks/[slug]/index.tsx
+++ b/apps/mobile/app/dashboard/bookmarks/[slug]/index.tsx
@@ -12,7 +12,8 @@ import BottomActions from "@/components/bookmarks/BottomActions";
import FullPageError from "@/components/FullPageError";
import FullPageSpinner from "@/components/ui/FullPageSpinner";
import useAppSettings from "@/lib/settings";
-import { api } from "@/lib/trpc";
+import { useTRPC } from "@/lib/trpc";
+import { useQuery } from "@tanstack/react-query";
import { Settings } from "lucide-react-native";
import { useColorScheme } from "nativewind";
@@ -25,6 +26,7 @@ export default function BookmarkView() {
const { colorScheme } = useColorScheme();
const isDark = colorScheme === "dark";
const { settings } = useAppSettings();
+ const api = useTRPC();
const [bookmarkLinkType, setBookmarkLinkType] = useState(
settings.defaultBookmarkView,
@@ -38,10 +40,12 @@ export default function BookmarkView() {
data: bookmark,
error,
refetch,
- } = api.bookmarks.getBookmark.useQuery({
- bookmarkId: slug,
- includeContent: false,
- });
+ } = useQuery(
+ api.bookmarks.getBookmark.queryOptions({
+ bookmarkId: slug,
+ includeContent: false,
+ }),
+ );
if (error) {
return ;
diff --git a/apps/mobile/app/dashboard/bookmarks/[slug]/manage_lists.tsx b/apps/mobile/app/dashboard/bookmarks/[slug]/manage_lists.tsx
index 8402bb0b..1070207b 100644
--- a/apps/mobile/app/dashboard/bookmarks/[slug]/manage_lists.tsx
+++ b/apps/mobile/app/dashboard/bookmarks/[slug]/manage_lists.tsx
@@ -5,15 +5,18 @@ import { useLocalSearchParams } from "expo-router";
import CustomSafeAreaView from "@/components/ui/CustomSafeAreaView";
import { Text } from "@/components/ui/Text";
import { useToast } from "@/components/ui/Toast";
+import { useQuery } from "@tanstack/react-query";
+import type { ZBookmarkList } from "@karakeep/shared/types/lists";
import {
useAddBookmarkToList,
useBookmarkLists,
useRemoveBookmarkFromList,
} from "@karakeep/shared-react/hooks/lists";
-import { api } from "@karakeep/shared-react/trpc";
+import { useTRPC } from "@karakeep/shared-react/trpc";
const ListPickerPage = () => {
+ const api = useTRPC();
const { slug: bookmarkId } = useLocalSearchParams();
if (typeof bookmarkId !== "string") {
throw new Error("Unexpected param type");
@@ -26,13 +29,16 @@ const ListPickerPage = () => {
showProgress: false,
});
};
- const { data: existingLists } = api.lists.getListsOfBookmark.useQuery(
- {
- bookmarkId,
- },
- {
- select: (data) => new Set(data.lists.map((l) => l.id)),
- },
+ const { data: existingLists } = useQuery(
+ api.lists.getListsOfBookmark.queryOptions(
+ {
+ bookmarkId,
+ },
+ {
+ select: (data: { lists: ZBookmarkList[] }) =>
+ new Set(data.lists.map((l) => l.id)),
+ },
+ ),
);
const { data } = useBookmarkLists();
diff --git a/apps/mobile/app/dashboard/bookmarks/[slug]/manage_tags.tsx b/apps/mobile/app/dashboard/bookmarks/[slug]/manage_tags.tsx
index 984bc224..64d057f2 100644
--- a/apps/mobile/app/dashboard/bookmarks/[slug]/manage_tags.tsx
+++ b/apps/mobile/app/dashboard/bookmarks/[slug]/manage_tags.tsx
@@ -6,17 +6,19 @@ import FullPageSpinner from "@/components/ui/FullPageSpinner";
import { Text } from "@/components/ui/Text";
import { useToast } from "@/components/ui/Toast";
import { useColorScheme } from "@/lib/useColorScheme";
+import { useQuery } from "@tanstack/react-query";
import { Check, Plus } from "lucide-react-native";
import {
useAutoRefreshingBookmarkQuery,
useUpdateBookmarkTags,
} from "@karakeep/shared-react/hooks/bookmarks";
-import { api } from "@karakeep/shared-react/trpc";
+import { useTRPC } from "@karakeep/shared-react/trpc";
const NEW_TAG_ID = "new-tag";
const ListPickerPage = () => {
+ const api = useTRPC();
const { colors } = useColorScheme();
const { slug: bookmarkId } = useLocalSearchParams();
@@ -34,22 +36,24 @@ const ListPickerPage = () => {
});
};
- const { data: allTags, isPending: isAllTagsPending } = api.tags.list.useQuery(
- {},
- {
- select: React.useCallback(
- (data: { tags: { id: string; name: string }[] }) => {
- return data.tags
- .map((t) => ({
- id: t.id,
- name: t.name,
- lowered: t.name.toLowerCase(),
- }))
- .sort((a, b) => a.lowered.localeCompare(b.lowered));
- },
- [],
- ),
- },
+ const { data: allTags, isPending: isAllTagsPending } = useQuery(
+ api.tags.list.queryOptions(
+ {},
+ {
+ select: React.useCallback(
+ (data: { tags: { id: string; name: string }[] }) => {
+ return data.tags
+ .map((t) => ({
+ id: t.id,
+ name: t.name,
+ lowered: t.name.toLowerCase(),
+ }))
+ .sort((a, b) => a.lowered.localeCompare(b.lowered));
+ },
+ [],
+ ),
+ },
+ ),
);
const { data: existingTags } = useAutoRefreshingBookmarkQuery({
bookmarkId,
diff --git a/apps/mobile/app/dashboard/lists/[slug]/edit.tsx b/apps/mobile/app/dashboard/lists/[slug]/edit.tsx
index 6ccc2f26..e0654722 100644
--- a/apps/mobile/app/dashboard/lists/[slug]/edit.tsx
+++ b/apps/mobile/app/dashboard/lists/[slug]/edit.tsx
@@ -7,7 +7,8 @@ import FullPageSpinner from "@/components/ui/FullPageSpinner";
import { Input } from "@/components/ui/Input";
import { Text } from "@/components/ui/Text";
import { useToast } from "@/components/ui/Toast";
-import { api } from "@/lib/trpc";
+import { useTRPC } from "@/lib/trpc";
+import { useQuery } from "@tanstack/react-query";
import { useEditBookmarkList } from "@karakeep/shared-react/hooks/lists";
@@ -16,6 +17,7 @@ const EditListPage = () => {
const [text, setText] = useState("");
const [query, setQuery] = useState("");
const { toast } = useToast();
+ const api = useTRPC();
const { mutate, isPending: editIsPending } = useEditBookmarkList({
onSuccess: () => {
dismiss();
@@ -41,9 +43,11 @@ const EditListPage = () => {
throw new Error("Unexpected param type");
}
- const { data: list, isLoading: fetchIsPending } = api.lists.get.useQuery({
- listId,
- });
+ const { data: list, isLoading: fetchIsPending } = useQuery(
+ api.lists.get.queryOptions({
+ listId,
+ }),
+ );
const dismiss = () => {
router.back();
diff --git a/apps/mobile/app/dashboard/lists/[slug]/index.tsx b/apps/mobile/app/dashboard/lists/[slug]/index.tsx
index 11379588..97f797c6 100644
--- a/apps/mobile/app/dashboard/lists/[slug]/index.tsx
+++ b/apps/mobile/app/dashboard/lists/[slug]/index.tsx
@@ -5,14 +5,16 @@ import UpdatingBookmarkList from "@/components/bookmarks/UpdatingBookmarkList";
import FullPageError from "@/components/FullPageError";
import CustomSafeAreaView from "@/components/ui/CustomSafeAreaView";
import FullPageSpinner from "@/components/ui/FullPageSpinner";
-import { api } from "@/lib/trpc";
+import { useTRPC } from "@/lib/trpc";
import { MenuView } from "@react-native-menu/menu";
+import { useMutation, useQuery } from "@tanstack/react-query";
import { Ellipsis } from "lucide-react-native";
import { ZBookmarkList } from "@karakeep/shared/types/lists";
export default function ListView() {
const { slug } = useLocalSearchParams();
+ const api = useTRPC();
if (typeof slug !== "string") {
throw new Error("Unexpected param type");
}
@@ -20,7 +22,7 @@ export default function ListView() {
data: list,
error,
refetch,
- } = api.lists.get.useQuery({ listId: slug });
+ } = useQuery(api.lists.get.queryOptions({ listId: slug }));
return (
@@ -58,17 +60,22 @@ function ListActionsMenu({
listId: string;
role: ZBookmarkList["userRole"];
}) {
- const { mutate: deleteList } = api.lists.delete.useMutation({
- onSuccess: () => {
- router.replace("/dashboard/lists");
- },
- });
+ const api = useTRPC();
+ const { mutate: deleteList } = useMutation(
+ api.lists.delete.mutationOptions({
+ onSuccess: () => {
+ router.replace("/dashboard/lists");
+ },
+ }),
+ );
- const { mutate: leaveList } = api.lists.leaveList.useMutation({
- onSuccess: () => {
- router.replace("/dashboard/lists");
- },
- });
+ const { mutate: leaveList } = useMutation(
+ api.lists.leaveList.mutationOptions({
+ onSuccess: () => {
+ router.replace("/dashboard/lists");
+ },
+ }),
+ );
const handleDelete = () => {
Alert.alert("Delete List", "Are you sure you want to delete this list?", [
diff --git a/apps/mobile/app/dashboard/search.tsx b/apps/mobile/app/dashboard/search.tsx
index ab89ce8d..0e59c607 100644
--- a/apps/mobile/app/dashboard/search.tsx
+++ b/apps/mobile/app/dashboard/search.tsx
@@ -7,9 +7,13 @@ import CustomSafeAreaView from "@/components/ui/CustomSafeAreaView";
import FullPageSpinner from "@/components/ui/FullPageSpinner";
import { SearchInput } from "@/components/ui/SearchInput";
import { Text } from "@/components/ui/Text";
-import { api } from "@/lib/trpc";
+import { useTRPC } from "@/lib/trpc";
import AsyncStorage from "@react-native-async-storage/async-storage";
-import { keepPreviousData } from "@tanstack/react-query";
+import {
+ keepPreviousData,
+ useInfiniteQuery,
+ useQueryClient,
+} from "@tanstack/react-query";
import { useSearchHistory } from "@karakeep/shared-react/hooks/search-history";
import { useDebounce } from "@karakeep/shared-react/hooks/use-debounce";
@@ -29,7 +33,12 @@ export default function Search() {
removeItem: (k: string) => AsyncStorage.removeItem(k),
});
- const onRefresh = api.useUtils().bookmarks.searchBookmarks.invalidate;
+ const api = useTRPC();
+ const queryClient = useQueryClient();
+
+ const onRefresh = () => {
+ queryClient.invalidateQueries(api.bookmarks.searchBookmarks.pathFilter());
+ };
const {
data,
@@ -39,14 +48,16 @@ export default function Search() {
isFetching,
fetchNextPage,
isFetchingNextPage,
- } = api.bookmarks.searchBookmarks.useInfiniteQuery(
- { text: query },
- {
- placeholderData: keepPreviousData,
- gcTime: 0,
- initialCursor: null,
- getNextPageParam: (lastPage) => lastPage.nextCursor,
- },
+ } = useInfiniteQuery(
+ api.bookmarks.searchBookmarks.infiniteQueryOptions(
+ { text: query },
+ {
+ placeholderData: keepPreviousData,
+ gcTime: 0,
+ initialCursor: null,
+ getNextPageParam: (lastPage) => lastPage.nextCursor,
+ },
+ ),
);
const filteredHistory = useMemo(() => {
diff --git a/apps/mobile/app/dashboard/tags/[slug].tsx b/apps/mobile/app/dashboard/tags/[slug].tsx
index 3f294328..170cb04d 100644
--- a/apps/mobile/app/dashboard/tags/[slug].tsx
+++ b/apps/mobile/app/dashboard/tags/[slug].tsx
@@ -4,15 +4,21 @@ import UpdatingBookmarkList from "@/components/bookmarks/UpdatingBookmarkList";
import FullPageError from "@/components/FullPageError";
import CustomSafeAreaView from "@/components/ui/CustomSafeAreaView";
import FullPageSpinner from "@/components/ui/FullPageSpinner";
-import { api } from "@/lib/trpc";
+import { useTRPC } from "@/lib/trpc";
+import { useQuery } from "@tanstack/react-query";
export default function TagView() {
const { slug } = useLocalSearchParams();
+ const api = useTRPC();
if (typeof slug !== "string") {
throw new Error("Unexpected param type");
}
- const { data: tag, error, refetch } = api.tags.get.useQuery({ tagId: slug });
+ const {
+ data: tag,
+ error,
+ refetch,
+ } = useQuery(api.tags.get.queryOptions({ tagId: slug }));
return (
diff --git a/apps/mobile/app/sharing.tsx b/apps/mobile/app/sharing.tsx
index 3e2b6bfb..6d9167db 100644
--- a/apps/mobile/app/sharing.tsx
+++ b/apps/mobile/app/sharing.tsx
@@ -5,8 +5,9 @@ import { useShareIntentContext } from "expo-share-intent";
import { Button } from "@/components/ui/Button";
import { Text } from "@/components/ui/Text";
import useAppSettings from "@/lib/settings";
-import { api } from "@/lib/trpc";
+import { useTRPC } from "@/lib/trpc";
import { useUploadAsset } from "@/lib/upload";
+import { useMutation, useQueryClient } from "@tanstack/react-query";
import { z } from "zod";
import { BookmarkTypes, ZBookmark } from "@karakeep/shared/types/bookmarks";
@@ -18,8 +19,11 @@ type Mode =
| { type: "error" };
function SaveBookmark({ setMode }: { setMode: (mode: Mode) => void }) {
+ const api = useTRPC();
+ const queryClient = useQueryClient();
+
const onSaved = (d: ZBookmark & { alreadyExists: boolean }) => {
- invalidateAllBookmarks();
+ queryClient.invalidateQueries(api.bookmarks.getBookmarks.pathFilter());
setMode({
type: d.alreadyExists ? "alreadyExists" : "success",
bookmarkId: d.id,
@@ -36,9 +40,6 @@ function SaveBookmark({ setMode }: { setMode: (mode: Mode) => void }) {
},
});
- const invalidateAllBookmarks =
- api.useUtils().bookmarks.getBookmarks.invalidate;
-
useEffect(() => {
if (isLoading) {
return;
@@ -77,12 +78,14 @@ function SaveBookmark({ setMode }: { setMode: (mode: Mode) => void }) {
}
}, [isLoading]);
- const { mutate, isPending } = api.bookmarks.createBookmark.useMutation({
- onSuccess: onSaved,
- onError: () => {
- setMode({ type: "error" });
- },
- });
+ const { mutate, isPending } = useMutation(
+ api.bookmarks.createBookmark.mutationOptions({
+ onSuccess: onSaved,
+ onError: () => {
+ setMode({ type: "error" });
+ },
+ }),
+ );
return (
diff --git a/apps/mobile/app/signin.tsx b/apps/mobile/app/signin.tsx
index 03cbba5a..5c6370ca 100644
--- a/apps/mobile/app/signin.tsx
+++ b/apps/mobile/app/signin.tsx
@@ -13,7 +13,8 @@ import { Button } from "@/components/ui/Button";
import { Input } from "@/components/ui/Input";
import { Text } from "@/components/ui/Text";
import useAppSettings from "@/lib/settings";
-import { api } from "@/lib/trpc";
+import { useTRPC } from "@/lib/trpc";
+import { useMutation } from "@tanstack/react-query";
import { Bug, Edit3 } from "lucide-react-native";
enum LoginType {
@@ -24,6 +25,7 @@ enum LoginType {
export default function Signin() {
const { settings, setSettings } = useAppSettings();
const router = useRouter();
+ const api = useTRPC();
const [error, setError] = useState();
const [loginType, setLoginType] = useState(LoginType.Password);
@@ -43,33 +45,37 @@ export default function Signin() {
};
const { mutate: login, isPending: userNamePasswordRequestIsPending } =
- api.apiKeys.exchange.useMutation({
- onSuccess: (resp) => {
- setSettings({ ...settings, apiKey: resp.key, apiKeyId: resp.id });
- },
- onError: (e) => {
- if (e.data?.code === "UNAUTHORIZED") {
- setError("Wrong username or password");
- } else {
- setError(`${e.message}`);
- }
- },
- });
+ useMutation(
+ api.apiKeys.exchange.mutationOptions({
+ onSuccess: (resp) => {
+ setSettings({ ...settings, apiKey: resp.key, apiKeyId: resp.id });
+ },
+ onError: (e) => {
+ if (e.data?.code === "UNAUTHORIZED") {
+ setError("Wrong username or password");
+ } else {
+ setError(`${e.message}`);
+ }
+ },
+ }),
+ );
const { mutate: validateApiKey, isPending: apiKeyValueRequestIsPending } =
- api.apiKeys.validate.useMutation({
- onSuccess: () => {
- const apiKey = apiKeyRef.current;
- setSettings({ ...settings, apiKey: apiKey });
- },
- onError: (e) => {
- if (e.data?.code === "UNAUTHORIZED") {
- setError("Invalid API key");
- } else {
- setError(`${e.message}`);
- }
- },
- });
+ useMutation(
+ api.apiKeys.validate.mutationOptions({
+ onSuccess: () => {
+ const apiKey = apiKeyRef.current;
+ setSettings({ ...settings, apiKey: apiKey });
+ },
+ onError: (e) => {
+ if (e.data?.code === "UNAUTHORIZED") {
+ setError("Invalid API key");
+ } else {
+ setError(`${e.message}`);
+ }
+ },
+ }),
+ );
if (settings.apiKey) {
return ;
diff --git a/apps/mobile/components/bookmarks/BookmarkCard.tsx b/apps/mobile/components/bookmarks/BookmarkCard.tsx
index 6c3ef070..38fed737 100644
--- a/apps/mobile/components/bookmarks/BookmarkCard.tsx
+++ b/apps/mobile/components/bookmarks/BookmarkCard.tsx
@@ -15,9 +15,10 @@ import { router, useRouter } from "expo-router";
import * as Sharing from "expo-sharing";
import { Text } from "@/components/ui/Text";
import useAppSettings from "@/lib/settings";
-import { api } from "@/lib/trpc";
+import { useTRPC } from "@/lib/trpc";
import { buildApiHeaders } from "@/lib/utils";
import { MenuView } from "@react-native-menu/menu";
+import { useQuery } from "@tanstack/react-query";
import { Ellipsis, ShareIcon, Star } from "lucide-react-native";
import type { ZBookmark } from "@karakeep/shared/types/bookmarks";
@@ -477,20 +478,23 @@ export default function BookmarkCard({
}: {
bookmark: ZBookmark;
}) {
- const { data: bookmark } = api.bookmarks.getBookmark.useQuery(
- {
- bookmarkId: initialData.id,
- },
- {
- initialData,
- refetchInterval: (query) => {
- const data = query.state.data;
- if (!data) {
- return false;
- }
- return getBookmarkRefreshInterval(data);
+ const api = useTRPC();
+ const { data: bookmark } = useQuery(
+ api.bookmarks.getBookmark.queryOptions(
+ {
+ bookmarkId: initialData.id,
+ },
+ {
+ initialData,
+ refetchInterval: (query) => {
+ const data = query.state.data;
+ if (!data) {
+ return false;
+ }
+ return getBookmarkRefreshInterval(data);
+ },
},
- },
+ ),
);
const router = useRouter();
diff --git a/apps/mobile/components/bookmarks/BookmarkLinkPreview.tsx b/apps/mobile/components/bookmarks/BookmarkLinkPreview.tsx
index 4478bdda..aa073c69 100644
--- a/apps/mobile/components/bookmarks/BookmarkLinkPreview.tsx
+++ b/apps/mobile/components/bookmarks/BookmarkLinkPreview.tsx
@@ -6,8 +6,9 @@ import { WebViewSourceUri } from "react-native-webview/lib/WebViewTypes";
import { Text } from "@/components/ui/Text";
import { useAssetUrl } from "@/lib/hooks";
import { useReaderSettings, WEBVIEW_FONT_FAMILIES } from "@/lib/readerSettings";
-import { api } from "@/lib/trpc";
+import { useTRPC } from "@/lib/trpc";
import { useColorScheme } from "@/lib/useColorScheme";
+import { useQuery } from "@tanstack/react-query";
import { BookmarkTypes, ZBookmark } from "@karakeep/shared/types/bookmarks";
@@ -65,16 +66,19 @@ export function BookmarkLinkReaderPreview({
}) {
const { isDarkColorScheme: isDark } = useColorScheme();
const { settings: readerSettings } = useReaderSettings();
+ const api = useTRPC();
const {
data: bookmarkWithContent,
error,
isLoading,
refetch,
- } = api.bookmarks.getBookmark.useQuery({
- bookmarkId: bookmark.id,
- includeContent: true,
- });
+ } = useQuery(
+ api.bookmarks.getBookmark.queryOptions({
+ bookmarkId: bookmark.id,
+ includeContent: true,
+ }),
+ );
if (isLoading) {
return ;
diff --git a/apps/mobile/components/bookmarks/UpdatingBookmarkList.tsx b/apps/mobile/components/bookmarks/UpdatingBookmarkList.tsx
index e627ee16..b5aeaa40 100644
--- a/apps/mobile/components/bookmarks/UpdatingBookmarkList.tsx
+++ b/apps/mobile/components/bookmarks/UpdatingBookmarkList.tsx
@@ -1,4 +1,5 @@
-import { api } from "@/lib/trpc";
+import { useTRPC } from "@/lib/trpc";
+import { useInfiniteQuery, useQueryClient } from "@tanstack/react-query";
import type { ZGetBookmarksRequest } from "@karakeep/shared/types/bookmarks";
import { BookmarkTypes } from "@karakeep/shared/types/bookmarks";
@@ -14,7 +15,8 @@ export default function UpdatingBookmarkList({
query: Omit; // Sort order is not supported in mobile yet
header?: React.ReactElement;
}) {
- const apiUtils = api.useUtils();
+ const api = useTRPC();
+ const queryClient = useQueryClient();
const {
data,
isPending,
@@ -23,12 +25,14 @@ export default function UpdatingBookmarkList({
fetchNextPage,
isFetchingNextPage,
refetch,
- } = api.bookmarks.getBookmarks.useInfiniteQuery(
- { ...query, useCursorV2: true, includeContent: false },
- {
- initialCursor: null,
- getNextPageParam: (lastPage) => lastPage.nextCursor,
- },
+ } = useInfiniteQuery(
+ api.bookmarks.getBookmarks.infiniteQueryOptions(
+ { ...query, useCursorV2: true, includeContent: false },
+ {
+ initialCursor: null,
+ getNextPageParam: (lastPage) => lastPage.nextCursor,
+ },
+ ),
);
if (error) {
@@ -40,8 +44,8 @@ export default function UpdatingBookmarkList({
}
const onRefresh = () => {
- apiUtils.bookmarks.getBookmarks.invalidate();
- apiUtils.bookmarks.getBookmark.invalidate();
+ queryClient.invalidateQueries(api.bookmarks.getBookmarks.pathFilter());
+ queryClient.invalidateQueries(api.bookmarks.getBookmark.pathFilter());
};
return (
diff --git a/apps/mobile/components/highlights/HighlightCard.tsx b/apps/mobile/components/highlights/HighlightCard.tsx
index 7e0b4a2b..c8f97e32 100644
--- a/apps/mobile/components/highlights/HighlightCard.tsx
+++ b/apps/mobile/components/highlights/HighlightCard.tsx
@@ -2,7 +2,8 @@ import { ActivityIndicator, Alert, Pressable, View } from "react-native";
import * as Haptics from "expo-haptics";
import { useRouter } from "expo-router";
import { Text } from "@/components/ui/Text";
-import { api } from "@/lib/trpc";
+import { useTRPC } from "@/lib/trpc";
+import { useQuery } from "@tanstack/react-query";
import dayjs from "dayjs";
import relativeTime from "dayjs/plugin/relativeTime";
import { ExternalLink, Trash2 } from "lucide-react-native";
@@ -29,6 +30,7 @@ export default function HighlightCard({
}) {
const { toast } = useToast();
const router = useRouter();
+ const api = useTRPC();
const onError = () => {
toast({
@@ -64,13 +66,15 @@ export default function HighlightCard({
],
);
- const { data: bookmark } = api.bookmarks.getBookmark.useQuery(
- {
- bookmarkId: highlight.bookmarkId,
- },
- {
- retry: false,
- },
+ const { data: bookmark } = useQuery(
+ api.bookmarks.getBookmark.queryOptions(
+ {
+ bookmarkId: highlight.bookmarkId,
+ },
+ {
+ retry: false,
+ },
+ ),
);
const handleBookmarkPress = () => {
diff --git a/apps/mobile/lib/providers.tsx b/apps/mobile/lib/providers.tsx
index 01d2d5b5..4a7def1d 100644
--- a/apps/mobile/lib/providers.tsx
+++ b/apps/mobile/lib/providers.tsx
@@ -2,7 +2,7 @@ import { useEffect } from "react";
import FullPageSpinner from "@/components/ui/FullPageSpinner";
import { Toaster } from "sonner-native";
-import { TRPCProvider } from "@karakeep/shared-react/providers/trpc-provider";
+import { TRPCSettingsProvider } from "@karakeep/shared-react/providers/trpc-provider";
import { ReaderSettingsProvider } from "./readerSettings";
import useAppSettings from "./settings";
@@ -20,11 +20,11 @@ export function Providers({ children }: { children: React.ReactNode }) {
}
return (
-
+
{children}
-
+
);
}
diff --git a/apps/mobile/lib/session.ts b/apps/mobile/lib/session.ts
index 8eb646cb..9f539693 100644
--- a/apps/mobile/lib/session.ts
+++ b/apps/mobile/lib/session.ts
@@ -1,12 +1,16 @@
import { useCallback } from "react";
+import { useMutation } from "@tanstack/react-query";
import useAppSettings from "./settings";
-import { api } from "./trpc";
+import { useTRPC } from "./trpc";
export function useSession() {
const { settings, setSettings } = useAppSettings();
+ const api = useTRPC();
- const { mutate: deleteKey } = api.apiKeys.revoke.useMutation();
+ const { mutate: deleteKey } = useMutation(
+ api.apiKeys.revoke.mutationOptions(),
+ );
const logout = useCallback(() => {
if (settings.apiKeyId) {
diff --git a/apps/mobile/lib/trpc.ts b/apps/mobile/lib/trpc.ts
index e56968b8..915c265d 100644
--- a/apps/mobile/lib/trpc.ts
+++ b/apps/mobile/lib/trpc.ts
@@ -1,5 +1,3 @@
-import { createTRPCReact } from "@trpc/react-query";
-
-import type { AppRouter } from "@karakeep/trpc/routers/_app";
-
-export const api = createTRPCReact();
+// Re-export from shared-react to ensure there's only one TRPCProvider context
+// This is necessary because the hooks in shared-react use useTRPC from shared-react
+export { TRPCProvider, useTRPC } from "@karakeep/shared-react/trpc";
diff --git a/apps/mobile/lib/upload.ts b/apps/mobile/lib/upload.ts
index 06f007f7..13abae16 100644
--- a/apps/mobile/lib/upload.ts
+++ b/apps/mobile/lib/upload.ts
@@ -1,5 +1,5 @@
import ReactNativeBlobUtil from "react-native-blob-util";
-import { useMutation } from "@tanstack/react-query";
+import { useMutation, useQueryClient } from "@tanstack/react-query";
import { BookmarkTypes, ZBookmark } from "@karakeep/shared/types/bookmarks";
import {
@@ -8,7 +8,7 @@ import {
} from "@karakeep/shared/types/uploads";
import type { Settings } from "./settings";
-import { api } from "./trpc";
+import { useTRPC } from "./trpc";
import { buildApiHeaders } from "./utils";
export function useUploadAsset(
@@ -18,13 +18,13 @@ export function useUploadAsset(
onError?: (e: string) => void;
},
) {
- const invalidateAllBookmarks =
- api.useUtils().bookmarks.getBookmarks.invalidate;
+ const api = useTRPC();
+ const queryClient = useQueryClient();
- const { mutate: createBookmark, isPending: isCreatingBookmark } =
- api.bookmarks.createBookmark.useMutation({
+ const { mutate: createBookmark, isPending: isCreatingBookmark } = useMutation(
+ api.bookmarks.createBookmark.mutationOptions({
onSuccess: (d) => {
- invalidateAllBookmarks();
+ queryClient.invalidateQueries(api.bookmarks.getBookmarks.pathFilter());
if (options.onSuccess) {
options.onSuccess(d);
}
@@ -34,7 +34,8 @@ export function useUploadAsset(
options.onError(e.message);
}
},
- });
+ }),
+ );
const { mutate: uploadAsset, isPending: isUploading } = useMutation({
mutationFn: async (file: { type: string; name: string; uri: string }) => {
diff --git a/apps/web/app/check-email/page.tsx b/apps/web/app/check-email/page.tsx
index 227e116c..9e6a37b8 100644
--- a/apps/web/app/check-email/page.tsx
+++ b/apps/web/app/check-email/page.tsx
@@ -11,26 +11,30 @@ import {
CardHeader,
CardTitle,
} from "@/components/ui/card";
-import { api } from "@/lib/trpc";
+import { useTRPC } from "@/lib/trpc";
+import { useMutation } from "@tanstack/react-query";
import { Loader2, Mail } from "lucide-react";
export default function CheckEmailPage() {
+ const api = useTRPC();
const searchParams = useSearchParams();
const router = useRouter();
const [message, setMessage] = useState("");
const email = searchParams.get("email");
- const resendEmailMutation = api.users.resendVerificationEmail.useMutation({
- onSuccess: () => {
- setMessage(
- "A new verification email has been sent to your email address.",
- );
- },
- onError: (error) => {
- setMessage(error.message || "Failed to resend verification email.");
- },
- });
+ const resendEmailMutation = useMutation(
+ api.users.resendVerificationEmail.mutationOptions({
+ onSuccess: () => {
+ setMessage(
+ "A new verification email has been sent to your email address.",
+ );
+ },
+ onError: (error) => {
+ setMessage(error.message || "Failed to resend verification email.");
+ },
+ }),
+ );
const handleResendEmail = () => {
if (email) {
diff --git a/apps/web/app/reader/[bookmarkId]/page.tsx b/apps/web/app/reader/[bookmarkId]/page.tsx
index 3eba7c7a..0ba72016 100644
--- a/apps/web/app/reader/[bookmarkId]/page.tsx
+++ b/apps/web/app/reader/[bookmarkId]/page.tsx
@@ -10,22 +10,28 @@ import { FullPageSpinner } from "@/components/ui/full-page-spinner";
import { Separator } from "@/components/ui/separator";
import { useSession } from "@/lib/auth/client";
import { useReaderSettings } from "@/lib/readerSettings";
+import { useQuery } from "@tanstack/react-query";
import { HighlighterIcon as Highlight, Printer, X } from "lucide-react";
-import { api } from "@karakeep/shared-react/trpc";
+import { useTRPC } from "@karakeep/shared-react/trpc";
import { BookmarkTypes } from "@karakeep/shared/types/bookmarks";
import { READER_FONT_FAMILIES } from "@karakeep/shared/types/readers";
import { getBookmarkTitle } from "@karakeep/shared/utils/bookmarkUtils";
export default function ReaderViewPage() {
+ const api = useTRPC();
const params = useParams<{ bookmarkId: string }>();
const bookmarkId = params.bookmarkId;
- const { data: highlights } = api.highlights.getForBookmark.useQuery({
- bookmarkId,
- });
- const { data: bookmark } = api.bookmarks.getBookmark.useQuery({
- bookmarkId,
- });
+ const { data: highlights } = useQuery(
+ api.highlights.getForBookmark.queryOptions({
+ bookmarkId,
+ }),
+ );
+ const { data: bookmark } = useQuery(
+ api.bookmarks.getBookmark.queryOptions({
+ bookmarkId,
+ }),
+ );
const { data: session } = useSession();
const router = useRouter();
diff --git a/apps/web/app/settings/assets/page.tsx b/apps/web/app/settings/assets/page.tsx
index a2d2c9ab..0991816c 100644
--- a/apps/web/app/settings/assets/page.tsx
+++ b/apps/web/app/settings/assets/page.tsx
@@ -16,8 +16,9 @@ import {
} from "@/components/ui/table";
import { ASSET_TYPE_TO_ICON } from "@/lib/attachments";
import { useTranslation } from "@/lib/i18n/client";
-import { api } from "@/lib/trpc";
+import { useTRPC } from "@/lib/trpc";
import { formatBytes } from "@/lib/utils";
+import { useInfiniteQuery } from "@tanstack/react-query";
import { ExternalLink, Trash2 } from "lucide-react";
import { useDetachBookmarkAsset } from "@karakeep/shared-react/hooks/assets";
@@ -28,6 +29,7 @@ import {
} from "@karakeep/trpc/lib/attachments";
export default function AssetsSettingsPage() {
+ const api = useTRPC();
const { t } = useTranslation();
const { mutate: detachAsset, isPending: isDetaching } =
useDetachBookmarkAsset({
@@ -49,13 +51,15 @@ export default function AssetsSettingsPage() {
fetchNextPage,
hasNextPage,
isFetchingNextPage,
- } = api.assets.list.useInfiniteQuery(
- {
- limit: 20,
- },
- {
- getNextPageParam: (lastPage) => lastPage.nextCursor,
- },
+ } = useInfiniteQuery(
+ api.assets.list.infiniteQueryOptions(
+ {
+ limit: 20,
+ },
+ {
+ getNextPageParam: (lastPage) => lastPage.nextCursor,
+ },
+ ),
);
const assets = data?.pages.flatMap((page) => page.assets) ?? [];
diff --git a/apps/web/app/settings/broken-links/page.tsx b/apps/web/app/settings/broken-links/page.tsx
index 139e8f91..4197d62e 100644
--- a/apps/web/app/settings/broken-links/page.tsx
+++ b/apps/web/app/settings/broken-links/page.tsx
@@ -11,6 +11,7 @@ import {
TableHeader,
TableRow,
} from "@/components/ui/table";
+import { useQuery, useQueryClient } from "@tanstack/react-query";
import { RefreshCw, Trash2 } from "lucide-react";
import { useTranslation } from "react-i18next";
@@ -18,20 +19,23 @@ import {
useDeleteBookmark,
useRecrawlBookmark,
} from "@karakeep/shared-react/hooks/bookmarks";
-import { api } from "@karakeep/shared-react/trpc";
+import { useTRPC } from "@karakeep/shared-react/trpc";
export default function BrokenLinksPage() {
+ const api = useTRPC();
const { t } = useTranslation();
- const apiUtils = api.useUtils();
- const { data, isPending } = api.bookmarks.getBrokenLinks.useQuery();
+ const queryClient = useQueryClient();
+ const { data, isPending } = useQuery(
+ api.bookmarks.getBrokenLinks.queryOptions(),
+ );
const { mutate: deleteBookmark, isPending: isDeleting } = useDeleteBookmark({
onSuccess: () => {
toast({
description: t("toasts.bookmarks.deleted"),
});
- apiUtils.bookmarks.getBrokenLinks.invalidate();
+ queryClient.invalidateQueries(api.bookmarks.getBrokenLinks.pathFilter());
},
onError: () => {
toast({
@@ -47,7 +51,9 @@ export default function BrokenLinksPage() {
toast({
description: t("toasts.bookmarks.refetch"),
});
- apiUtils.bookmarks.getBrokenLinks.invalidate();
+ queryClient.invalidateQueries(
+ api.bookmarks.getBrokenLinks.pathFilter(),
+ );
},
onError: () => {
toast({
diff --git a/apps/web/app/settings/rules/page.tsx b/apps/web/app/settings/rules/page.tsx
index 17f5b388..6d0b6522 100644
--- a/apps/web/app/settings/rules/page.tsx
+++ b/apps/web/app/settings/rules/page.tsx
@@ -6,21 +6,25 @@ import RuleList from "@/components/dashboard/rules/RuleEngineRuleList";
import { Button } from "@/components/ui/button";
import { FullPageSpinner } from "@/components/ui/full-page-spinner";
import { useTranslation } from "@/lib/i18n/client";
-import { api } from "@/lib/trpc";
+import { useTRPC } from "@/lib/trpc";
+import { useQuery } from "@tanstack/react-query";
import { PlusCircle } from "lucide-react";
import { RuleEngineRule } from "@karakeep/shared/types/rules";
export default function RulesSettingsPage() {
+ const api = useTRPC();
const { t } = useTranslation();
const [editingRule, setEditingRule] = useState<
(Omit & { id: string | null }) | null
>(null);
- const { data: rules, isLoading } = api.rules.list.useQuery(undefined, {
- refetchOnWindowFocus: true,
- refetchOnMount: true,
- });
+ const { data: rules, isLoading } = useQuery(
+ api.rules.list.queryOptions(undefined, {
+ refetchOnWindowFocus: true,
+ refetchOnMount: true,
+ }),
+ );
const handleCreateRule = () => {
const newRule = {
diff --git a/apps/web/app/settings/stats/page.tsx b/apps/web/app/settings/stats/page.tsx
index 28c017f5..06076376 100644
--- a/apps/web/app/settings/stats/page.tsx
+++ b/apps/web/app/settings/stats/page.tsx
@@ -6,7 +6,8 @@ import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Progress } from "@/components/ui/progress";
import { Skeleton } from "@/components/ui/skeleton";
import { useTranslation } from "@/lib/i18n/client";
-import { api } from "@/lib/trpc";
+import { useTRPC } from "@/lib/trpc";
+import { useQuery } from "@tanstack/react-query";
import {
Archive,
BarChart3,
@@ -159,9 +160,10 @@ function StatCard({
}
export default function StatsPage() {
+ const api = useTRPC();
const { t } = useTranslation();
- const { data: stats, isLoading } = api.users.stats.useQuery();
- const { data: userSettings } = api.users.settings.useQuery();
+ const { data: stats, isLoading } = useQuery(api.users.stats.queryOptions());
+ const { data: userSettings } = useQuery(api.users.settings.queryOptions());
const maxHourlyActivity = useMemo(() => {
if (!stats) return 0;
@@ -237,7 +239,6 @@ export default function StatsPage() {
-
{/* Overview Stats */}
-
{/* Bookmark Types */}
@@ -532,7 +532,6 @@ export default function StatsPage() {
-
{/* Activity Patterns */}
{/* Hourly Activity */}
@@ -583,7 +582,6 @@ export default function StatsPage() {
-
{/* Asset Storage */}
{stats.assetsByType.length > 0 && (
diff --git a/apps/web/app/verify-email/page.tsx b/apps/web/app/verify-email/page.tsx
index da9b8b6b..7da96761 100644
--- a/apps/web/app/verify-email/page.tsx
+++ b/apps/web/app/verify-email/page.tsx
@@ -11,10 +11,12 @@ import {
CardHeader,
CardTitle,
} from "@/components/ui/card";
-import { api } from "@/lib/trpc";
+import { useTRPC } from "@/lib/trpc";
+import { useMutation } from "@tanstack/react-query";
import { CheckCircle, Loader2, XCircle } from "lucide-react";
export default function VerifyEmailPage() {
+ const api = useTRPC();
const searchParams = useSearchParams();
const router = useRouter();
const [status, setStatus] = useState<"loading" | "success" | "error">(
@@ -25,32 +27,36 @@ export default function VerifyEmailPage() {
const token = searchParams.get("token");
const email = searchParams.get("email");
- const verifyEmailMutation = api.users.verifyEmail.useMutation({
- onSuccess: () => {
- setStatus("success");
- setMessage(
- "Your email has been successfully verified! You can now sign in.",
- );
- },
- onError: (error) => {
- setStatus("error");
- setMessage(
- error.message ||
- "Failed to verify email. The link may be invalid or expired.",
- );
- },
- });
+ const verifyEmailMutation = useMutation(
+ api.users.verifyEmail.mutationOptions({
+ onSuccess: () => {
+ setStatus("success");
+ setMessage(
+ "Your email has been successfully verified! You can now sign in.",
+ );
+ },
+ onError: (error) => {
+ setStatus("error");
+ setMessage(
+ error.message ||
+ "Failed to verify email. The link may be invalid or expired.",
+ );
+ },
+ }),
+ );
- const resendEmailMutation = api.users.resendVerificationEmail.useMutation({
- onSuccess: () => {
- setMessage(
- "A new verification email has been sent to your email address.",
- );
- },
- onError: (error) => {
- setMessage(error.message || "Failed to resend verification email.");
- },
- });
+ const resendEmailMutation = useMutation(
+ api.users.resendVerificationEmail.mutationOptions({
+ onSuccess: () => {
+ setMessage(
+ "A new verification email has been sent to your email address.",
+ );
+ },
+ onError: (error) => {
+ setMessage(error.message || "Failed to resend verification email.");
+ },
+ }),
+ );
useEffect(() => {
if (token && email) {
diff --git a/apps/web/components/admin/AddUserDialog.tsx b/apps/web/components/admin/AddUserDialog.tsx
index 3c578eca..2e29c6da 100644
--- a/apps/web/components/admin/AddUserDialog.tsx
+++ b/apps/web/components/admin/AddUserDialog.tsx
@@ -1,213 +1,217 @@
-import { useEffect, useState } from "react";
-import { ActionButton } from "@/components/ui/action-button";
-import { Button } from "@/components/ui/button";
-import {
- Dialog,
- DialogClose,
- DialogContent,
- DialogFooter,
- DialogHeader,
- DialogTitle,
- DialogTrigger,
-} from "@/components/ui/dialog";
-import {
- Form,
- FormControl,
- FormField,
- FormItem,
- FormLabel,
- FormMessage,
-} from "@/components/ui/form";
-import { Input } from "@/components/ui/input";
-import {
- Select,
- SelectContent,
- SelectItem,
- SelectTrigger,
- SelectValue,
-} from "@/components/ui/select";
-import { toast } from "@/components/ui/sonner";
-import { api } from "@/lib/trpc";
-import { zodResolver } from "@hookform/resolvers/zod";
-import { TRPCClientError } from "@trpc/client";
-import { useForm } from "react-hook-form";
-import { z } from "zod";
-
-import { zAdminCreateUserSchema } from "@karakeep/shared/types/admin";
-
-type AdminCreateUserSchema = z.infer;
-
-export default function AddUserDialog({
- children,
-}: {
- children?: React.ReactNode;
-}) {
- const apiUtils = api.useUtils();
- const [isOpen, onOpenChange] = useState(false);
- const form = useForm({
- resolver: zodResolver(zAdminCreateUserSchema),
- defaultValues: {
- name: "",
- email: "",
- password: "",
- confirmPassword: "",
- role: "user",
- },
- });
- const { mutate, isPending } = api.admin.createUser.useMutation({
- onSuccess: () => {
- toast({
- description: "User created successfully",
- });
- onOpenChange(false);
- apiUtils.users.list.invalidate();
- apiUtils.admin.userStats.invalidate();
- },
- onError: (error) => {
- if (error instanceof TRPCClientError) {
- toast({
- variant: "destructive",
- description: error.message,
- });
- } else {
- toast({
- variant: "destructive",
- description: "Failed to create user",
- });
- }
- },
- });
-
- useEffect(() => {
- if (!isOpen) {
- form.reset();
- }
- }, [isOpen, form]);
-
- return (
-
- );
-}
+import { useEffect, useState } from "react";
+import { ActionButton } from "@/components/ui/action-button";
+import { Button } from "@/components/ui/button";
+import {
+ Dialog,
+ DialogClose,
+ DialogContent,
+ DialogFooter,
+ DialogHeader,
+ DialogTitle,
+ DialogTrigger,
+} from "@/components/ui/dialog";
+import {
+ Form,
+ FormControl,
+ FormField,
+ FormItem,
+ FormLabel,
+ FormMessage,
+} from "@/components/ui/form";
+import { Input } from "@/components/ui/input";
+import {
+ Select,
+ SelectContent,
+ SelectItem,
+ SelectTrigger,
+ SelectValue,
+} from "@/components/ui/select";
+import { toast } from "@/components/ui/sonner";
+import { useTRPC } from "@/lib/trpc";
+import { zodResolver } from "@hookform/resolvers/zod";
+import { useMutation, useQueryClient } from "@tanstack/react-query";
+import { TRPCClientError } from "@trpc/client";
+import { useForm } from "react-hook-form";
+import { z } from "zod";
+
+import { zAdminCreateUserSchema } from "@karakeep/shared/types/admin";
+
+type AdminCreateUserSchema = z.infer;
+
+export default function AddUserDialog({
+ children,
+}: {
+ children?: React.ReactNode;
+}) {
+ const api = useTRPC();
+ const queryClient = useQueryClient();
+ const [isOpen, onOpenChange] = useState(false);
+ const form = useForm({
+ resolver: zodResolver(zAdminCreateUserSchema),
+ defaultValues: {
+ name: "",
+ email: "",
+ password: "",
+ confirmPassword: "",
+ role: "user",
+ },
+ });
+ const { mutate, isPending } = useMutation(
+ api.admin.createUser.mutationOptions({
+ onSuccess: () => {
+ toast({
+ description: "User created successfully",
+ });
+ onOpenChange(false);
+ queryClient.invalidateQueries(api.users.list.pathFilter());
+ queryClient.invalidateQueries(api.admin.userStats.pathFilter());
+ },
+ onError: (error) => {
+ if (error instanceof TRPCClientError) {
+ toast({
+ variant: "destructive",
+ description: error.message,
+ });
+ } else {
+ toast({
+ variant: "destructive",
+ description: "Failed to create user",
+ });
+ }
+ },
+ }),
+ );
+
+ useEffect(() => {
+ if (!isOpen) {
+ form.reset();
+ }
+ }, [isOpen, form]);
+
+ return (
+
+ );
+}
diff --git a/apps/web/components/admin/AdminNotices.tsx b/apps/web/components/admin/AdminNotices.tsx
index 77b1b481..e9d9a692 100644
--- a/apps/web/components/admin/AdminNotices.tsx
+++ b/apps/web/components/admin/AdminNotices.tsx
@@ -2,7 +2,8 @@
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
import { Badge } from "@/components/ui/badge";
-import { api } from "@/lib/trpc";
+import { useTRPC } from "@/lib/trpc";
+import { useQuery } from "@tanstack/react-query";
import { AlertCircle } from "lucide-react";
import { AdminCard } from "./AdminCard";
@@ -14,7 +15,8 @@ interface AdminNotice {
}
function useAdminNotices() {
- const { data } = api.admin.getAdminNoticies.useQuery();
+ const api = useTRPC();
+ const { data } = useQuery(api.admin.getAdminNoticies.queryOptions());
if (!data) {
return [];
}
diff --git a/apps/web/components/admin/BackgroundJobs.tsx b/apps/web/components/admin/BackgroundJobs.tsx
index 382069c8..3dab3c54 100644
--- a/apps/web/components/admin/BackgroundJobs.tsx
+++ b/apps/web/components/admin/BackgroundJobs.tsx
@@ -13,8 +13,8 @@ import {
import { Skeleton } from "@/components/ui/skeleton";
import { toast } from "@/components/ui/sonner";
import { useTranslation } from "@/lib/i18n/client";
-import { api } from "@/lib/trpc";
-import { keepPreviousData } from "@tanstack/react-query";
+import { useTRPC } from "@/lib/trpc";
+import { keepPreviousData, useMutation, useQuery } from "@tanstack/react-query";
import {
Activity,
AlertTriangle,
@@ -254,13 +254,51 @@ function JobCard({
}
function useJobActions() {
+ const api = useTRPC();
const { t } = useTranslation();
const { mutateAsync: recrawlLinks, isPending: isRecrawlPending } =
- api.admin.recrawlLinks.useMutation({
+ useMutation(
+ api.admin.recrawlLinks.mutationOptions({
+ onSuccess: () => {
+ toast({
+ description: "Recrawl enqueued",
+ });
+ },
+ onError: (e) => {
+ toast({
+ variant: "destructive",
+ description: e.message,
+ });
+ },
+ }),
+ );
+
+ const { mutateAsync: reindexBookmarks, isPending: isReindexPending } =
+ useMutation(
+ api.admin.reindexAllBookmarks.mutationOptions({
+ onSuccess: () => {
+ toast({
+ description: "Reindex enqueued",
+ });
+ },
+ onError: (e) => {
+ toast({
+ variant: "destructive",
+ description: e.message,
+ });
+ },
+ }),
+ );
+
+ const {
+ mutateAsync: reprocessAssetsFixMode,
+ isPending: isReprocessingPending,
+ } = useMutation(
+ api.admin.reprocessAssetsFixMode.mutationOptions({
onSuccess: () => {
toast({
- description: "Recrawl enqueued",
+ description: "Reprocessing enqueued",
});
},
onError: (e) => {
@@ -269,13 +307,17 @@ function useJobActions() {
description: e.message,
});
},
- });
+ }),
+ );
- const { mutateAsync: reindexBookmarks, isPending: isReindexPending } =
- api.admin.reindexAllBookmarks.useMutation({
+ const {
+ mutateAsync: reRunInferenceOnAllBookmarks,
+ isPending: isInferencePending,
+ } = useMutation(
+ api.admin.reRunInferenceOnAllBookmarks.mutationOptions({
onSuccess: () => {
toast({
- description: "Reindex enqueued",
+ description: "Inference jobs enqueued",
});
},
onError: (e) => {
@@ -284,58 +326,27 @@ function useJobActions() {
description: e.message,
});
},
- });
-
- const {
- mutateAsync: reprocessAssetsFixMode,
- isPending: isReprocessingPending,
- } = api.admin.reprocessAssetsFixMode.useMutation({
- onSuccess: () => {
- toast({
- description: "Reprocessing enqueued",
- });
- },
- onError: (e) => {
- toast({
- variant: "destructive",
- description: e.message,
- });
- },
- });
-
- const {
- mutateAsync: reRunInferenceOnAllBookmarks,
- isPending: isInferencePending,
- } = api.admin.reRunInferenceOnAllBookmarks.useMutation({
- onSuccess: () => {
- toast({
- description: "Inference jobs enqueued",
- });
- },
- onError: (e) => {
- toast({
- variant: "destructive",
- description: e.message,
- });
- },
- });
+ }),
+ );
const {
mutateAsync: runAdminMaintenanceTask,
isPending: isAdminMaintenancePending,
- } = api.admin.runAdminMaintenanceTask.useMutation({
- onSuccess: () => {
- toast({
- description: "Admin maintenance request has been enqueued!",
- });
- },
- onError: (e) => {
- toast({
- variant: "destructive",
- description: e.message,
- });
- },
- });
+ } = useMutation(
+ api.admin.runAdminMaintenanceTask.mutationOptions({
+ onSuccess: () => {
+ toast({
+ description: "Admin maintenance request has been enqueued!",
+ });
+ },
+ onError: (e) => {
+ toast({
+ variant: "destructive",
+ description: e.message,
+ });
+ },
+ }),
+ );
return {
crawlActions: [
@@ -466,13 +477,13 @@ function useJobActions() {
}
export default function BackgroundJobs() {
+ const api = useTRPC();
const { t } = useTranslation();
- const { data: serverStats } = api.admin.backgroundJobsStats.useQuery(
- undefined,
- {
+ const { data: serverStats } = useQuery(
+ api.admin.backgroundJobsStats.queryOptions(undefined, {
refetchInterval: 1000,
placeholderData: keepPreviousData,
- },
+ }),
);
const actions = useJobActions();
diff --git a/apps/web/components/admin/BasicStats.tsx b/apps/web/components/admin/BasicStats.tsx
index 67352f66..9c88ba83 100644
--- a/apps/web/components/admin/BasicStats.tsx
+++ b/apps/web/components/admin/BasicStats.tsx
@@ -3,7 +3,7 @@
import { AdminCard } from "@/components/admin/AdminCard";
import { useClientConfig } from "@/lib/clientConfig";
import { useTranslation } from "@/lib/i18n/client";
-import { api } from "@/lib/trpc";
+import { useTRPC } from "@/lib/trpc";
import { useQuery } from "@tanstack/react-query";
const REPO_LATEST_RELEASE_API =
@@ -42,7 +42,7 @@ function ReleaseInfo() {
rel="noreferrer"
title="Update available"
>
- ({latestRelease} ⬆️)
+ ({latestRelease}⬆️)
);
}
@@ -71,10 +71,13 @@ function StatsSkeleton() {
}
export default function BasicStats() {
+ const api = useTRPC();
const { t } = useTranslation();
- const { data: serverStats } = api.admin.stats.useQuery(undefined, {
- refetchInterval: 5000,
- });
+ const { data: serverStats } = useQuery(
+ api.admin.stats.queryOptions(undefined, {
+ refetchInterval: 5000,
+ }),
+ );
if (!serverStats) {
return ;
diff --git a/apps/web/components/admin/BookmarkDebugger.tsx b/apps/web/components/admin/BookmarkDebugger.tsx
index 1628fdcc..78eb2c85 100644
--- a/apps/web/components/admin/BookmarkDebugger.tsx
+++ b/apps/web/components/admin/BookmarkDebugger.tsx
@@ -8,8 +8,9 @@ import { Button } from "@/components/ui/button";
import InfoTooltip from "@/components/ui/info-tooltip";
import { Input } from "@/components/ui/input";
import { useTranslation } from "@/lib/i18n/client";
-import { api } from "@/lib/trpc";
+import { useTRPC } from "@/lib/trpc";
import { formatBytes } from "@/lib/utils";
+import { useMutation, useQuery } from "@tanstack/react-query";
import { formatDistanceToNow } from "date-fns";
import {
AlertCircle,
@@ -37,6 +38,7 @@ import { toast } from "sonner";
import { BookmarkTypes } from "@karakeep/shared/types/bookmarks";
export default function BookmarkDebugger() {
+ const api = useTRPC();
const { t } = useTranslation();
const [inputValue, setInputValue] = useState("");
const [bookmarkId, setBookmarkId] = useQueryState(
@@ -56,9 +58,11 @@ export default function BookmarkDebugger() {
data: debugInfo,
isLoading,
error,
- } = api.admin.getBookmarkDebugInfo.useQuery(
- { bookmarkId: bookmarkId },
- { enabled: !!bookmarkId && bookmarkId.length > 0 },
+ } = useQuery(
+ api.admin.getBookmarkDebugInfo.queryOptions(
+ { bookmarkId: bookmarkId },
+ { enabled: !!bookmarkId && bookmarkId.length > 0 },
+ ),
);
const handleLookup = () => {
@@ -67,57 +71,65 @@ export default function BookmarkDebugger() {
}
};
- const recrawlMutation = api.admin.adminRecrawlBookmark.useMutation({
- onSuccess: () => {
- toast.success(t("admin.admin_tools.action_success"), {
- description: t("admin.admin_tools.recrawl_queued"),
- });
- },
- onError: (error) => {
- toast.error(t("admin.admin_tools.action_failed"), {
- description: error.message,
- });
- },
- });
+ const recrawlMutation = useMutation(
+ api.admin.adminRecrawlBookmark.mutationOptions({
+ onSuccess: () => {
+ toast.success(t("admin.admin_tools.action_success"), {
+ description: t("admin.admin_tools.recrawl_queued"),
+ });
+ },
+ onError: (error) => {
+ toast.error(t("admin.admin_tools.action_failed"), {
+ description: error.message,
+ });
+ },
+ }),
+ );
- const reindexMutation = api.admin.adminReindexBookmark.useMutation({
- onSuccess: () => {
- toast.success(t("admin.admin_tools.action_success"), {
- description: t("admin.admin_tools.reindex_queued"),
- });
- },
- onError: (error) => {
- toast.error(t("admin.admin_tools.action_failed"), {
- description: error.message,
- });
- },
- });
+ const reindexMutation = useMutation(
+ api.admin.adminReindexBookmark.mutationOptions({
+ onSuccess: () => {
+ toast.success(t("admin.admin_tools.action_success"), {
+ description: t("admin.admin_tools.reindex_queued"),
+ });
+ },
+ onError: (error) => {
+ toast.error(t("admin.admin_tools.action_failed"), {
+ description: error.message,
+ });
+ },
+ }),
+ );
- const retagMutation = api.admin.adminRetagBookmark.useMutation({
- onSuccess: () => {
- toast.success(t("admin.admin_tools.action_success"), {
- description: t("admin.admin_tools.retag_queued"),
- });
- },
- onError: (error) => {
- toast.error(t("admin.admin_tools.action_failed"), {
- description: error.message,
- });
- },
- });
+ const retagMutation = useMutation(
+ api.admin.adminRetagBookmark.mutationOptions({
+ onSuccess: () => {
+ toast.success(t("admin.admin_tools.action_success"), {
+ description: t("admin.admin_tools.retag_queued"),
+ });
+ },
+ onError: (error) => {
+ toast.error(t("admin.admin_tools.action_failed"), {
+ description: error.message,
+ });
+ },
+ }),
+ );
- const resummarizeMutation = api.admin.adminResummarizeBookmark.useMutation({
- onSuccess: () => {
- toast.success(t("admin.admin_tools.action_success"), {
- description: t("admin.admin_tools.resummarize_queued"),
- });
- },
- onError: (error) => {
- toast.error(t("admin.admin_tools.action_failed"), {
- description: error.message,
- });
- },
- });
+ const resummarizeMutation = useMutation(
+ api.admin.adminResummarizeBookmark.mutationOptions({
+ onSuccess: () => {
+ toast.success(t("admin.admin_tools.action_success"), {
+ description: t("admin.admin_tools.resummarize_queued"),
+ });
+ },
+ onError: (error) => {
+ toast.error(t("admin.admin_tools.action_failed"), {
+ description: error.message,
+ });
+ },
+ }),
+ );
const handleRecrawl = () => {
if (bookmarkId) {
diff --git a/apps/web/components/admin/CreateInviteDialog.tsx b/apps/web/components/admin/CreateInviteDialog.tsx
index 6738adc9..c8b6be8c 100644
--- a/apps/web/components/admin/CreateInviteDialog.tsx
+++ b/apps/web/components/admin/CreateInviteDialog.tsx
@@ -20,8 +20,9 @@ import {
} from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import { toast } from "@/components/ui/sonner";
-import { api } from "@/lib/trpc";
+import { useTRPC } from "@/lib/trpc";
import { zodResolver } from "@hookform/resolvers/zod";
+import { useMutation, useQueryClient } from "@tanstack/react-query";
import { TRPCClientError } from "@trpc/client";
import { useForm } from "react-hook-form";
import { z } from "zod";
@@ -37,6 +38,8 @@ interface CreateInviteDialogProps {
export default function CreateInviteDialog({
children,
}: CreateInviteDialogProps) {
+ const api = useTRPC();
+ const queryClient = useQueryClient();
const [open, setOpen] = useState(false);
const [errorMessage, setErrorMessage] = useState("");
@@ -47,25 +50,26 @@ export default function CreateInviteDialog({
},
});
- const invalidateInvitesList = api.useUtils().invites.list.invalidate;
- const createInviteMutation = api.invites.create.useMutation({
- onSuccess: () => {
- toast({
- description: "Invite sent successfully",
- });
- invalidateInvitesList();
- setOpen(false);
- form.reset();
- setErrorMessage("");
- },
- onError: (e) => {
- if (e instanceof TRPCClientError) {
- setErrorMessage(e.message);
- } else {
- setErrorMessage("Failed to send invite");
- }
- },
- });
+ const createInviteMutation = useMutation(
+ api.invites.create.mutationOptions({
+ onSuccess: () => {
+ toast({
+ description: "Invite sent successfully",
+ });
+ queryClient.invalidateQueries(api.invites.list.pathFilter());
+ setOpen(false);
+ form.reset();
+ setErrorMessage("");
+ },
+ onError: (e) => {
+ if (e instanceof TRPCClientError) {
+ setErrorMessage(e.message);
+ } else {
+ setErrorMessage("Failed to send invite");
+ }
+ },
+ }),
+ );
return (