From 9aa609e8154d329b693e31b3354c6d3d20ac8216 Mon Sep 17 00:00:00 2001 From: ahmed-abdelkarim Date: Sun, 7 Sep 2025 18:19:26 +0300 Subject: feat: Show loading indicator while file is being generated #1787 (#1870) Co-authored-by: ahmed.abdelakrim --- apps/web/components/settings/ImportExport.tsx | 58 +++++++++++++++++++++++---- 1 file changed, 51 insertions(+), 7 deletions(-) (limited to 'apps/web') diff --git a/apps/web/components/settings/ImportExport.tsx b/apps/web/components/settings/ImportExport.tsx index ba2e9129..c644af27 100644 --- a/apps/web/components/settings/ImportExport.tsx +++ b/apps/web/components/settings/ImportExport.tsx @@ -1,8 +1,7 @@ "use client"; -import { useState } from "react"; -import Link from "next/link"; -import { buttonVariants } from "@/components/ui/button"; +import { useCallback, useEffect, useState } from "react"; +import { Button, buttonVariants } from "@/components/ui/button"; import FilePickerButton from "@/components/ui/file-picker-button"; import { Progress } from "@/components/ui/progress"; import { @@ -15,9 +14,11 @@ import { import { useBookmarkImport } from "@/lib/hooks/useBookmarkImport"; import { useTranslation } from "@/lib/i18n/client"; import { cn } from "@/lib/utils"; -import { Download, Upload } from "lucide-react"; +import { useQuery, useQueryClient } from "@tanstack/react-query"; +import { Download, Loader2, Upload } from "lucide-react"; import { Card, CardContent } from "../ui/card"; +import { toast } from "../ui/use-toast"; function ImportCard({ text, @@ -47,6 +48,47 @@ function ImportCard({ function ExportButton() { const { t } = useTranslation(); const [format, setFormat] = useState<"json" | "netscape">("json"); + const queryClient = useQueryClient(); + const { isFetching, refetch, error } = useQuery({ + queryKey: ["exportBookmarks"], + queryFn: async () => { + const res = await fetch(`/api/bookmarks/export?format=${format}`); + if (!res.ok) { + const error = await res.json(); + throw new Error(error?.error || "Failed to export bookmarks"); + } + const match = res.headers + .get("Content-Disposition") + ?.match(/filename\*?=(?:UTF-8''|")?([^"]+)/i); + const filename = match + ? match[1] + : `karakeep-export-${new Date().toISOString()}.${format}`; + return { blob: res.blob(), filename }; + }, + enabled: false, + }); + + useEffect(() => { + if (error) { + toast({ + description: error.message, + variant: "destructive", + }); + } + }, [error]); + + const onExport = useCallback(async () => { + const { data } = await refetch(); + if (!data) return; + const { blob, filename } = data; + const url = window.URL.createObjectURL(await blob); + const a = document.createElement("a"); + a.href = url; + a.download = filename; + a.click(); + window.URL.revokeObjectURL(url); + queryClient.setQueryData(["exportBookmarks"], () => null); + }, [refetch]); return ( @@ -70,15 +112,17 @@ function ExportButton() { - + {isFetching && }

Export

- +
); -- cgit v1.2.3-70-g09d2