refactor: breadcrumbs
This commit is contained in:
parent
1f1d584eea
commit
5ad47cce1a
37 changed files with 99 additions and 201 deletions
|
|
@ -1,4 +1,4 @@
|
||||||
import { Link, useMatches, type UIMatch } from "react-router";
|
import { useMatches, Link } from "@tanstack/react-router";
|
||||||
import {
|
import {
|
||||||
Breadcrumb,
|
Breadcrumb,
|
||||||
BreadcrumbItem,
|
BreadcrumbItem,
|
||||||
|
|
@ -13,25 +13,22 @@ export interface BreadcrumbItemData {
|
||||||
href?: string;
|
href?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface RouteHandle {
|
type BreadcrumbFunction = (match: ReturnType<typeof useMatches>[number]) => BreadcrumbItemData[] | null;
|
||||||
breadcrumb?: (match: UIMatch) => BreadcrumbItemData[] | null;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function AppBreadcrumb() {
|
export function AppBreadcrumb() {
|
||||||
const matches = useMatches();
|
const matches = useMatches();
|
||||||
|
|
||||||
// Find the last match with a breadcrumb handler
|
|
||||||
const lastMatchWithBreadcrumb = [...matches].reverse().find((match) => {
|
const lastMatchWithBreadcrumb = [...matches].reverse().find((match) => {
|
||||||
const handle = match.handle as RouteHandle | undefined;
|
const breadcrumbFn = match.staticData?.breadcrumb as BreadcrumbFunction | undefined;
|
||||||
return handle?.breadcrumb;
|
return breadcrumbFn;
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!lastMatchWithBreadcrumb) {
|
if (!lastMatchWithBreadcrumb) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const handle = lastMatchWithBreadcrumb.handle as RouteHandle;
|
const breadcrumbFn = lastMatchWithBreadcrumb.staticData?.breadcrumb as BreadcrumbFunction;
|
||||||
const breadcrumbs = handle.breadcrumb?.(lastMatchWithBreadcrumb);
|
const breadcrumbs = breadcrumbFn?.(lastMatchWithBreadcrumb);
|
||||||
|
|
||||||
if (!breadcrumbs || breadcrumbs.length === 0) {
|
if (!breadcrumbs || breadcrumbs.length === 0) {
|
||||||
return null;
|
return null;
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ import { AppSidebar } from "./app-sidebar";
|
||||||
import { authClient } from "../lib/auth-client";
|
import { authClient } from "../lib/auth-client";
|
||||||
import { DevPanelListener } from "./dev-panel-listener";
|
import { DevPanelListener } from "./dev-panel-listener";
|
||||||
import { Outlet, useNavigate } from "@tanstack/react-router";
|
import { Outlet, useNavigate } from "@tanstack/react-router";
|
||||||
|
import { AppBreadcrumb } from "./app-breadcrumb";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
loaderData: AppContext;
|
loaderData: AppContext;
|
||||||
|
|
@ -37,7 +38,7 @@ export function Layout({ loaderData }: Props) {
|
||||||
<div className="flex items-center justify-between py-3 sm:py-4 px-2 sm:px-8 mx-auto container gap-4">
|
<div className="flex items-center justify-between py-3 sm:py-4 px-2 sm:px-8 mx-auto container gap-4">
|
||||||
<div className="flex items-center gap-4 min-w-0">
|
<div className="flex items-center gap-4 min-w-0">
|
||||||
<SidebarTrigger />
|
<SidebarTrigger />
|
||||||
{/* <AppBreadcrumb /> */}
|
<AppBreadcrumb />
|
||||||
</div>
|
</div>
|
||||||
{loaderData.user && (
|
{loaderData.user && (
|
||||||
<div className="flex items-center gap-4">
|
<div className="flex items-center gap-4">
|
||||||
|
|
|
||||||
|
|
@ -40,12 +40,6 @@ import type {
|
||||||
} from "~/client/lib/types";
|
} from "~/client/lib/types";
|
||||||
import { useNavigate } from "@tanstack/react-router";
|
import { useNavigate } from "@tanstack/react-router";
|
||||||
|
|
||||||
// export const handle = {
|
|
||||||
// breadcrumb: (match: Route.MetaArgs) => {
|
|
||||||
// const data = match.loaderData;
|
|
||||||
// return [{ label: "Backups", href: "/backups" }, { label: data.schedule.name }];
|
|
||||||
// },
|
|
||||||
// };
|
|
||||||
type Props = {
|
type Props = {
|
||||||
loaderData: {
|
loaderData: {
|
||||||
schedule: BackupSchedule;
|
schedule: BackupSchedule;
|
||||||
|
|
|
||||||
|
|
@ -23,10 +23,6 @@ import { SortableCard } from "~/client/components/sortable-card";
|
||||||
import { BackupCard } from "../components/backup-card";
|
import { BackupCard } from "../components/backup-card";
|
||||||
import { Link } from "@tanstack/react-router";
|
import { Link } from "@tanstack/react-router";
|
||||||
|
|
||||||
export const handle = {
|
|
||||||
breadcrumb: () => [{ label: "Backups" }],
|
|
||||||
};
|
|
||||||
|
|
||||||
export const clientLoader = async () => {
|
export const clientLoader = async () => {
|
||||||
const jobs = await listBackupSchedules();
|
const jobs = await listBackupSchedules();
|
||||||
if (jobs.data) return jobs.data;
|
if (jobs.data) return jobs.data;
|
||||||
|
|
|
||||||
|
|
@ -16,10 +16,6 @@ import { getCronExpression } from "~/utils/utils";
|
||||||
import { CreateScheduleForm, type BackupScheduleFormValues } from "../components/create-schedule-form";
|
import { CreateScheduleForm, type BackupScheduleFormValues } from "../components/create-schedule-form";
|
||||||
import { Link, useNavigate } from "@tanstack/react-router";
|
import { Link, useNavigate } from "@tanstack/react-router";
|
||||||
|
|
||||||
// export const handle = {
|
|
||||||
// breadcrumb: () => [{ label: "Backups", href: "/backups" }, { label: "Create" }],
|
|
||||||
// };
|
|
||||||
|
|
||||||
export function CreateBackupPage() {
|
export function CreateBackupPage() {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const formId = useId();
|
const formId = useId();
|
||||||
|
|
|
||||||
|
|
@ -10,10 +10,6 @@ import { Alert, AlertDescription } from "~/client/components/ui/alert";
|
||||||
import { CreateNotificationForm, type NotificationFormValues } from "../components/create-notification-form";
|
import { CreateNotificationForm, type NotificationFormValues } from "../components/create-notification-form";
|
||||||
import { useNavigate } from "@tanstack/react-router";
|
import { useNavigate } from "@tanstack/react-router";
|
||||||
|
|
||||||
export const handle = {
|
|
||||||
breadcrumb: () => [{ label: "Notifications", href: "/notifications" }, { label: "Create" }],
|
|
||||||
};
|
|
||||||
|
|
||||||
export function CreateNotificationPage() {
|
export function CreateNotificationPage() {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const formId = useId();
|
const formId = useId();
|
||||||
|
|
|
||||||
|
|
@ -26,13 +26,6 @@ import { Alert, AlertDescription } from "~/client/components/ui/alert";
|
||||||
import { CreateNotificationForm, type NotificationFormValues } from "../components/create-notification-form";
|
import { CreateNotificationForm, type NotificationFormValues } from "../components/create-notification-form";
|
||||||
import { useNavigate } from "@tanstack/react-router";
|
import { useNavigate } from "@tanstack/react-router";
|
||||||
|
|
||||||
// export const handle = {
|
|
||||||
// breadcrumb: (match: Route.MetaArgs) => [
|
|
||||||
// { label: "Notifications", href: "/notifications" },
|
|
||||||
// { label: match.params.id },
|
|
||||||
// ],
|
|
||||||
// };
|
|
||||||
|
|
||||||
export function NotificationDetailsPage({ notificationId }: { notificationId: string }) {
|
export function NotificationDetailsPage({ notificationId }: { notificationId: string }) {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const formId = useId();
|
const formId = useId();
|
||||||
|
|
|
||||||
|
|
@ -11,10 +11,6 @@ import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "~
|
||||||
import { listNotificationDestinationsOptions } from "~/client/api-client/@tanstack/react-query.gen";
|
import { listNotificationDestinationsOptions } from "~/client/api-client/@tanstack/react-query.gen";
|
||||||
import { useNavigate } from "@tanstack/react-router";
|
import { useNavigate } from "@tanstack/react-router";
|
||||||
|
|
||||||
export const handle = {
|
|
||||||
breadcrumb: () => [{ label: "Notifications" }],
|
|
||||||
};
|
|
||||||
|
|
||||||
export function NotificationsPage() {
|
export function NotificationsPage() {
|
||||||
const [searchQuery, setSearchQuery] = useState("");
|
const [searchQuery, setSearchQuery] = useState("");
|
||||||
const [typeFilter, setTypeFilter] = useState("");
|
const [typeFilter, setTypeFilter] = useState("");
|
||||||
|
|
|
||||||
|
|
@ -13,10 +13,6 @@ import { parseError } from "~/client/lib/errors";
|
||||||
import { Alert, AlertDescription } from "~/client/components/ui/alert";
|
import { Alert, AlertDescription } from "~/client/components/ui/alert";
|
||||||
import { useNavigate } from "@tanstack/react-router";
|
import { useNavigate } from "@tanstack/react-router";
|
||||||
|
|
||||||
export const handle = {
|
|
||||||
breadcrumb: () => [{ label: "Repositories", href: "/repositories" }, { label: "Create" }],
|
|
||||||
};
|
|
||||||
|
|
||||||
export function CreateRepositoryPage() {
|
export function CreateRepositoryPage() {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const formId = useId();
|
const formId = useId();
|
||||||
|
|
|
||||||
|
|
@ -12,10 +12,6 @@ import { cn } from "~/client/lib/utils";
|
||||||
import { EmptyState } from "~/client/components/empty-state";
|
import { EmptyState } from "~/client/components/empty-state";
|
||||||
import { useNavigate } from "@tanstack/react-router";
|
import { useNavigate } from "@tanstack/react-router";
|
||||||
|
|
||||||
export const handle = {
|
|
||||||
breadcrumb: () => [{ label: "Repositories" }],
|
|
||||||
};
|
|
||||||
|
|
||||||
export function RepositoriesPage() {
|
export function RepositoriesPage() {
|
||||||
const [searchQuery, setSearchQuery] = useState("");
|
const [searchQuery, setSearchQuery] = useState("");
|
||||||
const [statusFilter, setStatusFilter] = useState("");
|
const [statusFilter, setStatusFilter] = useState("");
|
||||||
|
|
|
||||||
|
|
@ -6,13 +6,6 @@ import { RepositoryInfoTabContent } from "../tabs/info";
|
||||||
import { RepositorySnapshotsTabContent } from "../tabs/snapshots";
|
import { RepositorySnapshotsTabContent } from "../tabs/snapshots";
|
||||||
import { useNavigate, useSearch } from "@tanstack/react-router";
|
import { useNavigate, useSearch } from "@tanstack/react-router";
|
||||||
|
|
||||||
// export const handle = {
|
|
||||||
// breadcrumb: (match: Route.MetaArgs) => [
|
|
||||||
// { label: "Repositories", href: "/repositories" },
|
|
||||||
// { label: match.loaderData?.name || match.params.id },
|
|
||||||
// ],
|
|
||||||
// };
|
|
||||||
|
|
||||||
export default function RepositoryDetailsPage({ repositoryId }: { repositoryId: string }) {
|
export default function RepositoryDetailsPage({ repositoryId }: { repositoryId: string }) {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,6 @@
|
||||||
import { RestoreForm } from "~/client/components/restore-form";
|
import { RestoreForm } from "~/client/components/restore-form";
|
||||||
import type { Repository, Snapshot } from "~/client/lib/types";
|
import type { Repository, Snapshot } from "~/client/lib/types";
|
||||||
|
|
||||||
// export const handle = {
|
|
||||||
// breadcrumb: (match: Route.MetaArgs) => [
|
|
||||||
// { label: "Repositories", href: "/repositories" },
|
|
||||||
// { label: match.loaderData?.repository.name || match.params.id, href: `/repositories/${match.params.id}` },
|
|
||||||
// { label: match.params.snapshotId, href: `/repositories/${match.params.id}/${match.params.snapshotId}` },
|
|
||||||
// { label: "Restore" },
|
|
||||||
// ],
|
|
||||||
// };
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
snapshot: Snapshot;
|
snapshot: Snapshot;
|
||||||
repository: Repository;
|
repository: Repository;
|
||||||
|
|
|
||||||
|
|
@ -12,14 +12,6 @@ import { useState } from "react";
|
||||||
import { Database } from "lucide-react";
|
import { Database } from "lucide-react";
|
||||||
import { Link, useParams } from "@tanstack/react-router";
|
import { Link, useParams } from "@tanstack/react-router";
|
||||||
|
|
||||||
// export const handle = {
|
|
||||||
// breadcrumb: (match: Route.MetaArgs) => [
|
|
||||||
// { label: "Repositories", href: "/repositories" },
|
|
||||||
// { label: match.loaderData?.repository.name || match.params.id, href: `/repositories/${match.params.id}` },
|
|
||||||
// { label: match.params.snapshotId },
|
|
||||||
// ],
|
|
||||||
// };
|
|
||||||
|
|
||||||
export const SnapshotError = () => {
|
export const SnapshotError = () => {
|
||||||
const { repoId } = useParams({ from: "/(dashboard)/repositories/$repoId/$snapshotId" });
|
const { repoId } = useParams({ from: "/(dashboard)/repositories/$repoId/$snapshotId" });
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -28,10 +28,6 @@ import { TwoFactorSection } from "../components/two-factor-section";
|
||||||
import { UserManagement } from "../components/user-management";
|
import { UserManagement } from "../components/user-management";
|
||||||
import { useNavigate, useSearch } from "@tanstack/react-router";
|
import { useNavigate, useSearch } from "@tanstack/react-router";
|
||||||
|
|
||||||
export const handle = {
|
|
||||||
breadcrumb: () => [{ label: "Settings" }],
|
|
||||||
};
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
appContext: AppContext;
|
appContext: AppContext;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -10,10 +10,6 @@ import { parseError } from "~/client/lib/errors";
|
||||||
import { Alert, AlertDescription } from "~/client/components/ui/alert";
|
import { Alert, AlertDescription } from "~/client/components/ui/alert";
|
||||||
import { useNavigate } from "@tanstack/react-router";
|
import { useNavigate } from "@tanstack/react-router";
|
||||||
|
|
||||||
export const handle = {
|
|
||||||
breadcrumb: () => [{ label: "Volumes", href: "/volumes" }, { label: "Create" }],
|
|
||||||
};
|
|
||||||
|
|
||||||
export function CreateVolumePage() {
|
export function CreateVolumePage() {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const formId = useId();
|
const formId = useId();
|
||||||
|
|
|
||||||
|
|
@ -31,10 +31,6 @@ import {
|
||||||
} from "~/client/api-client/@tanstack/react-query.gen";
|
} from "~/client/api-client/@tanstack/react-query.gen";
|
||||||
import { useNavigate } from "@tanstack/react-router";
|
import { useNavigate } from "@tanstack/react-router";
|
||||||
|
|
||||||
export const handle = {
|
|
||||||
breadcrumb: (match: Route.MetaArgs) => [{ label: "Volumes", href: "/volumes" }, { label: match.params.id }],
|
|
||||||
};
|
|
||||||
|
|
||||||
const getVolumeStatusVariant = (status: VolumeStatus): "success" | "neutral" | "error" | "warning" => {
|
const getVolumeStatusVariant = (status: VolumeStatus): "success" | "neutral" | "error" | "warning" => {
|
||||||
const statusMap = {
|
const statusMap = {
|
||||||
mounted: "success" as const,
|
mounted: "success" as const,
|
||||||
|
|
|
||||||
|
|
@ -23,10 +23,6 @@ const getVolumeStatusVariant = (status: VolumeStatus): "success" | "neutral" | "
|
||||||
return statusMap[status];
|
return statusMap[status];
|
||||||
};
|
};
|
||||||
|
|
||||||
export const handle = {
|
|
||||||
breadcrumb: () => [{ label: "Volumes" }],
|
|
||||||
};
|
|
||||||
|
|
||||||
export function VolumesPage() {
|
export function VolumesPage() {
|
||||||
const [searchQuery, setSearchQuery] = useState("");
|
const [searchQuery, setSearchQuery] = useState("");
|
||||||
const [statusFilter, setStatusFilter] = useState("");
|
const [statusFilter, setStatusFilter] = useState("");
|
||||||
|
|
|
||||||
|
|
@ -1,9 +0,0 @@
|
||||||
import { redirect } from "react-router";
|
|
||||||
|
|
||||||
export const clientLoader = async () => {
|
|
||||||
return redirect("/volumes");
|
|
||||||
};
|
|
||||||
|
|
||||||
export const loader = async () => {
|
|
||||||
return redirect("/volumes");
|
|
||||||
};
|
|
||||||
|
|
@ -1,5 +1,3 @@
|
||||||
import { createContext } from "react-router";
|
|
||||||
|
|
||||||
type User = {
|
type User = {
|
||||||
id: string;
|
id: string;
|
||||||
email: string;
|
email: string;
|
||||||
|
|
@ -13,8 +11,3 @@ export type AppContext = {
|
||||||
user: User | null;
|
user: User | null;
|
||||||
hasUsers: boolean;
|
hasUsers: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const appContext = createContext<AppContext>({
|
|
||||||
user: null,
|
|
||||||
hasUsers: false,
|
|
||||||
});
|
|
||||||
|
|
|
||||||
96
app/root.tsx
96
app/root.tsx
|
|
@ -1,96 +0,0 @@
|
||||||
import { MutationCache, QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
|
||||||
import { isRouteErrorResponse, Links, Meta, Outlet, Scripts, ScrollRestoration } from "react-router";
|
|
||||||
|
|
||||||
import type { Route } from "./+types/root";
|
|
||||||
import "./app.css";
|
|
||||||
import { Toaster } from "./client/components/ui/sonner";
|
|
||||||
import { useServerEvents } from "./client/hooks/use-server-events";
|
|
||||||
import { client } from "./client/api-client/client.gen";
|
|
||||||
|
|
||||||
client.setConfig({
|
|
||||||
baseUrl: "/",
|
|
||||||
});
|
|
||||||
|
|
||||||
export const links: Route.LinksFunction = () => [
|
|
||||||
{ rel: "preconnect", href: "https://fonts.googleapis.com" },
|
|
||||||
{
|
|
||||||
rel: "preconnect",
|
|
||||||
href: "https://fonts.gstatic.com",
|
|
||||||
crossOrigin: "anonymous",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
rel: "stylesheet",
|
|
||||||
href: "https://fonts.googleapis.com/css2?family=Google+Sans+Code:ital,wght@0,300..800;1,300..800&display=swap",
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const queryClient = new QueryClient({
|
|
||||||
mutationCache: new MutationCache({
|
|
||||||
onSuccess: () => {
|
|
||||||
void queryClient.invalidateQueries();
|
|
||||||
},
|
|
||||||
onError: (error) => {
|
|
||||||
console.error("Mutation error:", error);
|
|
||||||
void queryClient.invalidateQueries();
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
|
|
||||||
export function Layout({ children }: { children: React.ReactNode }) {
|
|
||||||
return (
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charSet="utf-8" />
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" />
|
|
||||||
<link rel="icon" type="image/png" href="/images/favicon/favicon-96x96.png" sizes="96x96" />
|
|
||||||
<link rel="icon" type="image/svg+xml" href="/images/favicon/favicon.svg" />
|
|
||||||
<link rel="shortcut icon" href="/images/favicon/favicon.ico" />
|
|
||||||
<link rel="apple-touch-icon" sizes="180x180" href="/images/favicon/apple-touch-icon.png" />
|
|
||||||
<meta name="apple-mobile-web-app-title" content="Zerobyte" />
|
|
||||||
<link rel="manifest" href="/images/favicon/site.webmanifest" />
|
|
||||||
<Meta />
|
|
||||||
<Links />
|
|
||||||
</head>
|
|
||||||
<QueryClientProvider client={queryClient}>
|
|
||||||
<body className="dark">
|
|
||||||
{children}
|
|
||||||
<Toaster />
|
|
||||||
<ScrollRestoration />
|
|
||||||
<Scripts />
|
|
||||||
</body>
|
|
||||||
</QueryClientProvider>
|
|
||||||
</html>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function App() {
|
|
||||||
useServerEvents();
|
|
||||||
|
|
||||||
return <Outlet />;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function ErrorBoundary({ error }: Route.ErrorBoundaryProps) {
|
|
||||||
let message = "Oops!";
|
|
||||||
let details = "An unexpected error occurred.";
|
|
||||||
let stack: string | undefined;
|
|
||||||
|
|
||||||
if (isRouteErrorResponse(error)) {
|
|
||||||
message = error.status === 404 ? "404" : "Error";
|
|
||||||
details = error.status === 404 ? "The requested page could not be found." : error.statusText || details;
|
|
||||||
} else if (import.meta.env.DEV && error && error instanceof Error) {
|
|
||||||
details = error.message;
|
|
||||||
stack = error.stack;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<main className="pt-4 p-4 container mx-auto">
|
|
||||||
<h1>{message}</h1>
|
|
||||||
<p>{details}</p>
|
|
||||||
{stack && (
|
|
||||||
<pre className="w-full p-4 overflow-x-auto">
|
|
||||||
<code>{stack}</code>
|
|
||||||
</pre>
|
|
||||||
)}
|
|
||||||
</main>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
@ -3,6 +3,7 @@ import { setupRouterSsrQueryIntegration } from "@tanstack/react-router-ssr-query
|
||||||
import { routeTree } from "./routeTree.gen";
|
import { routeTree } from "./routeTree.gen";
|
||||||
import { MutationCache, QueryClient } from "@tanstack/react-query";
|
import { MutationCache, QueryClient } from "@tanstack/react-query";
|
||||||
import { client } from "./client/api-client/client.gen";
|
import { client } from "./client/api-client/client.gen";
|
||||||
|
import type { BreadcrumbItemData } from "./client/components/app-breadcrumb";
|
||||||
|
|
||||||
client.setConfig({
|
client.setConfig({
|
||||||
baseUrl: "/",
|
baseUrl: "/",
|
||||||
|
|
@ -45,4 +46,7 @@ declare module "@tanstack/react-router" {
|
||||||
interface Register {
|
interface Register {
|
||||||
router: ReturnType<typeof getRouter>;
|
router: ReturnType<typeof getRouter>;
|
||||||
}
|
}
|
||||||
|
interface StaticDataRouteOption {
|
||||||
|
breadcrumb?: (match: any) => BreadcrumbItemData[] | null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,15 @@ export const Route = createFileRoute("/(dashboard)/backups/$backupId/$snapshotId
|
||||||
context.queryClient.ensureQueryData({ ...getRepositoryOptions({ path: { id: schedule.data?.repositoryId } }) }),
|
context.queryClient.ensureQueryData({ ...getRepositoryOptions({ path: { id: schedule.data?.repositoryId } }) }),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return { snapshot, repository };
|
return { snapshot, repository, schedule: schedule.data };
|
||||||
|
},
|
||||||
|
staticData: {
|
||||||
|
breadcrumb: (match) => [
|
||||||
|
{ label: "Backup Jobs", href: "/backups" },
|
||||||
|
{ label: match.loaderData?.schedule?.name || "Job", href: `/backups/${match.params.backupId}` },
|
||||||
|
{ label: match.params.snapshotId },
|
||||||
|
{ label: "Restore" },
|
||||||
|
],
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,12 @@ export const Route = createFileRoute("/(dashboard)/backups/$scheduleId")({
|
||||||
|
|
||||||
return { schedule, notifs, repos, scheduleNotifs, mirrors };
|
return { schedule, notifs, repos, scheduleNotifs, mirrors };
|
||||||
},
|
},
|
||||||
|
staticData: {
|
||||||
|
breadcrumb: (match) => [
|
||||||
|
{ label: "Backup Jobs", href: "/backups" },
|
||||||
|
{ label: match.loaderData?.schedule.name || "Job Details" },
|
||||||
|
],
|
||||||
|
},
|
||||||
head: ({ loaderData }) => ({
|
head: ({ loaderData }) => ({
|
||||||
meta: [
|
meta: [
|
||||||
{ title: `Zerobyte - ${loaderData?.schedule.name || "Backup Job Details"}` },
|
{ title: `Zerobyte - ${loaderData?.schedule.name || "Backup Job Details"}` },
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,12 @@ export const Route = createFileRoute("/(dashboard)/backups/create")({
|
||||||
context.queryClient.ensureQueryData({ ...listRepositoriesOptions() }),
|
context.queryClient.ensureQueryData({ ...listRepositoriesOptions() }),
|
||||||
]);
|
]);
|
||||||
},
|
},
|
||||||
|
staticData: {
|
||||||
|
breadcrumb: () => [
|
||||||
|
{ label: "Backup Jobs", href: "/backups" },
|
||||||
|
{ label: "Create" },
|
||||||
|
],
|
||||||
|
},
|
||||||
component: RouteComponent,
|
component: RouteComponent,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,9 @@ export const Route = createFileRoute("/(dashboard)/backups/")({
|
||||||
...listBackupSchedulesOptions(),
|
...listBackupSchedulesOptions(),
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
staticData: {
|
||||||
|
breadcrumb: () => [{ label: "Backup Jobs" }],
|
||||||
|
},
|
||||||
head: () => ({
|
head: () => ({
|
||||||
meta: [
|
meta: [
|
||||||
{ title: "Zerobyte - Backup Jobs" },
|
{ title: "Zerobyte - Backup Jobs" },
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,12 @@ export const Route = createFileRoute("/(dashboard)/notifications/$notificationId
|
||||||
|
|
||||||
return res;
|
return res;
|
||||||
},
|
},
|
||||||
|
staticData: {
|
||||||
|
breadcrumb: (match) => [
|
||||||
|
{ label: "Notifications", href: "/notifications" },
|
||||||
|
{ label: match.loaderData?.name || "Notification Details" },
|
||||||
|
],
|
||||||
|
},
|
||||||
head: ({ loaderData }) => ({
|
head: ({ loaderData }) => ({
|
||||||
meta: [
|
meta: [
|
||||||
{ title: `Zerobyte - ${loaderData?.name}` },
|
{ title: `Zerobyte - ${loaderData?.name}` },
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,12 @@ import { CreateNotificationPage } from "~/client/modules/notifications/routes/cr
|
||||||
|
|
||||||
export const Route = createFileRoute("/(dashboard)/notifications/create")({
|
export const Route = createFileRoute("/(dashboard)/notifications/create")({
|
||||||
component: RouteComponent,
|
component: RouteComponent,
|
||||||
|
staticData: {
|
||||||
|
breadcrumb: () => [
|
||||||
|
{ label: "Notifications", href: "/notifications" },
|
||||||
|
{ label: "Create" },
|
||||||
|
],
|
||||||
|
},
|
||||||
head: () => ({
|
head: () => ({
|
||||||
meta: [
|
meta: [
|
||||||
{ title: "Zerobyte - Create Notification" },
|
{ title: "Zerobyte - Create Notification" },
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,9 @@ export const Route = createFileRoute("/(dashboard)/notifications/")({
|
||||||
loader: async ({ context }) => {
|
loader: async ({ context }) => {
|
||||||
await context.queryClient.ensureQueryData({ ...listNotificationDestinationsOptions() });
|
await context.queryClient.ensureQueryData({ ...listNotificationDestinationsOptions() });
|
||||||
},
|
},
|
||||||
|
staticData: {
|
||||||
|
breadcrumb: () => [{ label: "Notifications" }],
|
||||||
|
},
|
||||||
head: () => ({
|
head: () => ({
|
||||||
meta: [
|
meta: [
|
||||||
{ title: "Zerobyte - Notifications" },
|
{ title: "Zerobyte - Notifications" },
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,14 @@ export const Route = createFileRoute("/(dashboard)/repositories/$repoId/$snapId/
|
||||||
|
|
||||||
return { snapshot, repository };
|
return { snapshot, repository };
|
||||||
},
|
},
|
||||||
|
staticData: {
|
||||||
|
breadcrumb: (match) => [
|
||||||
|
{ label: "Repositories", href: "/repositories" },
|
||||||
|
{ label: match.loaderData?.repository?.name || "Repository", href: `/repositories/${match.params.repoId}` },
|
||||||
|
{ label: match.params.snapId, href: `/repositories/${match.params.repoId}/${match.params.snapId}` },
|
||||||
|
{ label: "Restore" },
|
||||||
|
],
|
||||||
|
},
|
||||||
head: ({ params }) => ({
|
head: ({ params }) => ({
|
||||||
meta: [
|
meta: [
|
||||||
{ title: `Zerobyte - Restore Snapshot ${params.snapId}` },
|
{ title: `Zerobyte - Restore Snapshot ${params.snapId}` },
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ export const Route = createFileRoute("/(dashboard)/repositories/$repoId/$snapsho
|
||||||
component: RouteComponent,
|
component: RouteComponent,
|
||||||
errorComponent: (e) => <div>{e.error.message}</div>,
|
errorComponent: (e) => <div>{e.error.message}</div>,
|
||||||
loader: async ({ params, context }) => {
|
loader: async ({ params, context }) => {
|
||||||
await context.queryClient.ensureQueryData({
|
const res = await context.queryClient.ensureQueryData({
|
||||||
...getRepositoryOptions({ path: { id: params.repoId } }),
|
...getRepositoryOptions({ path: { id: params.repoId } }),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -25,6 +25,15 @@ export const Route = createFileRoute("/(dashboard)/repositories/$repoId/$snapsho
|
||||||
query: { path: "/" },
|
query: { path: "/" },
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
return res;
|
||||||
|
},
|
||||||
|
staticData: {
|
||||||
|
breadcrumb: (match) => [
|
||||||
|
{ label: "Repositories", href: "/repositories" },
|
||||||
|
{ label: match.loaderData?.name || "Repository", href: `/repositories/${match.params.repoId}` },
|
||||||
|
{ label: match.params.snapshotId },
|
||||||
|
],
|
||||||
},
|
},
|
||||||
head: ({ params }) => ({
|
head: ({ params }) => ({
|
||||||
meta: [
|
meta: [
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,12 @@ export const Route = createFileRoute("/(dashboard)/repositories/$repositoryId")(
|
||||||
return res;
|
return res;
|
||||||
},
|
},
|
||||||
validateSearch: type({ tab: "string?" }),
|
validateSearch: type({ tab: "string?" }),
|
||||||
|
staticData: {
|
||||||
|
breadcrumb: (match) => [
|
||||||
|
{ label: "Repositories", href: "/repositories" },
|
||||||
|
{ label: match.loaderData?.name || "Repository Details" },
|
||||||
|
],
|
||||||
|
},
|
||||||
head: ({ loaderData }) => ({
|
head: ({ loaderData }) => ({
|
||||||
meta: [
|
meta: [
|
||||||
{ title: `Zerobyte - ${loaderData?.name}` },
|
{ title: `Zerobyte - ${loaderData?.name}` },
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,12 @@ import { CreateRepositoryPage } from "~/client/modules/repositories/routes/creat
|
||||||
|
|
||||||
export const Route = createFileRoute("/(dashboard)/repositories/create")({
|
export const Route = createFileRoute("/(dashboard)/repositories/create")({
|
||||||
component: RouteComponent,
|
component: RouteComponent,
|
||||||
|
staticData: {
|
||||||
|
breadcrumb: () => [
|
||||||
|
{ label: "Repositories", href: "/repositories" },
|
||||||
|
{ label: "Create" },
|
||||||
|
],
|
||||||
|
},
|
||||||
head: () => ({
|
head: () => ({
|
||||||
meta: [
|
meta: [
|
||||||
{ title: "Zerobyte - Create Repository" },
|
{ title: "Zerobyte - Create Repository" },
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,9 @@ export const Route = createFileRoute("/(dashboard)/repositories/")({
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
errorComponent: (e) => <div>{e.error.message}</div>,
|
errorComponent: (e) => <div>{e.error.message}</div>,
|
||||||
|
staticData: {
|
||||||
|
breadcrumb: () => [{ label: "Repositories" }],
|
||||||
|
},
|
||||||
head: () => ({
|
head: () => ({
|
||||||
meta: [
|
meta: [
|
||||||
{ title: "Zerobyte - Repositories" },
|
{ title: "Zerobyte - Repositories" },
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,9 @@ export const Route = createFileRoute("/(dashboard)/settings/")({
|
||||||
const authContext = await fetchUser();
|
const authContext = await fetchUser();
|
||||||
return authContext as AppContext;
|
return authContext as AppContext;
|
||||||
},
|
},
|
||||||
|
staticData: {
|
||||||
|
breadcrumb: () => [{ label: "Settings" }],
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
function RouteComponent() {
|
function RouteComponent() {
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,12 @@ export const Route = createFileRoute("/(dashboard)/volumes/$volumeId")({
|
||||||
return res;
|
return res;
|
||||||
},
|
},
|
||||||
validateSearch: type({ tab: "string?" }),
|
validateSearch: type({ tab: "string?" }),
|
||||||
|
staticData: {
|
||||||
|
breadcrumb: (match) => [
|
||||||
|
{ label: "Volumes", href: "/volumes" },
|
||||||
|
{ label: match.loaderData?.volume.name || "Volume Details" },
|
||||||
|
],
|
||||||
|
},
|
||||||
head: ({ loaderData }) => ({
|
head: ({ loaderData }) => ({
|
||||||
meta: [
|
meta: [
|
||||||
{ title: `Zerobyte - ${loaderData?.volume.name}` },
|
{ title: `Zerobyte - ${loaderData?.volume.name}` },
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,9 @@ import { CreateVolumePage } from "~/client/modules/volumes/routes/create-volume"
|
||||||
|
|
||||||
export const Route = createFileRoute("/(dashboard)/volumes/create")({
|
export const Route = createFileRoute("/(dashboard)/volumes/create")({
|
||||||
component: RouteComponent,
|
component: RouteComponent,
|
||||||
|
staticData: {
|
||||||
|
breadcrumb: () => [{ label: "Volumes", href: "/volumes" }, { label: "Create" }],
|
||||||
|
},
|
||||||
head: () => ({
|
head: () => ({
|
||||||
meta: [
|
meta: [
|
||||||
{ title: "Zerobyte - Create Volume" },
|
{ title: "Zerobyte - Create Volume" },
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,9 @@ export const Route = createFileRoute("/(dashboard)/volumes/")({
|
||||||
loader: async ({ context }) => {
|
loader: async ({ context }) => {
|
||||||
await context.queryClient.ensureQueryData(listVolumesOptions());
|
await context.queryClient.ensureQueryData(listVolumesOptions());
|
||||||
},
|
},
|
||||||
|
staticData: {
|
||||||
|
breadcrumb: () => [{ label: "Volumes" }],
|
||||||
|
},
|
||||||
head: () => ({
|
head: () => ({
|
||||||
meta: [
|
meta: [
|
||||||
{ title: "Zerobyte - Volumes" },
|
{ title: "Zerobyte - Volumes" },
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue