refactor: breadcrumbs

This commit is contained in:
Nicolas Meienberger 2026-02-08 15:54:05 +01:00
parent 1f1d584eea
commit 5ad47cce1a
37 changed files with 99 additions and 201 deletions

View file

@ -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;

View file

@ -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">

View file

@ -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;

View file

@ -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;

View file

@ -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();

View file

@ -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();

View file

@ -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();

View file

@ -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("");

View file

@ -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();

View file

@ -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("");

View file

@ -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();

View file

@ -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;

View file

@ -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" });

View file

@ -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;
}; };

View file

@ -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();

View file

@ -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,

View file

@ -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("");

View file

@ -1,9 +0,0 @@
import { redirect } from "react-router";
export const clientLoader = async () => {
return redirect("/volumes");
};
export const loader = async () => {
return redirect("/volumes");
};

View file

@ -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,
});

View file

@ -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>
);
}

View file

@ -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;
}
} }

View file

@ -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" },
],
}, },
}); });

View file

@ -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"}` },

View file

@ -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,
}); });

View file

@ -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" },

View file

@ -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}` },

View file

@ -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" },

View file

@ -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" },

View file

@ -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}` },

View file

@ -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: [

View file

@ -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}` },

View file

@ -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" },

View file

@ -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" },

View file

@ -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() {

View file

@ -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}` },

View file

@ -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" },

View file

@ -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" },