refactor: pr feedbacks

This commit is contained in:
Nicolas Meienberger 2026-02-11 19:23:06 +01:00
parent caeb867554
commit 9422815843
10 changed files with 55 additions and 66 deletions

View file

@ -1,6 +1,6 @@
import { useMutation, useQuery, useSuspenseQuery } from "@tanstack/react-query";
import { Copy, Plus, Trash2 } from "lucide-react";
import { useMemo, useState } from "react";
import { useEffect, useMemo, useState } from "react";
import { toast } from "sonner";
import { Button } from "~/client/components/ui/button";
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "~/client/components/ui/card";
@ -38,9 +38,9 @@ type MirrorAssignment = {
lastCopyError: string | null;
};
export const ScheduleMirrorsConfig = ({ scheduleId, primaryRepositoryId, repositories, initialData }: Props) => {
const buildAssignments = (mirrors: GetScheduleMirrorsResponse) => {
const map = new Map<string, MirrorAssignment>();
for (const mirror of initialData) {
for (const mirror of mirrors) {
map.set(mirror.repositoryId, {
repositoryId: mirror.repositoryId,
enabled: mirror.enabled,
@ -49,8 +49,11 @@ export const ScheduleMirrorsConfig = ({ scheduleId, primaryRepositoryId, reposit
lastCopyError: mirror.lastCopyError,
});
}
return map;
};
const [assignments, setAssignments] = useState<Map<string, MirrorAssignment>>(map);
export const ScheduleMirrorsConfig = ({ scheduleId, primaryRepositoryId, repositories, initialData }: Props) => {
const [assignments, setAssignments] = useState<Map<string, MirrorAssignment>>(() => buildAssignments(initialData));
const [hasChanges, setHasChanges] = useState(false);
const [isAddingNew, setIsAddingNew] = useState(false);
@ -58,6 +61,12 @@ export const ScheduleMirrorsConfig = ({ scheduleId, primaryRepositoryId, reposit
...getScheduleMirrorsOptions({ path: { scheduleId: scheduleId.toString() } }),
});
useEffect(() => {
if (!hasChanges) {
setAssignments(buildAssignments(currentMirrors));
}
}, [currentMirrors, hasChanges]);
const { data: compatibility } = useQuery({
...getMirrorCompatibilityOptions({ path: { scheduleId: scheduleId.toString() } }),
});

View file

@ -30,7 +30,7 @@ export const clientLoader = async () => {
};
export function BackupsPage() {
const { data: schedules, isLoading } = useSuspenseQuery({
const { data: schedules } = useSuspenseQuery({
...listBackupSchedulesOptions(),
});
@ -69,14 +69,6 @@ export function BackupsPage() {
}
};
if (isLoading) {
return (
<div className="flex items-center justify-center h-full">
<p className="text-muted-foreground">Loading backup schedules...</p>
</div>
);
}
if (!schedules || schedules.length === 0) {
return (
<EmptyState

View file

@ -21,7 +21,7 @@ export function CreateBackupPage() {
const formId = useId();
const [selectedVolumeId, setSelectedVolumeId] = useState<number | undefined>();
const { data: volumesData, isLoading: loadingVolumes } = useSuspenseQuery({
const { data: volumesData } = useSuspenseQuery({
...listVolumesOptions(),
});
@ -79,14 +79,6 @@ export function CreateBackupPage() {
const selectedVolume = volumesData.find((v) => v.id === selectedVolumeId);
if (loadingVolumes) {
return (
<div className="flex items-center justify-center h-full">
<p className="text-muted-foreground">Loading...</p>
</div>
);
}
if (!volumesData.length) {
return (
<EmptyState

View file

@ -122,32 +122,34 @@ export function SnapshotDetailsPage({ repositoryId, snapshotId }: { repositoryId
<span className="text-muted-foreground">Time:</span>
<p>{formatDateTime(data.time)}</p>
</div>
<>
<div>
<span className="text-muted-foreground">Backup Schedule:</span>
<p>
<Link
to="/backups/$scheduleId"
className="text-primary hover:underline"
params={{ scheduleId: backupSchedule?.shortId || "" }}
>
{backupSchedule?.name}
</Link>
</p>
</div>
<div>
<span className="text-muted-foreground">Volume:</span>
<p>
<Link
to={`/volumes/$volumeId`}
className="text-primary hover:underline"
params={{ volumeId: backupSchedule?.volume.shortId || "" }}
>
{backupSchedule?.volume.name}
</Link>
</p>
</div>
</>
{backupSchedule && (
<>
<div>
<span className="text-muted-foreground">Backup Schedule:</span>
<p>
<Link
to="/backups/$scheduleId"
className="text-primary hover:underline"
params={{ scheduleId: backupSchedule.shortId }}
>
{backupSchedule?.name}
</Link>
</p>
</div>
<div>
<span className="text-muted-foreground">Volume:</span>
<p>
<Link
to={`/volumes/$volumeId`}
className="text-primary hover:underline"
params={{ volumeId: backupSchedule.volume.shortId }}
>
{backupSchedule?.volume.name}
</Link>
</p>
</div>
</>
)}
<div className="col-span-2">
<span className="text-muted-foreground">Paths:</span>

View file

@ -11,7 +11,7 @@ export const authMiddleware = createMiddleware().server(async ({ next, request }
const isAuthRoute = ["/login", "/onboarding"].includes(new URL(request.url).pathname);
if (!session?.user?.id && !isAuthRoute) {
const status = await getStatus();
const status = await getStatus().catch(() => ({ data: { hasUsers: false } }));
if (!status.data?.hasUsers) {
throw redirect({ to: "/onboarding" });
}

View file

@ -2,15 +2,16 @@ import { createFileRoute } from "@tanstack/react-router";
import { createServerFn } from "@tanstack/react-start";
import { getRequestHeaders } from "@tanstack/react-start/server";
import { Layout } from "~/client/components/layout";
import type { AppContext } from "~/context";
import { authMiddleware } from "~/middleware/auth";
import { auth } from "~/server/lib/auth";
import { authService } from "~/server/modules/auth/auth.service";
export const fetchUser = createServerFn({ method: "GET" }).handler(async () => {
const headers = getRequestHeaders();
const session = await auth.api.getSession({ headers });
const hasUsers = await authService.hasUsers();
return { user: session?.user, hasUsers: true };
return { user: session?.user ?? null, hasUsers };
});
export const Route = createFileRoute("/(dashboard)")({
@ -21,7 +22,7 @@ export const Route = createFileRoute("/(dashboard)")({
},
loader: async () => {
const authContext = await fetchUser();
return authContext as AppContext;
return authContext;
},
});

View file

@ -48,6 +48,7 @@ const envSchema = type({
appVersion: s.APP_VERSION,
trustedOrigins: s.TRUSTED_ORIGINS?.split(",")
.map((origin) => origin.trim())
.filter(Boolean)
.concat(s.BASE_URL) ?? [s.BASE_URL],
disableRateLimiting: s.DISABLE_RATE_LIMITING === "true",
appSecret: s.APP_SECRET,

View file

@ -99,7 +99,6 @@
"typescript": "^5.9.3",
"vite": "^7.3.1",
"vite-bundle-analyzer": "^1.2.3",
"vite-plugin-babel": "^1.4.1",
"vite-tsconfig-paths": "^6.0.5",
"wait-for-expect": "^4.0.0",
},
@ -1349,7 +1348,7 @@
"next-themes": ["next-themes@0.4.6", "", { "peerDependencies": { "react": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc", "react-dom": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc" } }, "sha512-pZvgD5L0IEvX5/9GWyHMf3m8BKiVQwsCMHfoFosXtXBMnaS0ZnIJ9ST4b4NqLVKDEm8QBxoNNGNaBv2JNF6XNA=="],
"nf3": ["nf3@0.3.9", "", {}, "sha512-vgmoL6pUXpKXx+WepG4H1xc2nf1N/5vKgU9QerryuSFIyef8EDkj7esYcIgqMe/mE6nGsy6M6b8wfssEBQizVQ=="],
"nf3": ["nf3@0.3.10", "", {}, "sha512-UlqmHkZiHGgSkRj17yrOXEsSu5ECvtlJ3Xm1W5WsWrTKgu9m7OjrMZh9H/ME2LcWrTlMD0/vmmNVpyBG4yRdGg=="],
"nitro": ["nitro@3.0.1-alpha.2", "", { "dependencies": { "consola": "^3.4.2", "crossws": "^0.4.3", "db0": "^0.3.4", "h3": "^2.0.1-rc.11", "jiti": "^2.6.1", "nf3": "^0.3.5", "ofetch": "^2.0.0-alpha.3", "ohash": "^2.0.11", "oxc-minify": "^0.110.0", "oxc-transform": "^0.110.0", "srvx": "^0.10.1", "undici": "^7.18.2", "unenv": "^2.0.0-rc.24", "unstorage": "^2.0.0-alpha.5" }, "peerDependencies": { "rolldown": ">=1.0.0-beta.0", "rollup": "^4", "vite": "^7 || ^8 || >=8.0.0-0", "xml2js": "^0.6.2" }, "optionalPeers": ["rolldown", "rollup", "vite", "xml2js"], "bin": { "nitro": "dist/cli/index.mjs" } }, "sha512-YviDY5J/trS821qQ1fpJtpXWIdPYiOizC/meHavlm1Hfuhx//H+Egd1+4C5SegJRgtWMnRPW9n//6Woaw81cTQ=="],
@ -1621,8 +1620,6 @@
"vite-bundle-analyzer": ["vite-bundle-analyzer@1.3.6", "", { "bin": { "analyze": "dist/bin.js" } }, "sha512-elFXkF9oGy4CJEeOdP1DSEHRDKWfmaEDfT9/GuF4jmyfmUF6nC//iN+ythqMEVvrtchOEHGKLumZwnu1NjoI0w=="],
"vite-plugin-babel": ["vite-plugin-babel@1.4.1", "", { "peerDependencies": { "@babel/core": "^7.0.0", "vite": "^2.7.0 || ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" } }, "sha512-quO+viHGSv1cjbfhbeiMZ7SZpo8P29NiUh9LJfKhpmIDwy0THRiTRUbanBbkNcZcSyHFgp1n7TByd1C2kanqLQ=="],
"vite-tsconfig-paths": ["vite-tsconfig-paths@6.0.5", "", { "dependencies": { "debug": "^4.1.1", "globrex": "^0.1.2", "tsconfck": "^3.0.3" }, "peerDependencies": { "vite": "*" } }, "sha512-f/WvY6ekHykUF1rWJUAbCU7iS/5QYDIugwpqJA+ttwKbxSbzNlqlE8vZSrsnxNQciUW+z6lvhlXMaEyZn9MSig=="],
"vitefu": ["vitefu@1.1.1", "", { "peerDependencies": { "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0-beta.0" }, "optionalPeers": ["vite"] }, "sha512-B/Fegf3i8zh0yFbpzZ21amWzHmuNlLlmJT6n7bu5e+pCHUKQIfXSYokrqOBGEMMe9UG2sostKQF9mml/vYaWJQ=="],

View file

@ -120,7 +120,6 @@
"typescript": "^5.9.3",
"vite": "^7.3.1",
"vite-bundle-analyzer": "^1.2.3",
"vite-plugin-babel": "^1.4.1",
"vite-tsconfig-paths": "^6.0.5",
"wait-for-expect": "^4.0.0"
},

View file

@ -4,18 +4,10 @@ import { tanstackStart } from "@tanstack/react-start/plugin/vite";
import { nitro } from "nitro/vite";
import tsconfigPaths from "vite-tsconfig-paths";
import viteReact from "@vitejs/plugin-react";
import babel from "vite-plugin-babel";
export default defineConfig({
plugins: [
tsconfigPaths(),
babel({
filter: /\.[jt]sx?$/,
babelConfig: {
presets: ["@babel/preset-typescript"],
plugins: [["babel-plugin-react-compiler"]],
},
}),
tanstackStart({
srcDirectory: "app",
router: {
@ -23,7 +15,11 @@ export default defineConfig({
},
}),
nitro({ preset: "bun" }),
viteReact(),
viteReact({
babel: {
plugins: ["babel-plugin-react-compiler"],
},
}),
tailwindcss(),
],
build: {