diff options
| author | ahmed-abdelkarim <ahmad.abdelkarim89@gmail.com> | 2025-09-07 18:19:26 +0300 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2025-09-07 16:19:26 +0100 |
| commit | 9aa609e8154d329b693e31b3354c6d3d20ac8216 (patch) | |
| tree | f4e21d58de4e34f8bdf8253c6e18e0453340c633 /apps/web | |
| parent | c57fd5137cc29870667777a371a4d1fcdf69436b (diff) | |
| download | karakeep-9aa609e8154d329b693e31b3354c6d3d20ac8216.tar.zst | |
feat: Show loading indicator while file is being generated #1787 (#1870)
Co-authored-by: ahmed.abdelakrim <ahmad.abdelakrim89@gmail.com>
Diffstat (limited to 'apps/web')
| -rw-r--r-- | apps/web/components/settings/ImportExport.tsx | 58 |
1 files changed, 51 insertions, 7 deletions
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 ( <Card className="transition-all hover:shadow-md"> @@ -70,15 +112,17 @@ function ExportButton() { </SelectContent> </Select> </div> - <Link - href={`/api/bookmarks/export?format=${format}`} + <Button className={cn( buttonVariants({ variant: "default", size: "sm" }), "flex items-center gap-2", )} + onClick={onExport} + disabled={isFetching} > + {isFetching && <Loader2 className="mr-2 animate-spin" />} <p>Export</p> - </Link> + </Button> </CardContent> </Card> ); |
