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 {
|
||||
Breadcrumb,
|
||||
BreadcrumbItem,
|
||||
|
|
@ -13,25 +13,22 @@ export interface BreadcrumbItemData {
|
|||
href?: string;
|
||||
}
|
||||
|
||||
interface RouteHandle {
|
||||
breadcrumb?: (match: UIMatch) => BreadcrumbItemData[] | null;
|
||||
}
|
||||
type BreadcrumbFunction = (match: ReturnType<typeof useMatches>[number]) => BreadcrumbItemData[] | null;
|
||||
|
||||
export function AppBreadcrumb() {
|
||||
const matches = useMatches();
|
||||
|
||||
// Find the last match with a breadcrumb handler
|
||||
const lastMatchWithBreadcrumb = [...matches].reverse().find((match) => {
|
||||
const handle = match.handle as RouteHandle | undefined;
|
||||
return handle?.breadcrumb;
|
||||
const breadcrumbFn = match.staticData?.breadcrumb as BreadcrumbFunction | undefined;
|
||||
return breadcrumbFn;
|
||||
});
|
||||
|
||||
if (!lastMatchWithBreadcrumb) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const handle = lastMatchWithBreadcrumb.handle as RouteHandle;
|
||||
const breadcrumbs = handle.breadcrumb?.(lastMatchWithBreadcrumb);
|
||||
const breadcrumbFn = lastMatchWithBreadcrumb.staticData?.breadcrumb as BreadcrumbFunction;
|
||||
const breadcrumbs = breadcrumbFn?.(lastMatchWithBreadcrumb);
|
||||
|
||||
if (!breadcrumbs || breadcrumbs.length === 0) {
|
||||
return null;
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import { AppSidebar } from "./app-sidebar";
|
|||
import { authClient } from "../lib/auth-client";
|
||||
import { DevPanelListener } from "./dev-panel-listener";
|
||||
import { Outlet, useNavigate } from "@tanstack/react-router";
|
||||
import { AppBreadcrumb } from "./app-breadcrumb";
|
||||
|
||||
type Props = {
|
||||
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 gap-4 min-w-0">
|
||||
<SidebarTrigger />
|
||||
{/* <AppBreadcrumb /> */}
|
||||
<AppBreadcrumb />
|
||||
</div>
|
||||
{loaderData.user && (
|
||||
<div className="flex items-center gap-4">
|
||||
|
|
|
|||
|
|
@ -40,12 +40,6 @@ import type {
|
|||
} from "~/client/lib/types";
|
||||
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 = {
|
||||
loaderData: {
|
||||
schedule: BackupSchedule;
|
||||
|
|
|
|||
|
|
@ -23,10 +23,6 @@ import { SortableCard } from "~/client/components/sortable-card";
|
|||
import { BackupCard } from "../components/backup-card";
|
||||
import { Link } from "@tanstack/react-router";
|
||||
|
||||
export const handle = {
|
||||
breadcrumb: () => [{ label: "Backups" }],
|
||||
};
|
||||
|
||||
export const clientLoader = async () => {
|
||||
const jobs = await listBackupSchedules();
|
||||
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 { Link, useNavigate } from "@tanstack/react-router";
|
||||
|
||||
// export const handle = {
|
||||
// breadcrumb: () => [{ label: "Backups", href: "/backups" }, { label: "Create" }],
|
||||
// };
|
||||
|
||||
export function CreateBackupPage() {
|
||||
const navigate = useNavigate();
|
||||
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 { useNavigate } from "@tanstack/react-router";
|
||||
|
||||
export const handle = {
|
||||
breadcrumb: () => [{ label: "Notifications", href: "/notifications" }, { label: "Create" }],
|
||||
};
|
||||
|
||||
export function CreateNotificationPage() {
|
||||
const navigate = useNavigate();
|
||||
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 { 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 }) {
|
||||
const navigate = useNavigate();
|
||||
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 { useNavigate } from "@tanstack/react-router";
|
||||
|
||||
export const handle = {
|
||||
breadcrumb: () => [{ label: "Notifications" }],
|
||||
};
|
||||
|
||||
export function NotificationsPage() {
|
||||
const [searchQuery, setSearchQuery] = useState("");
|
||||
const [typeFilter, setTypeFilter] = useState("");
|
||||
|
|
|
|||
|
|
@ -13,10 +13,6 @@ import { parseError } from "~/client/lib/errors";
|
|||
import { Alert, AlertDescription } from "~/client/components/ui/alert";
|
||||
import { useNavigate } from "@tanstack/react-router";
|
||||
|
||||
export const handle = {
|
||||
breadcrumb: () => [{ label: "Repositories", href: "/repositories" }, { label: "Create" }],
|
||||
};
|
||||
|
||||
export function CreateRepositoryPage() {
|
||||
const navigate = useNavigate();
|
||||
const formId = useId();
|
||||
|
|
|
|||
|
|
@ -12,10 +12,6 @@ import { cn } from "~/client/lib/utils";
|
|||
import { EmptyState } from "~/client/components/empty-state";
|
||||
import { useNavigate } from "@tanstack/react-router";
|
||||
|
||||
export const handle = {
|
||||
breadcrumb: () => [{ label: "Repositories" }],
|
||||
};
|
||||
|
||||
export function RepositoriesPage() {
|
||||
const [searchQuery, setSearchQuery] = useState("");
|
||||
const [statusFilter, setStatusFilter] = useState("");
|
||||
|
|
|
|||
|
|
@ -6,13 +6,6 @@ import { RepositoryInfoTabContent } from "../tabs/info";
|
|||
import { RepositorySnapshotsTabContent } from "../tabs/snapshots";
|
||||
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 }) {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
|
|
|
|||
|
|
@ -1,15 +1,6 @@
|
|||
import { RestoreForm } from "~/client/components/restore-form";
|
||||
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 = {
|
||||
snapshot: Snapshot;
|
||||
repository: Repository;
|
||||
|
|
|
|||
|
|
@ -12,14 +12,6 @@ import { useState } from "react";
|
|||
import { Database } from "lucide-react";
|
||||
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 = () => {
|
||||
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 { useNavigate, useSearch } from "@tanstack/react-router";
|
||||
|
||||
export const handle = {
|
||||
breadcrumb: () => [{ label: "Settings" }],
|
||||
};
|
||||
|
||||
type Props = {
|
||||
appContext: AppContext;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -10,10 +10,6 @@ import { parseError } from "~/client/lib/errors";
|
|||
import { Alert, AlertDescription } from "~/client/components/ui/alert";
|
||||
import { useNavigate } from "@tanstack/react-router";
|
||||
|
||||
export const handle = {
|
||||
breadcrumb: () => [{ label: "Volumes", href: "/volumes" }, { label: "Create" }],
|
||||
};
|
||||
|
||||
export function CreateVolumePage() {
|
||||
const navigate = useNavigate();
|
||||
const formId = useId();
|
||||
|
|
|
|||
|
|
@ -31,10 +31,6 @@ import {
|
|||
} from "~/client/api-client/@tanstack/react-query.gen";
|
||||
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 statusMap = {
|
||||
mounted: "success" as const,
|
||||
|
|
|
|||
|
|
@ -23,10 +23,6 @@ const getVolumeStatusVariant = (status: VolumeStatus): "success" | "neutral" | "
|
|||
return statusMap[status];
|
||||
};
|
||||
|
||||
export const handle = {
|
||||
breadcrumb: () => [{ label: "Volumes" }],
|
||||
};
|
||||
|
||||
export function VolumesPage() {
|
||||
const [searchQuery, setSearchQuery] = 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 = {
|
||||
id: string;
|
||||
email: string;
|
||||
|
|
@ -13,8 +11,3 @@ export type AppContext = {
|
|||
user: User | null;
|
||||
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 { MutationCache, QueryClient } from "@tanstack/react-query";
|
||||
import { client } from "./client/api-client/client.gen";
|
||||
import type { BreadcrumbItemData } from "./client/components/app-breadcrumb";
|
||||
|
||||
client.setConfig({
|
||||
baseUrl: "/",
|
||||
|
|
@ -45,4 +46,7 @@ declare module "@tanstack/react-router" {
|
|||
interface Register {
|
||||
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 } }) }),
|
||||
]);
|
||||
|
||||
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 };
|
||||
},
|
||||
staticData: {
|
||||
breadcrumb: (match) => [
|
||||
{ label: "Backup Jobs", href: "/backups" },
|
||||
{ label: match.loaderData?.schedule.name || "Job Details" },
|
||||
],
|
||||
},
|
||||
head: ({ loaderData }) => ({
|
||||
meta: [
|
||||
{ title: `Zerobyte - ${loaderData?.schedule.name || "Backup Job Details"}` },
|
||||
|
|
|
|||
|
|
@ -9,6 +9,12 @@ export const Route = createFileRoute("/(dashboard)/backups/create")({
|
|||
context.queryClient.ensureQueryData({ ...listRepositoriesOptions() }),
|
||||
]);
|
||||
},
|
||||
staticData: {
|
||||
breadcrumb: () => [
|
||||
{ label: "Backup Jobs", href: "/backups" },
|
||||
{ label: "Create" },
|
||||
],
|
||||
},
|
||||
component: RouteComponent,
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -9,6 +9,9 @@ export const Route = createFileRoute("/(dashboard)/backups/")({
|
|||
...listBackupSchedulesOptions(),
|
||||
});
|
||||
},
|
||||
staticData: {
|
||||
breadcrumb: () => [{ label: "Backup Jobs" }],
|
||||
},
|
||||
head: () => ({
|
||||
meta: [
|
||||
{ title: "Zerobyte - Backup Jobs" },
|
||||
|
|
|
|||
|
|
@ -11,6 +11,12 @@ export const Route = createFileRoute("/(dashboard)/notifications/$notificationId
|
|||
|
||||
return res;
|
||||
},
|
||||
staticData: {
|
||||
breadcrumb: (match) => [
|
||||
{ label: "Notifications", href: "/notifications" },
|
||||
{ label: match.loaderData?.name || "Notification Details" },
|
||||
],
|
||||
},
|
||||
head: ({ loaderData }) => ({
|
||||
meta: [
|
||||
{ title: `Zerobyte - ${loaderData?.name}` },
|
||||
|
|
|
|||
|
|
@ -3,6 +3,12 @@ import { CreateNotificationPage } from "~/client/modules/notifications/routes/cr
|
|||
|
||||
export const Route = createFileRoute("/(dashboard)/notifications/create")({
|
||||
component: RouteComponent,
|
||||
staticData: {
|
||||
breadcrumb: () => [
|
||||
{ label: "Notifications", href: "/notifications" },
|
||||
{ label: "Create" },
|
||||
],
|
||||
},
|
||||
head: () => ({
|
||||
meta: [
|
||||
{ title: "Zerobyte - Create Notification" },
|
||||
|
|
|
|||
|
|
@ -7,6 +7,9 @@ export const Route = createFileRoute("/(dashboard)/notifications/")({
|
|||
loader: async ({ context }) => {
|
||||
await context.queryClient.ensureQueryData({ ...listNotificationDestinationsOptions() });
|
||||
},
|
||||
staticData: {
|
||||
breadcrumb: () => [{ label: "Notifications" }],
|
||||
},
|
||||
head: () => ({
|
||||
meta: [
|
||||
{ title: "Zerobyte - Notifications" },
|
||||
|
|
|
|||
|
|
@ -15,6 +15,14 @@ export const Route = createFileRoute("/(dashboard)/repositories/$repoId/$snapId/
|
|||
|
||||
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 }) => ({
|
||||
meta: [
|
||||
{ title: `Zerobyte - Restore Snapshot ${params.snapId}` },
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ export const Route = createFileRoute("/(dashboard)/repositories/$repoId/$snapsho
|
|||
component: RouteComponent,
|
||||
errorComponent: (e) => <div>{e.error.message}</div>,
|
||||
loader: async ({ params, context }) => {
|
||||
await context.queryClient.ensureQueryData({
|
||||
const res = await context.queryClient.ensureQueryData({
|
||||
...getRepositoryOptions({ path: { id: params.repoId } }),
|
||||
});
|
||||
|
||||
|
|
@ -25,6 +25,15 @@ export const Route = createFileRoute("/(dashboard)/repositories/$repoId/$snapsho
|
|||
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 }) => ({
|
||||
meta: [
|
||||
|
|
|
|||
|
|
@ -25,6 +25,12 @@ export const Route = createFileRoute("/(dashboard)/repositories/$repositoryId")(
|
|||
return res;
|
||||
},
|
||||
validateSearch: type({ tab: "string?" }),
|
||||
staticData: {
|
||||
breadcrumb: (match) => [
|
||||
{ label: "Repositories", href: "/repositories" },
|
||||
{ label: match.loaderData?.name || "Repository Details" },
|
||||
],
|
||||
},
|
||||
head: ({ loaderData }) => ({
|
||||
meta: [
|
||||
{ title: `Zerobyte - ${loaderData?.name}` },
|
||||
|
|
|
|||
|
|
@ -3,6 +3,12 @@ import { CreateRepositoryPage } from "~/client/modules/repositories/routes/creat
|
|||
|
||||
export const Route = createFileRoute("/(dashboard)/repositories/create")({
|
||||
component: RouteComponent,
|
||||
staticData: {
|
||||
breadcrumb: () => [
|
||||
{ label: "Repositories", href: "/repositories" },
|
||||
{ label: "Create" },
|
||||
],
|
||||
},
|
||||
head: () => ({
|
||||
meta: [
|
||||
{ title: "Zerobyte - Create Repository" },
|
||||
|
|
|
|||
|
|
@ -10,6 +10,9 @@ export const Route = createFileRoute("/(dashboard)/repositories/")({
|
|||
});
|
||||
},
|
||||
errorComponent: (e) => <div>{e.error.message}</div>,
|
||||
staticData: {
|
||||
breadcrumb: () => [{ label: "Repositories" }],
|
||||
},
|
||||
head: () => ({
|
||||
meta: [
|
||||
{ title: "Zerobyte - Repositories" },
|
||||
|
|
|
|||
|
|
@ -11,6 +11,9 @@ export const Route = createFileRoute("/(dashboard)/settings/")({
|
|||
const authContext = await fetchUser();
|
||||
return authContext as AppContext;
|
||||
},
|
||||
staticData: {
|
||||
breadcrumb: () => [{ label: "Settings" }],
|
||||
},
|
||||
});
|
||||
|
||||
function RouteComponent() {
|
||||
|
|
|
|||
|
|
@ -14,6 +14,12 @@ export const Route = createFileRoute("/(dashboard)/volumes/$volumeId")({
|
|||
return res;
|
||||
},
|
||||
validateSearch: type({ tab: "string?" }),
|
||||
staticData: {
|
||||
breadcrumb: (match) => [
|
||||
{ label: "Volumes", href: "/volumes" },
|
||||
{ label: match.loaderData?.volume.name || "Volume Details" },
|
||||
],
|
||||
},
|
||||
head: ({ loaderData }) => ({
|
||||
meta: [
|
||||
{ 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")({
|
||||
component: RouteComponent,
|
||||
staticData: {
|
||||
breadcrumb: () => [{ label: "Volumes", href: "/volumes" }, { label: "Create" }],
|
||||
},
|
||||
head: () => ({
|
||||
meta: [
|
||||
{ title: "Zerobyte - Create Volume" },
|
||||
|
|
|
|||
|
|
@ -8,6 +8,9 @@ export const Route = createFileRoute("/(dashboard)/volumes/")({
|
|||
loader: async ({ context }) => {
|
||||
await context.queryClient.ensureQueryData(listVolumesOptions());
|
||||
},
|
||||
staticData: {
|
||||
breadcrumb: () => [{ label: "Volumes" }],
|
||||
},
|
||||
head: () => ({
|
||||
meta: [
|
||||
{ title: "Zerobyte - Volumes" },
|
||||
|
|
|
|||
Loading…
Reference in a new issue