diff --git a/app/client/modules/repositories/routes/snapshot-details.tsx b/app/client/modules/repositories/routes/snapshot-details.tsx
index 6674eae4..1d1d14c5 100644
--- a/app/client/modules/repositories/routes/snapshot-details.tsx
+++ b/app/client/modules/repositories/routes/snapshot-details.tsx
@@ -4,6 +4,7 @@ import {
getSnapshotDetailsOptions,
listBackupSchedulesOptions,
} from "~/client/api-client/@tanstack/react-query.gen";
+import type { GetSnapshotDetailsResponse } from "~/client/api-client/types.gen";
import { Button } from "~/client/components/ui/button";
import { Card, CardContent, CardHeader, CardTitle } from "~/client/components/ui/card";
import { SnapshotFileBrowser } from "~/client/modules/backups/components/snapshot-file-browser";
@@ -49,7 +50,13 @@ const SnapshotFileBrowserSkeleton = () => (
);
-export function SnapshotDetailsPage({ repositoryId, snapshotId }: { repositoryId: string; snapshotId: string }) {
+type Props = {
+ repositoryId: string;
+ snapshotId: string;
+ initialSnapshot?: GetSnapshotDetailsResponse;
+};
+
+export function SnapshotDetailsPage({ repositoryId, snapshotId, initialSnapshot }: Props) {
const [showAllPaths, setShowAllPaths] = useState(false);
const { data: repository } = useSuspenseQuery({
@@ -62,6 +69,7 @@ export function SnapshotDetailsPage({ repositoryId, snapshotId }: { repositoryId
const { data, error } = useQuery({
...getSnapshotDetailsOptions({ path: { shortId: repositoryId, snapshotId: snapshotId } }),
+ initialData: initialSnapshot,
});
const backupSchedule = schedules?.find((s) => data?.tags?.includes(s.shortId));
diff --git a/app/client/modules/settings/components/org-members-section.tsx b/app/client/modules/settings/components/org-members-section.tsx
index c9594762..8ae2112b 100644
--- a/app/client/modules/settings/components/org-members-section.tsx
+++ b/app/client/modules/settings/components/org-members-section.tsx
@@ -2,12 +2,12 @@ import { useMutation, useSuspenseQuery } from "@tanstack/react-query";
import { Shield, ShieldAlert, Trash2 } from "lucide-react";
import { useState } from "react";
import { toast } from "sonner";
-import type { GetOrgMembersResponse } from "~/client/api-client";
import {
getOrgMembersOptions,
removeOrgMemberMutation,
updateMemberRoleMutation,
} from "~/client/api-client/@tanstack/react-query.gen";
+import type { GetOrgMembersResponse } from "~/client/api-client/types.gen";
import {
AlertDialog,
AlertDialogAction,
@@ -25,11 +25,11 @@ import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "~
import { useOrganizationContext } from "~/client/hooks/use-org-context";
import { cn } from "~/client/lib/utils";
-type OrgMembersSectionProps = {
+type Props = {
initialMembers?: GetOrgMembersResponse;
};
-export function OrgMembersSection({ initialMembers }: OrgMembersSectionProps) {
+export function OrgMembersSection({ initialMembers }: Props) {
const { activeMember } = useOrganizationContext();
const [memberToRemove, setMemberToRemove] = useState<{ id: string; name: string } | null>(null);
diff --git a/app/client/modules/settings/components/sso/sso-settings-section.tsx b/app/client/modules/settings/components/sso/sso-settings-section.tsx
index ec98d675..6b5adcad 100644
--- a/app/client/modules/settings/components/sso/sso-settings-section.tsx
+++ b/app/client/modules/settings/components/sso/sso-settings-section.tsx
@@ -9,6 +9,7 @@ import {
getSsoSettingsOptions,
updateSsoProviderAutoLinkingMutation,
} from "~/client/api-client/@tanstack/react-query.gen";
+import type { GetSsoSettingsResponse } from "~/client/api-client/types.gen";
import {
AlertDialog,
AlertDialogAction,
@@ -33,17 +34,17 @@ import { getOrigin } from "~/client/functions/get-origin";
import { authClient } from "~/client/lib/auth-client";
import { cn } from "~/client/lib/utils";
import { useServerFn } from "@tanstack/react-start";
-import type { GetSsoSettingsResponse } from "~/client/api-client";
type InvitationRole = "member" | "admin" | "owner";
-type SsoSettingsSectionProps = {
+type Props = {
initialSettings?: GetSsoSettingsResponse;
+ initialOrigin?: string;
};
-export function SsoSettingsSection({ initialSettings }: SsoSettingsSectionProps) {
+export function SsoSettingsSection({ initialSettings, initialOrigin }: Props) {
const originQuery = useServerFn(getOrigin);
- const { data } = useSuspenseQuery({ queryKey: ["app-origin"], queryFn: originQuery });
+ const { data } = useSuspenseQuery({ queryKey: ["app-origin"], queryFn: originQuery, initialData: initialOrigin });
const navigate = useNavigate();
const { activeOrganization } = useOrganizationContext();
diff --git a/app/client/modules/settings/routes/settings.tsx b/app/client/modules/settings/routes/settings.tsx
index 797b8281..3a304c8b 100644
--- a/app/client/modules/settings/routes/settings.tsx
+++ b/app/client/modules/settings/routes/settings.tsx
@@ -3,6 +3,7 @@ import { Download, KeyRound, User, X, Settings as SettingsIcon, Building2 } from
import { useState } from "react";
import { toast } from "sonner";
import { downloadResticPasswordMutation } from "~/client/api-client/@tanstack/react-query.gen";
+import type { GetOrgMembersResponse, GetSsoSettingsResponse } from "~/client/api-client/types.gen";
import { Button } from "~/client/components/ui/button";
import { Card, CardContent, CardDescription, CardTitle } from "~/client/components/ui/card";
import {
@@ -24,15 +25,15 @@ import { useNavigate, useSearch } from "@tanstack/react-router";
import { SsoSettingsSection } from "../components/sso/sso-settings-section";
import { OrgMembersSection } from "../components/org-members-section";
import { useOrganizationContext } from "~/client/hooks/use-org-context";
-import type { GetOrgMembersResponse, GetSsoSettingsResponse } from "~/client/api-client";
type Props = {
appContext: AppContext;
initialMembers?: GetOrgMembersResponse;
initialSsoSettings?: GetSsoSettingsResponse;
+ initialOrigin?: string;
};
-export function SettingsPage({ appContext, initialMembers, initialSsoSettings }: Props) {
+export function SettingsPage({ appContext, initialMembers, initialSsoSettings, initialOrigin }: Props) {
const [currentPassword, setCurrentPassword] = useState("");
const [newPassword, setNewPassword] = useState("");
const [confirmPassword, setConfirmPassword] = useState("");
@@ -312,7 +313,7 @@ export function SettingsPage({ appContext, initialMembers, initialSsoSettings }:
Configure OIDC provider settings
-
+
diff --git a/app/routes/(dashboard)/backups/$backupId/index.tsx b/app/routes/(dashboard)/backups/$backupId/index.tsx
index 84f2a9ad..842a17de 100644
--- a/app/routes/(dashboard)/backups/$backupId/index.tsx
+++ b/app/routes/(dashboard)/backups/$backupId/index.tsx
@@ -1,6 +1,7 @@
import { createFileRoute } from "@tanstack/react-router";
import { type } from "arktype";
import {
+ getBackupProgressOptions,
getBackupScheduleOptions,
getScheduleMirrorsOptions,
getScheduleNotificationsOptions,
@@ -24,6 +25,7 @@ export const Route = createFileRoute("/(dashboard)/backups/$backupId/")({
context.queryClient.ensureQueryData({ ...listRepositoriesOptions() }),
context.queryClient.ensureQueryData({ ...getScheduleNotificationsOptions({ path: { shortId: backupId } }) }),
context.queryClient.ensureQueryData({ ...getScheduleMirrorsOptions({ path: { shortId: backupId } }) }),
+ context.queryClient.ensureQueryData({ ...getBackupProgressOptions({ path: { shortId: backupId } }) }),
]);
const snapshotOptions = listSnapshotsOptions({
diff --git a/app/routes/(dashboard)/repositories/$repositoryId/$snapshotId/index.tsx b/app/routes/(dashboard)/repositories/$repositoryId/$snapshotId/index.tsx
index 92c289aa..98cbc480 100644
--- a/app/routes/(dashboard)/repositories/$repositoryId/$snapshotId/index.tsx
+++ b/app/routes/(dashboard)/repositories/$repositoryId/$snapshotId/index.tsx
@@ -1,16 +1,30 @@
import { createFileRoute } from "@tanstack/react-router";
-import { getRepositoryOptions } from "~/client/api-client/@tanstack/react-query.gen";
+import {
+ getRepositoryOptions,
+ getSnapshotDetailsOptions,
+ listBackupSchedulesOptions,
+} from "~/client/api-client/@tanstack/react-query.gen";
import { SnapshotDetailsPage } from "~/client/modules/repositories/routes/snapshot-details";
+import { prefetchOrSkip } from "~/utils/prefetch";
export const Route = createFileRoute("/(dashboard)/repositories/$repositoryId/$snapshotId/")({
component: RouteComponent,
errorComponent: (e) =>
{e.error.message}
,
loader: async ({ params, context }) => {
- const res = await context.queryClient.ensureQueryData({
- ...getRepositoryOptions({ path: { shortId: params.repositoryId } }),
- });
+ const [res] = await Promise.all([
+ context.queryClient.ensureQueryData({ ...getRepositoryOptions({ path: { shortId: params.repositoryId } }) }),
+ context.queryClient.ensureQueryData({ ...listBackupSchedulesOptions() }),
+ ]);
- return res;
+ const snapshotOptions = getSnapshotDetailsOptions({
+ path: { shortId: params.repositoryId, snapshotId: params.snapshotId },
+ });
+ await prefetchOrSkip(context.queryClient, snapshotOptions);
+
+ return {
+ ...res,
+ snapshot: context.queryClient.getQueryData(snapshotOptions.queryKey),
+ };
},
staticData: {
breadcrumb: (match) => [
@@ -32,6 +46,7 @@ export const Route = createFileRoute("/(dashboard)/repositories/$repositoryId/$s
function RouteComponent() {
const { repositoryId, snapshotId } = Route.useParams();
+ const { snapshot } = Route.useLoaderData();
- return ;
+ return ;
}
diff --git a/app/routes/(dashboard)/repositories/$repositoryId/index.tsx b/app/routes/(dashboard)/repositories/$repositoryId/index.tsx
index 86c5c125..0e7158f6 100644
--- a/app/routes/(dashboard)/repositories/$repositoryId/index.tsx
+++ b/app/routes/(dashboard)/repositories/$repositoryId/index.tsx
@@ -14,20 +14,19 @@ export const Route = createFileRoute("/(dashboard)/repositories/$repositoryId/")
errorComponent: (e) => {e.error.message}
,
loader: async ({ params, context }) => {
const snapshotOptions = listSnapshotsOptions({ path: { shortId: params.repositoryId } });
- const schedulesOptions = listBackupSchedulesOptions();
const statsOptions = getRepositoryStatsOptions({ path: { shortId: params.repositoryId } });
- const [res] = await Promise.all([
+ const [res, schedules] = await Promise.all([
context.queryClient.ensureQueryData(getRepositoryOptions({ path: { shortId: params.repositoryId } })),
+ context.queryClient.ensureQueryData(listBackupSchedulesOptions()),
prefetchOrSkip(context.queryClient, snapshotOptions),
- prefetchOrSkip(context.queryClient, schedulesOptions),
prefetchOrSkip(context.queryClient, statsOptions),
]);
return {
...res,
snapshots: context.queryClient.getQueryData(snapshotOptions.queryKey),
- backupSchedules: context.queryClient.getQueryData(schedulesOptions.queryKey),
+ backupSchedules: schedules,
stats: context.queryClient.getQueryData(statsOptions.queryKey),
};
},
diff --git a/app/routes/(dashboard)/route.tsx b/app/routes/(dashboard)/route.tsx
index 1b4e9ab4..0c26a03c 100644
--- a/app/routes/(dashboard)/route.tsx
+++ b/app/routes/(dashboard)/route.tsx
@@ -6,6 +6,7 @@ import { SIDEBAR_COOKIE_NAME } from "~/client/components/ui/sidebar";
import { authMiddleware } from "~/middleware/auth";
import { auth } from "~/server/lib/auth";
import { getOrganizationContext } from "~/server/lib/functions/organization-context";
+import { getServerConstants } from "~/server/lib/functions/server-constants";
import { authService } from "~/server/modules/auth/auth.service";
export const fetchUser = createServerFn({ method: "GET" }).handler(async () => {
@@ -32,6 +33,10 @@ export const Route = createFileRoute("/(dashboard)")({
queryKey: ["organization-context"],
queryFn: () => getOrganizationContext(),
}),
+ context.queryClient.ensureQueryData({
+ queryKey: ["server-constants"],
+ queryFn: () => getServerConstants(),
+ }),
]);
return authContext;
diff --git a/app/routes/(dashboard)/settings/index.tsx b/app/routes/(dashboard)/settings/index.tsx
index f3adff3c..f7816c2e 100644
--- a/app/routes/(dashboard)/settings/index.tsx
+++ b/app/routes/(dashboard)/settings/index.tsx
@@ -12,23 +12,32 @@ export const Route = createFileRoute("/(dashboard)/settings/")({
validateSearch: type({ tab: "string?" }),
errorComponent: () => Failed to load settings
,
loader: async ({ context }) => {
- const authContext = await fetchUser();
- const orgContext = await getOrganizationContext();
+ const [authContext, orgContext] = await Promise.all([
+ fetchUser(),
+ context.queryClient.ensureQueryData({
+ queryKey: ["organization-context"],
+ queryFn: () => getOrganizationContext(),
+ }),
+ ]);
const orgRole = orgContext.activeMember?.role;
+ const shouldPrefetchOrgQueries = authContext.user?.role === "admin" || orgRole === "owner" || orgRole === "admin";
- let org, members;
-
- if (authContext.user?.role === "admin" || orgRole === "owner" || orgRole === "admin") {
- const promises = await Promise.all([
+ if (shouldPrefetchOrgQueries) {
+ const [org, members, appOrigin] = await Promise.all([
context.queryClient.ensureQueryData({ ...getSsoSettingsOptions() }),
context.queryClient.ensureQueryData({ ...getOrgMembersOptions() }),
context.queryClient.ensureQueryData({ queryKey: ["app-origin"], queryFn: () => getOrigin() }),
]);
- org = promises[0];
- members = promises[1];
+
+ return {
+ authContext: authContext as AppContext,
+ org,
+ members,
+ appOrigin,
+ };
}
- return { authContext: authContext as AppContext, org, members };
+ return { authContext: authContext as AppContext };
},
staticData: {
breadcrumb: () => [{ label: "Settings" }],
@@ -36,7 +45,14 @@ export const Route = createFileRoute("/(dashboard)/settings/")({
});
function RouteComponent() {
- const { authContext, org, members } = Route.useLoaderData();
+ const { authContext, members, org, appOrigin } = Route.useLoaderData();
- return ;
+ return (
+
+ );
}
diff --git a/app/routes/__root.tsx b/app/routes/__root.tsx
index 2f001b50..7b56550b 100644
--- a/app/routes/__root.tsx
+++ b/app/routes/__root.tsx
@@ -2,6 +2,7 @@ import { Outlet, HeadContent, Scripts, createRootRouteWithContext } from "@tanst
import appCss from "../app.css?url";
import { apiClientMiddleware } from "~/middleware/api-client";
import type { QueryClient } from "@tanstack/react-query";
+import { ReactQueryDevtools } from "@tanstack/react-query-devtools";
import { Toaster } from "~/client/components/ui/sonner";
import { useServerEvents } from "~/client/hooks/use-server-events";
import { useEffect } from "react";
@@ -58,6 +59,7 @@ function RootLayout() {
+