aboutsummaryrefslogtreecommitdiffstats
path: root/apps/web/components/dashboard/lists
diff options
context:
space:
mode:
authorMohamed Bassem <me@mbassem.com>2026-02-01 12:29:54 +0000
committerGitHub <noreply@github.com>2026-02-01 12:29:54 +0000
commit65f6e83f11c82b0ec762e11f3392a80e614ee69a (patch)
tree945d8d73122f07fe6a77c2bd3ac9db566939ba3b /apps/web/components/dashboard/lists
parente516a525bca6f319a2f003e9677624e968b277bf (diff)
downloadkarakeep-65f6e83f11c82b0ec762e11f3392a80e614ee69a.tar.zst
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
Diffstat (limited to 'apps/web/components/dashboard/lists')
-rw-r--r--apps/web/components/dashboard/lists/CollapsibleBookmarkLists.tsx13
-rw-r--r--apps/web/components/dashboard/lists/LeaveListConfirmationDialog.tsx54
-rw-r--r--apps/web/components/dashboard/lists/ListHeader.tsx38
-rw-r--r--apps/web/components/dashboard/lists/ManageCollaboratorsModal.tsx154
-rw-r--r--apps/web/components/dashboard/lists/PendingInvitationsCard.tsx83
-rw-r--r--apps/web/components/dashboard/lists/RssLink.tsx33
6 files changed, 216 insertions, 159 deletions
diff --git a/apps/web/components/dashboard/lists/CollapsibleBookmarkLists.tsx b/apps/web/components/dashboard/lists/CollapsibleBookmarkLists.tsx
index 2bb5f41b..626d0757 100644
--- a/apps/web/components/dashboard/lists/CollapsibleBookmarkLists.tsx
+++ b/apps/web/components/dashboard/lists/CollapsibleBookmarkLists.tsx
@@ -1,8 +1,8 @@
import { useEffect, useState } from "react";
import { Collapsible, CollapsibleContent } from "@/components/ui/collapsible";
import { FullPageSpinner } from "@/components/ui/full-page-spinner";
-import { api } from "@/lib/trpc";
-import { keepPreviousData } from "@tanstack/react-query";
+import { useTRPC } from "@/lib/trpc";
+import { keepPreviousData, useQuery } from "@tanstack/react-query";
import { useBookmarkLists } from "@karakeep/shared-react/hooks/lists";
import { ZBookmarkList } from "@karakeep/shared/types/lists";
@@ -101,6 +101,7 @@ export function CollapsibleBookmarkLists({
filter?: (node: ZBookmarkListTreeNode) => boolean;
indentOffset?: number;
}) {
+ const api = useTRPC();
// If listsData is provided, use it directly. Otherwise, fetch it.
let { data: fetchedData } = useBookmarkLists(undefined, {
initialData: initialData ? { lists: initialData } : undefined,
@@ -108,9 +109,11 @@ export function CollapsibleBookmarkLists({
});
const data = listsData || fetchedData;
- const { data: listStats } = api.lists.stats.useQuery(undefined, {
- placeholderData: keepPreviousData,
- });
+ const { data: listStats } = useQuery(
+ api.lists.stats.queryOptions(undefined, {
+ placeholderData: keepPreviousData,
+ }),
+ );
if (!data) {
return <FullPageSpinner />;
diff --git a/apps/web/components/dashboard/lists/LeaveListConfirmationDialog.tsx b/apps/web/components/dashboard/lists/LeaveListConfirmationDialog.tsx
index f2a48062..79d23b6a 100644
--- a/apps/web/components/dashboard/lists/LeaveListConfirmationDialog.tsx
+++ b/apps/web/components/dashboard/lists/LeaveListConfirmationDialog.tsx
@@ -4,7 +4,8 @@ import { ActionButton } from "@/components/ui/action-button";
import ActionConfirmingDialog from "@/components/ui/action-confirming-dialog";
import { toast } from "@/components/ui/sonner";
import { useTranslation } from "@/lib/i18n/client";
-import { api } from "@/lib/trpc";
+import { useTRPC } from "@/lib/trpc";
+import { useMutation, useQueryClient } from "@tanstack/react-query";
import type { ZBookmarkList } from "@karakeep/shared/types/lists";
@@ -19,34 +20,37 @@ export default function LeaveListConfirmationDialog({
open: boolean;
setOpen: (v: boolean) => void;
}) {
+ const api = useTRPC();
const { t } = useTranslation();
const currentPath = usePathname();
const router = useRouter();
- const utils = api.useUtils();
+ const queryClient = useQueryClient();
- const { mutate: leaveList, isPending } = api.lists.leaveList.useMutation({
- onSuccess: () => {
- toast({
- description: t("lists.leave_list.success", {
- icon: list.icon,
- name: list.name,
- }),
- });
- setOpen(false);
- // Invalidate the lists cache
- utils.lists.list.invalidate();
- // If currently viewing this list, redirect to lists page
- if (currentPath.includes(list.id)) {
- router.push("/dashboard/lists");
- }
- },
- onError: (error) => {
- toast({
- variant: "destructive",
- description: error.message || t("common.something_went_wrong"),
- });
- },
- });
+ const { mutate: leaveList, isPending } = useMutation(
+ api.lists.leaveList.mutationOptions({
+ onSuccess: () => {
+ toast({
+ description: t("lists.leave_list.success", {
+ icon: list.icon,
+ name: list.name,
+ }),
+ });
+ setOpen(false);
+ // Invalidate the lists cache
+ queryClient.invalidateQueries(api.lists.list.pathFilter());
+ // If currently viewing this list, redirect to lists page
+ if (currentPath.includes(list.id)) {
+ router.push("/dashboard/lists");
+ }
+ },
+ onError: (error) => {
+ toast({
+ variant: "destructive",
+ description: error.message || t("common.something_went_wrong"),
+ });
+ },
+ }),
+ );
return (
<ActionConfirmingDialog
diff --git a/apps/web/components/dashboard/lists/ListHeader.tsx b/apps/web/components/dashboard/lists/ListHeader.tsx
index ecbb6431..4176a80e 100644
--- a/apps/web/components/dashboard/lists/ListHeader.tsx
+++ b/apps/web/components/dashboard/lists/ListHeader.tsx
@@ -10,9 +10,10 @@ import {
} from "@/components/ui/tooltip";
import { UserAvatar } from "@/components/ui/user-avatar";
import { useTranslation } from "@/lib/i18n/client";
+import { useQuery } from "@tanstack/react-query";
import { MoreHorizontal, SearchIcon } from "lucide-react";
-import { api } from "@karakeep/shared-react/trpc";
+import { useTRPC } from "@karakeep/shared-react/trpc";
import { parseSearchQuery } from "@karakeep/shared/searchQueryParser";
import { ZBookmarkList } from "@karakeep/shared/types/lists";
@@ -24,25 +25,30 @@ export default function ListHeader({
}: {
initialData: ZBookmarkList;
}) {
+ const api = useTRPC();
const { t } = useTranslation();
const router = useRouter();
- const { data: list, error } = api.lists.get.useQuery(
- {
- listId: initialData.id,
- },
- {
- initialData,
- },
+ const { data: list, error } = useQuery(
+ api.lists.get.queryOptions(
+ {
+ listId: initialData.id,
+ },
+ {
+ initialData,
+ },
+ ),
);
- const { data: collaboratorsData } = api.lists.getCollaborators.useQuery(
- {
- listId: initialData.id,
- },
- {
- refetchOnWindowFocus: false,
- enabled: list.hasCollaborators,
- },
+ const { data: collaboratorsData } = useQuery(
+ api.lists.getCollaborators.queryOptions(
+ {
+ listId: initialData.id,
+ },
+ {
+ refetchOnWindowFocus: false,
+ enabled: list.hasCollaborators,
+ },
+ ),
);
const parsedQuery = useMemo(() => {
diff --git a/apps/web/components/dashboard/lists/ManageCollaboratorsModal.tsx b/apps/web/components/dashboard/lists/ManageCollaboratorsModal.tsx
index 6c5dac1e..354e0dfe 100644
--- a/apps/web/components/dashboard/lists/ManageCollaboratorsModal.tsx
+++ b/apps/web/components/dashboard/lists/ManageCollaboratorsModal.tsx
@@ -25,7 +25,8 @@ import {
import { toast } from "@/components/ui/sonner";
import { UserAvatar } from "@/components/ui/user-avatar";
import { useTranslation } from "@/lib/i18n/client";
-import { api } from "@/lib/trpc";
+import { useTRPC } from "@/lib/trpc";
+import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { Loader2, Trash2, UserPlus, Users } from "lucide-react";
import { ZBookmarkList } from "@karakeep/shared/types/lists";
@@ -43,6 +44,7 @@ export function ManageCollaboratorsModal({
children?: React.ReactNode;
readOnly?: boolean;
}) {
+ const api = useTRPC();
if (
(userOpen !== undefined && !userSetOpen) ||
(userOpen === undefined && userSetOpen)
@@ -61,82 +63,102 @@ export function ManageCollaboratorsModal({
>("viewer");
const { t } = useTranslation();
- const utils = api.useUtils();
+ const queryClient = useQueryClient();
const invalidateListCaches = () =>
Promise.all([
- utils.lists.getCollaborators.invalidate({ listId: list.id }),
- utils.lists.get.invalidate({ listId: list.id }),
- utils.lists.list.invalidate(),
- utils.bookmarks.getBookmarks.invalidate({ listId: list.id }),
+ queryClient.invalidateQueries(
+ api.lists.getCollaborators.queryFilter({ listId: list.id }),
+ ),
+ queryClient.invalidateQueries(
+ api.lists.get.queryFilter({ listId: list.id }),
+ ),
+ queryClient.invalidateQueries(api.lists.list.pathFilter()),
+ queryClient.invalidateQueries(
+ api.bookmarks.getBookmarks.queryFilter({ listId: list.id }),
+ ),
]);
// Fetch collaborators
- const { data: collaboratorsData, isLoading } =
- api.lists.getCollaborators.useQuery({ listId: list.id }, { enabled: open });
+ const { data: collaboratorsData, isLoading } = useQuery(
+ api.lists.getCollaborators.queryOptions(
+ { listId: list.id },
+ { enabled: open },
+ ),
+ );
// Mutations
- const addCollaborator = api.lists.addCollaborator.useMutation({
- onSuccess: async () => {
- toast({
- description: t("lists.collaborators.invitation_sent"),
- });
- setNewCollaboratorEmail("");
- await invalidateListCaches();
- },
- onError: (error) => {
- toast({
- variant: "destructive",
- description: error.message || t("lists.collaborators.failed_to_add"),
- });
- },
- });
+ const addCollaborator = useMutation(
+ api.lists.addCollaborator.mutationOptions({
+ onSuccess: async () => {
+ toast({
+ description: t("lists.collaborators.invitation_sent"),
+ });
+ setNewCollaboratorEmail("");
+ await invalidateListCaches();
+ },
+ onError: (error) => {
+ toast({
+ variant: "destructive",
+ description: error.message || t("lists.collaborators.failed_to_add"),
+ });
+ },
+ }),
+ );
- const removeCollaborator = api.lists.removeCollaborator.useMutation({
- onSuccess: async () => {
- toast({
- description: t("lists.collaborators.removed"),
- });
- await invalidateListCaches();
- },
- onError: (error) => {
- toast({
- variant: "destructive",
- description: error.message || t("lists.collaborators.failed_to_remove"),
- });
- },
- });
+ const removeCollaborator = useMutation(
+ api.lists.removeCollaborator.mutationOptions({
+ onSuccess: async () => {
+ toast({
+ description: t("lists.collaborators.removed"),
+ });
+ await invalidateListCaches();
+ },
+ onError: (error) => {
+ toast({
+ variant: "destructive",
+ description:
+ error.message || t("lists.collaborators.failed_to_remove"),
+ });
+ },
+ }),
+ );
- const updateCollaboratorRole = api.lists.updateCollaboratorRole.useMutation({
- onSuccess: async () => {
- toast({
- description: t("lists.collaborators.role_updated"),
- });
- await invalidateListCaches();
- },
- onError: (error) => {
- toast({
- variant: "destructive",
- description:
- error.message || t("lists.collaborators.failed_to_update_role"),
- });
- },
- });
+ const updateCollaboratorRole = useMutation(
+ api.lists.updateCollaboratorRole.mutationOptions({
+ onSuccess: async () => {
+ toast({
+ description: t("lists.collaborators.role_updated"),
+ });
+ await invalidateListCaches();
+ },
+ onError: (error) => {
+ toast({
+ variant: "destructive",
+ description:
+ error.message || t("lists.collaborators.failed_to_update_role"),
+ });
+ },
+ }),
+ );
- const revokeInvitation = api.lists.revokeInvitation.useMutation({
- onSuccess: async () => {
- toast({
- description: t("lists.collaborators.invitation_revoked"),
- });
- await invalidateListCaches();
- },
- onError: (error) => {
- toast({
- variant: "destructive",
- description: error.message || t("lists.collaborators.failed_to_revoke"),
- });
- },
- });
+ const revokeInvitation = useMutation(
+ api.lists.revokeInvitation.mutationOptions({
+ onSuccess: async () => {
+ toast({
+ description: t("lists.collaborators.invitation_revoked"),
+ });
+ await invalidateListCaches();
+ },
+ onError: (error) => {
+ toast({
+ variant: "destructive",
+ description:
+ error.message || t("lists.collaborators.failed_to_revoke"),
+ });
+ },
+ }),
+ );
const handleAddCollaborator = () => {
if (!newCollaboratorEmail.trim()) {
diff --git a/apps/web/components/dashboard/lists/PendingInvitationsCard.tsx b/apps/web/components/dashboard/lists/PendingInvitationsCard.tsx
index 95a916ff..5d70daaf 100644
--- a/apps/web/components/dashboard/lists/PendingInvitationsCard.tsx
+++ b/apps/web/components/dashboard/lists/PendingInvitationsCard.tsx
@@ -10,7 +10,8 @@ import {
} from "@/components/ui/card";
import { toast } from "@/components/ui/sonner";
import { useTranslation } from "@/lib/i18n/client";
-import { api } from "@/lib/trpc";
+import { useTRPC } from "@/lib/trpc";
+import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { Check, Loader2, Mail, X } from "lucide-react";
interface Invitation {
@@ -27,41 +28,51 @@ interface Invitation {
}
function InvitationRow({ invitation }: { invitation: Invitation }) {
+ const api = useTRPC();
const { t } = useTranslation();
- const utils = api.useUtils();
+ const queryClient = useQueryClient();
- const acceptInvitation = api.lists.acceptInvitation.useMutation({
- onSuccess: async () => {
- toast({
- description: t("lists.invitations.accepted"),
- });
- await Promise.all([
- utils.lists.getPendingInvitations.invalidate(),
- utils.lists.list.invalidate(),
- ]);
- },
- onError: (error) => {
- toast({
- variant: "destructive",
- description: error.message || t("lists.invitations.failed_to_accept"),
- });
- },
- });
+ const acceptInvitation = useMutation(
+ api.lists.acceptInvitation.mutationOptions({
+ onSuccess: async () => {
+ toast({
+ description: t("lists.invitations.accepted"),
+ });
+ await Promise.all([
+ queryClient.invalidateQueries(
+ api.lists.getPendingInvitations.pathFilter(),
+ ),
+ queryClient.invalidateQueries(api.lists.list.pathFilter()),
+ ]);
+ },
+ onError: (error) => {
+ toast({
+ variant: "destructive",
+ description: error.message || t("lists.invitations.failed_to_accept"),
+ });
+ },
+ }),
+ );
- const declineInvitation = api.lists.declineInvitation.useMutation({
- onSuccess: async () => {
- toast({
- description: t("lists.invitations.declined"),
- });
- await utils.lists.getPendingInvitations.invalidate();
- },
- onError: (error) => {
- toast({
- variant: "destructive",
- description: error.message || t("lists.invitations.failed_to_decline"),
- });
- },
- });
+ const declineInvitation = useMutation(
+ api.lists.declineInvitation.mutationOptions({
+ onSuccess: async () => {
+ toast({
+ description: t("lists.invitations.declined"),
+ });
+ await queryClient.invalidateQueries(
+ api.lists.getPendingInvitations.pathFilter(),
+ );
+ },
+ onError: (error) => {
+ toast({
+ variant: "destructive",
+ description:
+ error.message || t("lists.invitations.failed_to_decline"),
+ });
+ },
+ }),
+ );
return (
<div className="flex items-center justify-between rounded-lg border p-4">
@@ -126,10 +137,12 @@ function InvitationRow({ invitation }: { invitation: Invitation }) {
}
export function PendingInvitationsCard() {
+ const api = useTRPC();
const { t } = useTranslation();
- const { data: invitations, isLoading } =
- api.lists.getPendingInvitations.useQuery();
+ const { data: invitations, isLoading } = useQuery(
+ api.lists.getPendingInvitations.queryOptions(),
+ );
if (isLoading) {
return null;
diff --git a/apps/web/components/dashboard/lists/RssLink.tsx b/apps/web/components/dashboard/lists/RssLink.tsx
index 1be48681..5bc0fdf0 100644
--- a/apps/web/components/dashboard/lists/RssLink.tsx
+++ b/apps/web/components/dashboard/lists/RssLink.tsx
@@ -7,29 +7,38 @@ import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Switch } from "@/components/ui/switch";
import { useClientConfig } from "@/lib/clientConfig";
-import { api } from "@/lib/trpc";
+import { useTRPC } from "@/lib/trpc";
+import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { Loader2, RotateCcw } from "lucide-react";
import { useTranslation } from "react-i18next";
export default function RssLink({ listId }: { listId: string }) {
+ const api = useTRPC();
const { t } = useTranslation();
const clientConfig = useClientConfig();
- const apiUtils = api.useUtils();
+ const queryClient = useQueryClient();
- const { mutate: regenRssToken, isPending: isRegenPending } =
- api.lists.regenRssToken.useMutation({
+ const { mutate: regenRssToken, isPending: isRegenPending } = useMutation(
+ api.lists.regenRssToken.mutationOptions({
onSuccess: () => {
- apiUtils.lists.getRssToken.invalidate({ listId });
+ queryClient.invalidateQueries(
+ api.lists.getRssToken.queryFilter({ listId }),
+ );
},
- });
- const { mutate: clearRssToken, isPending: isClearPending } =
- api.lists.clearRssToken.useMutation({
+ }),
+ );
+ const { mutate: clearRssToken, isPending: isClearPending } = useMutation(
+ api.lists.clearRssToken.mutationOptions({
onSuccess: () => {
- apiUtils.lists.getRssToken.invalidate({ listId });
+ queryClient.invalidateQueries(
+ api.lists.getRssToken.queryFilter({ listId }),
+ );
},
- });
- const { data: rssToken, isLoading: isTokenLoading } =
- api.lists.getRssToken.useQuery({ listId });
+ }),
+ );
+ const { data: rssToken, isLoading: isTokenLoading } = useQuery(
+ api.lists.getRssToken.queryOptions({ listId }),
+ );
const rssUrl = useMemo(() => {
if (!rssToken || !rssToken.token) {