diff --git a/app/client/modules/backups/components/snapshot-file-browser.tsx b/app/client/modules/backups/components/snapshot-file-browser.tsx
index 2fd534bc..078a126d 100644
--- a/app/client/modules/backups/components/snapshot-file-browser.tsx
+++ b/app/client/modules/backups/components/snapshot-file-browser.tsx
@@ -1,15 +1,12 @@
-import { useCallback } from "react";
-import { useQuery, useQueryClient } from "@tanstack/react-query";
-import { FileIcon, RotateCcw, Trash2 } from "lucide-react";
-import { FileTree } from "~/client/components/file-tree";
+import { RotateCcw, Trash2 } from "lucide-react";
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "~/client/components/ui/card";
import { Button, buttonVariants } from "~/client/components/ui/button";
import type { Snapshot } from "~/client/lib/types";
-import { listSnapshotFilesOptions } from "~/client/api-client/@tanstack/react-query.gen";
import { formatDateTime } from "~/client/lib/datetime";
-import { useFileBrowser } from "~/client/hooks/use-file-browser";
import { cn } from "~/client/lib/utils";
import { Link } from "@tanstack/react-router";
+import { SnapshotTreeBrowser } from "~/client/components/file-browsers/snapshot-tree-browser";
+import { findCommonAncestor } from "~/utils/common-ancestor";
interface Props {
snapshot: Snapshot;
@@ -22,65 +19,7 @@ interface Props {
export const SnapshotFileBrowser = (props: Props) => {
const { snapshot, repositoryId, backupId, onDeleteSnapshot, isDeletingSnapshot } = props;
- const queryClient = useQueryClient();
-
- const volumeBasePath = snapshot.paths[0]?.match(/^(.*?_data)(\/|$)/)?.[1] || "/";
-
- const { data: filesData, isLoading: filesLoading } = useQuery({
- ...listSnapshotFilesOptions({
- path: { id: repositoryId, snapshotId: snapshot.short_id },
- query: { path: volumeBasePath },
- }),
- });
-
- const stripBasePath = useCallback(
- (path: string): string => {
- if (!volumeBasePath) return path;
- if (path === volumeBasePath) return "/";
- if (path.startsWith(`${volumeBasePath}/`)) {
- const stripped = path.slice(volumeBasePath.length);
- return stripped;
- }
- return path;
- },
- [volumeBasePath],
- );
-
- const addBasePath = useCallback(
- (displayPath: string): string => {
- const vbp = volumeBasePath === "/" ? "" : volumeBasePath;
-
- if (!vbp) return displayPath;
- if (displayPath === "/") return vbp;
- return `${vbp}${displayPath}`;
- },
- [volumeBasePath],
- );
-
- const fileBrowser = useFileBrowser({
- initialData: filesData,
- isLoading: filesLoading,
- fetchFolder: async (path, offset = 0) => {
- return await queryClient.ensureQueryData(
- listSnapshotFilesOptions({
- path: { id: repositoryId, snapshotId: snapshot.short_id },
- query: { path, offset: offset.toString(), limit: "500" },
- }),
- );
- },
- prefetchFolder: (path) => {
- void queryClient.prefetchQuery(
- listSnapshotFilesOptions({
- path: { id: repositoryId, snapshotId: snapshot.short_id },
- query: { path, offset: "0", limit: "500" },
- }),
- );
- },
- pathTransform: {
- strip: stripBasePath,
- add: addBasePath,
- },
- });
+ const volumeBasePath = findCommonAncestor(snapshot.paths);
return (
@@ -126,33 +65,18 @@ export const SnapshotFileBrowser = (props: Props) => {
- {fileBrowser.isLoading && (
-
- )}
-
- {fileBrowser.isEmpty && (
-
-
-
No files in this snapshot
-
- )}
-
- {!fileBrowser.isLoading && !fileBrowser.isEmpty && (
-
-
-
- )}
+
diff --git a/app/client/modules/notifications/components/create-notification-form.tsx b/app/client/modules/notifications/components/create-notification-form.tsx
index f4473125..d166f858 100644
--- a/app/client/modules/notifications/components/create-notification-form.tsx
+++ b/app/client/modules/notifications/components/create-notification-form.tsx
@@ -15,6 +15,7 @@ import {
import { Input } from "~/client/components/ui/input";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "~/client/components/ui/select";
import { notificationConfigSchemaBase } from "~/schemas/notifications";
+import { useScrollToFormError } from "~/client/hooks/use-scroll-to-form-error";
import {
CustomForm,
DiscordForm,
@@ -116,11 +117,16 @@ export const CreateNotificationForm = ({ onSubmit, mode = "create", initialValue
});
const { watch } = form;
+ const scrollToFirstError = useScrollToFormError();
const watchedType = watch("type");
return (