diff options
| -rw-r--r-- | apps/web/components/settings/ImportExport.tsx | 13 | ||||
| -rw-r--r-- | apps/web/lib/hooks/useBookmarkImport.ts | 43 | ||||
| -rw-r--r-- | packages/shared/import-export/index.ts | 1 |
3 files changed, 54 insertions, 3 deletions
diff --git a/apps/web/components/settings/ImportExport.tsx b/apps/web/components/settings/ImportExport.tsx index 7d127443..b6e4da9a 100644 --- a/apps/web/components/settings/ImportExport.tsx +++ b/apps/web/components/settings/ImportExport.tsx @@ -1,6 +1,7 @@ "use client"; import { useCallback, useEffect, useState } from "react"; +import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"; import { Button, buttonVariants } from "@/components/ui/button"; import FilePickerButton from "@/components/ui/file-picker-button"; import { Progress } from "@/components/ui/progress"; @@ -15,7 +16,7 @@ import { useBookmarkImport } from "@/lib/hooks/useBookmarkImport"; import { useTranslation } from "@/lib/i18n/client"; import { cn } from "@/lib/utils"; import { useQuery, useQueryClient } from "@tanstack/react-query"; -import { Download, Loader2, Upload } from "lucide-react"; +import { AlertCircle, Download, Loader2, Upload } from "lucide-react"; import { Card, CardContent } from "../ui/card"; import { toast } from "../ui/use-toast"; @@ -131,10 +132,18 @@ function ExportButton() { export function ImportExportRow() { const { t } = useTranslation(); - const { importProgress, runUploadBookmarkFile } = useBookmarkImport(); + const { importProgress, quotaError, runUploadBookmarkFile } = + useBookmarkImport(); return ( <div className="flex flex-col gap-3"> + {quotaError && ( + <Alert variant="destructive" className="relative"> + <AlertCircle className="h-4 w-4" /> + <AlertTitle>Import Quota Exceeded</AlertTitle> + <AlertDescription>{quotaError}</AlertDescription> + </Alert> + )} <div className="grid gap-4 md:grid-cols-2"> <ImportCard text="HTML File" diff --git a/apps/web/lib/hooks/useBookmarkImport.ts b/apps/web/lib/hooks/useBookmarkImport.ts index d4ffda4e..0d9bbaaf 100644 --- a/apps/web/lib/hooks/useBookmarkImport.ts +++ b/apps/web/lib/hooks/useBookmarkImport.ts @@ -13,10 +13,12 @@ import { useAddBookmarkToList, useCreateBookmarkList, } from "@karakeep/shared-react/hooks/lists"; +import { api } from "@karakeep/shared-react/trpc"; import { importBookmarksFromFile, ImportSource, ParsedBookmark, + parseImportFile, } from "@karakeep/shared/import-export"; import { BookmarkTypes, @@ -36,7 +38,9 @@ export function useBookmarkImport() { const [importProgress, setImportProgress] = useState<ImportProgress | null>( null, ); + const [quotaError, setQuotaError] = useState<string | null>(null); + const apiUtils = api.useUtils(); const { mutateAsync: createImportSession } = useCreateImportSession(); const { mutateAsync: createBookmark } = useCreateBookmarkWithPostHook(); const { mutateAsync: createList } = useCreateBookmarkList(); @@ -51,6 +55,36 @@ export function useBookmarkImport() { file: File; source: ImportSource; }) => { + // Clear any previous quota error + setQuotaError(null); + + // First, parse the file to count bookmarks + const textContent = await file.text(); + const parsedBookmarks = parseImportFile(source, textContent); + const bookmarkCount = parsedBookmarks.length; + + // Check quota before proceeding + if (bookmarkCount > 0) { + const quotaUsage = + await apiUtils.client.subscriptions.getQuotaUsage.query(); + + if ( + !quotaUsage.bookmarks.unlimited && + quotaUsage.bookmarks.quota !== null + ) { + const remaining = + quotaUsage.bookmarks.quota - quotaUsage.bookmarks.used; + + if (remaining < bookmarkCount) { + const errorMsg = `Cannot import ${bookmarkCount} bookmarks. You have ${remaining} bookmark${remaining === 1 ? "" : "s"} remaining in your quota of ${quotaUsage.bookmarks.quota}.`; + setQuotaError(errorMsg); + throw new Error(errorMsg); + } + } + } + + // Proceed with import if quota check passes + // Use a custom parser to avoid re-parsing the file const result = await importBookmarksFromFile( { file, @@ -122,7 +156,12 @@ export function useBookmarkImport() { }, onProgress: (done, total) => setImportProgress({ done, total }), }, - {}, + { + // Use a custom parser to avoid re-parsing the file + parsers: { + [source]: () => parsedBookmarks, + }, + }, ); return result; }, @@ -158,6 +197,8 @@ export function useBookmarkImport() { return { importProgress, + quotaError, + clearQuotaError: () => setQuotaError(null), runUploadBookmarkFile: uploadBookmarkFileMutation.mutateAsync, isImporting: uploadBookmarkFileMutation.isPending, }; diff --git a/packages/shared/import-export/index.ts b/packages/shared/import-export/index.ts index 2d720d0b..dd266e06 100644 --- a/packages/shared/import-export/index.ts +++ b/packages/shared/import-export/index.ts @@ -1,3 +1,4 @@ export * from "./exporters"; export * from "./importer"; export type { ImportSource, ParsedBookmark } from "./parsers"; +export { parseImportFile } from "./parsers"; |
