aboutsummaryrefslogtreecommitdiffstats
path: root/apps/web/lib/hooks
diff options
context:
space:
mode:
authorMohamed Bassem <me@mbassem.com>2025-10-04 13:40:24 +0100
committerGitHub <noreply@github.com>2025-10-04 13:40:24 +0100
commit4a580d713621f99abb8baabc9b847ce039d44842 (patch)
treea2aa6f3ae8045ad50a9316624e2a7028dd098c6b /apps/web/lib/hooks
parent5e331a7d5b8d9666812170547574804d8b6da741 (diff)
downloadkarakeep-4a580d713621f99abb8baabc9b847ce039d44842.tar.zst
feat: Revamp import experience (#2001)
* WIP: import v2 * remove new session button * don't redirect after import * store and lint to root list * models + tests * redesign the progress * simplify the import session for ow * drop status from session schema * split the import session page * i18n * fix test * remove pagination * fix some colors in darkmode * one last fix * add privacy filter * privacy check * fix interactivity of import progress * fix test
Diffstat (limited to 'apps/web/lib/hooks')
-rw-r--r--apps/web/lib/hooks/useBookmarkImport.ts21
-rw-r--r--apps/web/lib/hooks/useImportSessions.ts62
2 files changed, 75 insertions, 8 deletions
diff --git a/apps/web/lib/hooks/useBookmarkImport.ts b/apps/web/lib/hooks/useBookmarkImport.ts
index de515677..a4ebdd9c 100644
--- a/apps/web/lib/hooks/useBookmarkImport.ts
+++ b/apps/web/lib/hooks/useBookmarkImport.ts
@@ -1,7 +1,6 @@
"use client";
import { useState } from "react";
-import { useRouter } from "next/navigation";
import { toast } from "@/components/ui/use-toast";
import { useTranslation } from "@/lib/i18n/client";
import { useMutation } from "@tanstack/react-query";
@@ -24,6 +23,8 @@ import {
MAX_BOOKMARK_TITLE_LENGTH,
} from "@karakeep/shared/types/bookmarks";
+import { useCreateImportSession } from "./useImportSessions";
+
export interface ImportProgress {
done: number;
total: number;
@@ -31,12 +32,12 @@ export interface ImportProgress {
export function useBookmarkImport() {
const { t } = useTranslation();
- const router = useRouter();
const [importProgress, setImportProgress] = useState<ImportProgress | null>(
null,
);
+ const { mutateAsync: createImportSession } = useCreateImportSession();
const { mutateAsync: createBookmark } = useCreateBookmarkWithPostHook();
const { mutateAsync: createList } = useCreateBookmarkList();
const { mutateAsync: addToList } = useAddBookmarkToList();
@@ -56,8 +57,12 @@ export function useBookmarkImport() {
source,
rootListName: t("settings.import.imported_bookmarks"),
deps: {
- createList: createList,
- createBookmark: async (bookmark: ParsedBookmark) => {
+ createImportSession,
+ createList,
+ createBookmark: async (
+ bookmark: ParsedBookmark,
+ sessionId: string,
+ ) => {
if (bookmark.content === undefined) {
throw new Error("Content is undefined");
}
@@ -69,6 +74,7 @@ export function useBookmarkImport() {
: undefined,
note: bookmark.notes,
archived: bookmark.archived,
+ importSessionId: sessionId,
...(bookmark.content.type === BookmarkTypes.LINK
? {
type: BookmarkTypes.LINK,
@@ -120,6 +126,8 @@ export function useBookmarkImport() {
return result;
},
onSuccess: async (result) => {
+ setImportProgress(null);
+
if (result.counts.total === 0) {
toast({ description: "No bookmarks found in the file." });
return;
@@ -127,7 +135,7 @@ export function useBookmarkImport() {
const { successes, failures, alreadyExisted } = result.counts;
if (successes > 0 || alreadyExisted > 0) {
toast({
- description: `Imported ${successes} bookmarks and skipped ${alreadyExisted} bookmarks that already existed`,
+ description: `Imported ${successes} bookmarks into import session. Background processing will start automatically.`,
variant: "default",
});
}
@@ -137,9 +145,6 @@ export function useBookmarkImport() {
variant: "destructive",
});
}
-
- if (result.rootListId)
- router.push(`/dashboard/lists/${result.rootListId}`);
},
onError: (error) => {
setImportProgress(null);
diff --git a/apps/web/lib/hooks/useImportSessions.ts b/apps/web/lib/hooks/useImportSessions.ts
new file mode 100644
index 00000000..cee99bbc
--- /dev/null
+++ b/apps/web/lib/hooks/useImportSessions.ts
@@ -0,0 +1,62 @@
+"use client";
+
+import { toast } from "@/components/ui/use-toast";
+
+import { api } from "@karakeep/shared-react/trpc";
+
+export function useCreateImportSession() {
+ const apiUtils = api.useUtils();
+
+ return api.importSessions.createImportSession.useMutation({
+ onSuccess: () => {
+ apiUtils.importSessions.listImportSessions.invalidate();
+ },
+ onError: (error) => {
+ toast({
+ description: error.message || "Failed to create import session",
+ variant: "destructive",
+ });
+ },
+ });
+}
+
+export function useListImportSessions() {
+ return api.importSessions.listImportSessions.useQuery(
+ {},
+ {
+ select: (data) => data.sessions,
+ },
+ );
+}
+
+export function useImportSessionStats(importSessionId: string) {
+ return api.importSessions.getImportSessionStats.useQuery(
+ {
+ importSessionId,
+ },
+ {
+ refetchInterval: 5000, // Refetch every 5 seconds to show progress
+ enabled: !!importSessionId,
+ },
+ );
+}
+
+export function useDeleteImportSession() {
+ const apiUtils = api.useUtils();
+
+ return api.importSessions.deleteImportSession.useMutation({
+ onSuccess: () => {
+ apiUtils.importSessions.listImportSessions.invalidate();
+ toast({
+ description: "Import session deleted successfully",
+ variant: "default",
+ });
+ },
+ onError: (error) => {
+ toast({
+ description: error.message || "Failed to delete import session",
+ variant: "destructive",
+ });
+ },
+ });
+}