Skip to content

Commit 5dadd00

Browse files
committed
Polish Studio and Builder navbar, layout, and file tracking
- Navbar: use pathname-based detection for tool pages, reduce padding to px-3 for edge-to-edge alignment, show "Relaycode Studio" and "Relaycode Builder" branding with grey sub-label - Navbar center: show active file name on Studio, "Extrinsic Builder" title on Builder (replaces redundant page headings) - Remove Builder page header and card header (moved to navbar center) - Output panel: add bg-muted/50 and border-t for light mode contrast - File explorer: add "+" button in FILES header - Editor tabs: per-file dirty dot via compiledSources snapshot instead of workspace-level isDirtySinceCompile on all tabs - Add page titles and meta descriptions for Studio and Builder - Move providers from Studio page to layout for navbar context access
1 parent d525f21 commit 5dadd00

14 files changed

Lines changed: 142 additions & 56 deletions

File tree

app/builder/layout.tsx

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,34 @@
1+
import type { Metadata } from "next";
12
import { NavBar } from "@/components/layout/site-header";
23
import { SiteFooter } from "@/components/layout/site-footer";
4+
import { Blocks } from "lucide-react";
5+
6+
export const metadata: Metadata = {
7+
title: "Relaycode Builder — Polkadot Extrinsic Builder",
8+
description:
9+
"Build, encode, decode, and submit any Substrate extrinsic visually. Supports all pallets across the Polkadot ecosystem.",
10+
};
311

412
interface BuilderLayoutProps {
513
children: React.ReactNode;
614
}
715

16+
function BuilderNavCenter() {
17+
return (
18+
<div className="flex items-center gap-2 text-sm">
19+
<Blocks className="h-4 w-4 text-muted-foreground" />
20+
<span className="font-medium text-foreground">Extrinsic Builder</span>
21+
<span className="text-muted-foreground hidden sm:inline">
22+
Build and analyze extrinsics for Polkadot
23+
</span>
24+
</div>
25+
);
26+
}
27+
828
export default function BuilderLayout({ children }: BuilderLayoutProps) {
929
return (
1030
<div className="flex min-h-screen flex-col">
11-
<NavBar />
31+
<NavBar centerElement={<BuilderNavCenter />} />
1232
<main className="flex-1">{children}</main>
1333
<SiteFooter />
1434
</div>

app/builder/page.tsx

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -80,13 +80,7 @@ function BuilderContent() {
8080

8181
const BuilderPage: React.FC = () => {
8282
return (
83-
<div className="container mx-auto px-4 py-8 min-h-[calc(100vh-4rem)]">
84-
<header className="text-center mb-12">
85-
<h1 className="text-4xl font-bold mb-2">Extrinsic Builder</h1>
86-
<p className="text-xl text-gray-600">
87-
Extrinsics builder for the Polkadot ecosystem
88-
</p>
89-
</header>
83+
<div className="container mx-auto px-4 py-6 min-h-[calc(100vh-4rem)]">
9084
<ClientProvider>
9185
<BuilderContent />
9286
</ClientProvider>

app/studio/layout.tsx

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,29 @@
1+
"use client";
2+
3+
import React from "react";
14
import { NavBar } from "@/components/layout/site-header";
5+
import { ClientProvider } from "@/context/client";
6+
import { ContractProvider } from "@/context/contract-provider";
7+
import { StudioProvider } from "@/context/studio-provider";
8+
import { StudioNavCenter } from "@/components/studio/studio-nav-center";
29

310
interface StudioLayoutProps {
411
children: React.ReactNode;
512
}
613

714
export default function StudioLayout({ children }: StudioLayoutProps) {
815
return (
9-
<div className="flex h-screen flex-col overflow-hidden">
10-
<NavBar />
11-
<main className="flex flex-col flex-1 min-h-0">{children}</main>
12-
</div>
16+
<ClientProvider>
17+
<ContractProvider>
18+
<StudioProvider>
19+
<div className="flex h-screen flex-col overflow-hidden">
20+
<title>Relaycode Studio — Polkadot Smart Contract IDE</title>
21+
<meta name="description" content="Browser-based smart contract IDE for Polkadot Hub. Write Solidity, compile to EVM or PVM, and deploy with native Polkadot wallets." />
22+
<NavBar centerElement={<StudioNavCenter />} />
23+
<main className="flex flex-col flex-1 min-h-0">{children}</main>
24+
</div>
25+
</StudioProvider>
26+
</ContractProvider>
27+
</ClientProvider>
1328
);
1429
}

app/studio/page.tsx

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,12 @@
11
"use client";
22

33
import React from "react";
4-
import { ClientProvider } from "@/context/client";
5-
import { ContractProvider } from "@/context/contract-provider";
6-
import { StudioProvider } from "@/context/studio-provider";
74
import { StudioLayout } from "@/components/studio/studio-layout";
85

96
export default function StudioPage() {
107
return (
118
<div className="flex flex-1 min-h-0 flex-col">
12-
<ClientProvider>
13-
<ContractProvider>
14-
<StudioProvider>
15-
<StudioLayout />
16-
</StudioProvider>
17-
</ContractProvider>
18-
</ClientProvider>
9+
<StudioLayout />
1910
</div>
2011
);
2112
}

components/builder/extrinsic-builder.tsx

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import {
1515
FormLabel,
1616
FormDescription,
1717
} from "@/components/ui/form";
18-
import { Card, CardContent, CardHeader } from "@/components/ui/card";
18+
import { Card, CardContent } from "@/components/ui/card";
1919
import { GenericTxCall } from "dedot/types";
2020
import { stringCamelCase } from "dedot/utils";
2121
import { assert } from "dedot/utils";
@@ -239,15 +239,7 @@ const ExtrinsicBuilder: React.FC<ExtrinsicBuilderProps> = ({
239239

240240
return (
241241
<Card className="w-full">
242-
<CardHeader>
243-
<div>
244-
<h2 className="text-2xl font-bold">Extrinsic Builder</h2>
245-
<p className="text-sm text-gray-500">
246-
Build and analyze extrinsics for Polkadot
247-
</p>
248-
</div>
249-
</CardHeader>
250-
<CardContent>
242+
<CardContent className="pt-6">
251243
<Form {...builderForm}>
252244
<form
253245
onSubmit={builderForm.handleSubmit(onSubmit)}

components/layout/main-nav.tsx

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
"use client";
22

33
import Link from "next/link";
4-
import { useSelectedLayoutSegment } from "next/navigation";
4+
import { useSelectedLayoutSegment, usePathname } from "next/navigation";
55
import * as React from "react";
66
import { Menu } from "lucide-react";
77

@@ -24,31 +24,36 @@ interface MainNavProps {
2424

2525
export function MainNav({ items, children }: MainNavProps) {
2626
const segment = useSelectedLayoutSegment();
27+
const pathname = usePathname();
2728
const [open, setOpen] = React.useState(false);
2829

30+
const isStudio = pathname?.startsWith("/studio");
31+
const isBuilder = pathname?.startsWith("/builder");
32+
const subLabel = isStudio ? "Studio" : isBuilder ? "Builder" : null;
33+
2934
return (
30-
<div className="flex gap-6 md:gap-10">
31-
<Link href="/" className="hidden items-center space-x-2 md:flex">
35+
<div className="flex items-center gap-6 md:gap-10">
36+
<Link href="/" className="hidden items-center gap-2 md:flex">
3237
<RelaycodeIcon className="h-8 w-8" />
3338
<span className="tracking-sm hidden font-heading text-xl font-bold sm:inline-block">
34-
{siteConfig.name}
39+
{siteConfig.name}{subLabel && <span className="font-normal text-muted-foreground">{" "}{subLabel}</span>}
3540
</span>
3641
</Link>
37-
<nav className="hidden gap-6 md:flex">
42+
<nav className="hidden gap-6 md:flex items-center">
3843
<Link
3944
href="/builder"
4045
className={cn(
41-
"mt-1.5 flex items-center font-heading text-xl font-semibold transition-colors hover:text-foreground/80 sm:text-sm",
42-
segment === "builder" ? "text-foreground" : "text-foreground/60"
46+
"flex items-center font-heading text-xl font-semibold transition-colors hover:text-foreground/80 sm:text-sm",
47+
isBuilder ? "text-foreground" : "text-foreground/60"
4348
)}
4449
>
4550
Builder
4651
</Link>
4752
<Link
4853
href="/studio"
4954
className={cn(
50-
"mt-1.5 flex items-center font-heading text-xl font-semibold transition-colors hover:text-foreground/80 sm:text-sm",
51-
segment === "studio" ? "text-foreground" : "text-foreground/60"
55+
"flex items-center font-heading text-xl font-semibold transition-colors hover:text-foreground/80 sm:text-sm",
56+
isStudio ? "text-foreground" : "text-foreground/60"
5257
)}
5358
>
5459
Studio
@@ -58,8 +63,8 @@ export function MainNav({ items, children }: MainNavProps) {
5863
key={index}
5964
href={item.disabled ? "#" : item.href}
6065
className={cn(
61-
"mt-1.5 flex items-center font-heading text-xl font-semibold transition-colors hover:text-foreground/80 sm:text-sm",
62-
item.href.startsWith(`/${segment}`)
66+
"flex items-center font-heading text-xl font-semibold transition-colors hover:text-foreground/80 sm:text-sm",
67+
pathname?.startsWith(item.href)
6368
? "text-foreground"
6469
: "text-foreground/60",
6570
item.disabled && "cursor-not-allowed opacity-80"

components/layout/site-header.tsx

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
"use client";
22

3+
import { usePathname } from "next/navigation";
34
import { MainNavItem } from "@/types";
45

56
import { MainNav } from "@/components/layout/main-nav";
@@ -14,24 +15,40 @@ interface NavBarProps {
1415
children?: React.ReactNode;
1516
rightElements?: React.ReactNode;
1617
scroll?: boolean;
18+
centerElement?: React.ReactNode;
1719
}
1820

1921
export function NavBar({
2022
items,
2123
children,
2224
rightElements,
2325
scroll = false,
26+
centerElement,
2427
}: NavBarProps) {
2528
const scrolled = useScroll(50);
29+
const pathname = usePathname();
30+
const isToolPage = pathname?.startsWith("/studio") || pathname?.startsWith("/builder");
31+
2632
return (
2733
<header
2834
className={`sticky top-0 z-40 flex w-full justify-center bg-background/60 backdrop-blur-xl transition-all ${
2935
scroll ? (scrolled ? "border-b" : "bg-background/0") : "border-b"
3036
}`}
3137
>
32-
<div className="container flex items-center justify-between py-4">
38+
<div
39+
className={`flex items-center justify-between w-full ${
40+
isToolPage ? "px-3 py-4" : "container py-4"
41+
}`}
42+
>
3343
<MainNav items={items}>{children}</MainNav>
3444

45+
{/* Center element (e.g., active file name on Studio) */}
46+
{centerElement && (
47+
<div className="absolute left-1/2 -translate-x-1/2 flex items-center">
48+
{centerElement}
49+
</div>
50+
)}
51+
3552
<div className="flex items-center space-x-2 sm:space-x-3">
3653
{rightElements}
3754
<ChainSelector />

components/studio/compiler-section.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,12 @@ export function CompilerSection() {
9696
mode: submittedTarget,
9797
bytecodeSource: "compile",
9898
});
99-
dispatch({ type: "SET_COMPILED_HASH", hash: submittedHash });
99+
// Snapshot sources for per-file dirty tracking
100+
const sourcesSnapshot: Record<string, string> = {};
101+
for (const [name, { content }] of Object.entries(sources)) {
102+
sourcesSnapshot[name] = content;
103+
}
104+
dispatch({ type: "SET_COMPILED_HASH", hash: submittedHash, sources: sourcesSnapshot });
100105
} catch (err) {
101106
if (compileIdRef.current !== id) return;
102107
setCompilation({

components/studio/editor-tabs.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { X, Plus } from "lucide-react";
66
import { cn } from "@/lib/utils";
77

88
export function EditorTabs() {
9-
const { state, dispatch, isDirtySinceCompile } = useStudio();
9+
const { state, dispatch, isFileDirty } = useStudio();
1010

1111
const handleCreateFile = () => {
1212
let name = "Untitled.sol";
@@ -37,7 +37,7 @@ export function EditorTabs() {
3737
onClick={() => dispatch({ type: "SET_ACTIVE_TAB", fileId: tab.fileId })}
3838
>
3939
<span className="truncate max-w-[120px]">{file.name}</span>
40-
{isDirtySinceCompile && (
40+
{isFileDirty(tab.fileId) && (
4141
<span className="w-1.5 h-1.5 rounded-full bg-yellow-500 shrink-0" />
4242
)}
4343
<button

components/studio/file-explorer.tsx

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -88,11 +88,20 @@ export function FileExplorer() {
8888
<span className="text-xs font-semibold uppercase text-muted-foreground tracking-wider">
8989
Files
9090
</span>
91-
<div
92-
className="cursor-help"
93-
title="Files are flat (no folders). Use import &quot;FileName.sol&quot; for local imports. Renaming a file does not update imports in other files."
94-
>
95-
<Info className="h-3 w-3 text-muted-foreground" />
91+
<div className="flex items-center gap-1">
92+
<button
93+
className="p-0.5 rounded hover:bg-accent/50 transition-colors"
94+
onClick={handleCreateFile}
95+
title="New file"
96+
>
97+
<Plus className="h-3.5 w-3.5 text-muted-foreground" />
98+
</button>
99+
<div
100+
className="cursor-help"
101+
title="Files are flat (no folders). Use import &quot;FileName.sol&quot; for local imports. Renaming a file does not update imports in other files."
102+
>
103+
<Info className="h-3 w-3 text-muted-foreground" />
104+
</div>
96105
</div>
97106
</div>
98107

0 commit comments

Comments
 (0)