Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions src/app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ import {
ZamaIcon,
} from "@/components/icons";
import { DefenderIcon } from "@/components/icons/defender-icon";
import { latestStable as monitorLatestStable } from "../../content/monitor/latest-versions";
import { latestStable as relayerLatestStable } from "../../content/relayer/latest-versions";
import { baseOptions } from "./layout.config";

export default function HomePage() {
Expand Down Expand Up @@ -137,15 +139,15 @@ export default function HomePage() {
{/* Dual Heroes: Monitor and Relayer */}
<div className="grid grid-cols-1 lg:grid-cols-2 gap-4 sm:gap-6 mb-4 sm:mb-6">
<FeatureCard
href="/relayer"
href={`/relayer/${relayerLatestStable}`}
icon={<RelayersIcon className="h-6 w-6 sm:h-8 sm:w-8" />}
title="Relayer"
description="Automate onchain transactions to schedule jobs, batch calls, and relay gasless meta transactions within your self-hosted infrastructure."
glowColor="tools"
/>

<FeatureCard
href="/monitor/1.1.x"
href={`/monitor/${monitorLatestStable}`}
icon={<MonitorIcon className="h-6 w-6 sm:h-8 sm:w-8" />}
title="Monitor"
description="Monitor onchain activity in real time to watch critical events, detect anomalies, trigger alerts on your preferred channels, and set automated responses with Relayer."
Expand Down
2 changes: 1 addition & 1 deletion src/components/sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -236,7 +236,7 @@ export function SidebarFooter(props: ComponentProps<"div">) {

export function SidebarViewport(props: ScrollAreaProps) {
return (
<ScrollArea {...props} className={cn("h-full", props.className)}>
<ScrollArea {...props} className={cn("flex-1 min-h-0", props.className)}>
<ScrollViewport
className="p-4 overscroll-contain"
style={
Expand Down
12 changes: 7 additions & 5 deletions src/components/ui/scroll-area.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ const ScrollArea = React.forwardRef<
>(({ className, children, ...props }, ref) => (
<ScrollAreaPrimitive.Root
ref={ref}
type="scroll"
type="always"
className={cn("overflow-hidden", className)}
{...props}
>
Expand Down Expand Up @@ -43,14 +43,16 @@ const ScrollBar = React.forwardRef<
ref={ref}
orientation={orientation}
className={cn(
"flex select-none data-[state=hidden]:animate-fd-fade-out",
orientation === "vertical" && "h-full w-1.5",
orientation === "horizontal" && "h-1.5 flex-col",
"flex select-none touch-none p-0.5 transition-colors",
orientation === "vertical" &&
"h-full w-2.5 border-l border-l-transparent",
orientation === "horizontal" &&
"h-2.5 flex-col border-t border-t-transparent",
className,
)}
{...props}
>
<ScrollAreaPrimitive.ScrollAreaThumb className="relative flex-1 rounded-full bg-fd-border" />
<ScrollAreaPrimitive.ScrollAreaThumb className="relative flex-1 rounded-full bg-fd-muted-foreground/40 hover:bg-fd-muted-foreground/60 transition-colors" />
</ScrollAreaPrimitive.Scrollbar>
));
ScrollBar.displayName = ScrollAreaPrimitive.Scrollbar.displayName;
Expand Down
87 changes: 86 additions & 1 deletion src/lib/is-active.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,59 @@ function normalize(url: string) {
return url;
}

// Products that have versioned paths like /product/X.Y.x/...
const VERSIONED_PRODUCTS = ["/relayer", "/monitor"];

// Version pattern: matches patterns like "1.3.x", "1.0.x", "2.1.x"
const VERSION_PATTERN = /^(\d+\.\d+\.x)$/;

interface ParsedPath {
base: string;
version: string | null; // null means development/unversioned
subpath: string;
}

/**
* Parses a path for versioned products.
* For "/relayer/1.3.x/quickstart" returns { base: "/relayer", version: "1.3.x", subpath: "/quickstart" }
* For "/relayer/quickstart" (development) returns { base: "/relayer", version: null, subpath: "/quickstart" }
* For "/relayer" (development root) returns { base: "/relayer", version: null, subpath: "" }
* For non-product paths, returns null.
*/
function parseProductPath(path: string): ParsedPath | null {
const normalizedPath = normalize(path);

for (const product of VERSIONED_PRODUCTS) {
if (normalizedPath === product) {
// Exact match to product root (development version)
return { base: product, version: null, subpath: "" };
}

if (normalizedPath.startsWith(`${product}/`)) {
const remainder = normalizedPath.slice(product.length + 1); // +1 for the slash
const segments = remainder.split("/");
const firstSegment = segments[0];

if (firstSegment && VERSION_PATTERN.test(firstSegment)) {
// Versioned path: /relayer/1.3.x or /relayer/1.3.x/quickstart
return {
base: product,
version: firstSegment,
subpath: segments.length > 1 ? `/${segments.slice(1).join("/")}` : "",
};
}
// Development path with subpath: /relayer/quickstart
return {
base: product,
version: null,
subpath: `/${remainder}`,
};
}
}

return null;
}

export function isActive(
url: string,
pathname: string,
Expand All @@ -13,7 +66,39 @@ export function isActive(
url = normalize(url);
pathname = normalize(pathname);

return url === pathname || (nested && pathname.startsWith(`${url}/`));
// Standard exact match or nested match
if (url === pathname || (nested && pathname.startsWith(`${url}/`))) {
return true;
}

// Check for versioned/development path matching
const urlParsed = parseProductPath(url);
const pathnameParsed = parseProductPath(pathname);

if (urlParsed && pathnameParsed) {
// Both are paths for the same product
if (urlParsed.base === pathnameParsed.base) {
// Same subpath means it's the same "page" just different version
if (urlParsed.subpath === pathnameParsed.subpath) {
return true;
}

// Check if pathname is nested under url's subpath
if (
nested &&
pathnameParsed.subpath.startsWith(`${urlParsed.subpath}/`)
) {
return true;
}

// Handle root match: /relayer/1.3.x should match any /relayer/* when nested
if (nested && urlParsed.subpath === "") {
return true;
}
}
}

return false;
}

export function isTabActive(tab: SidebarTab, pathname: string) {
Expand Down