diff --git a/__tests__/components/builder/extrinsic-builder.test.tsx b/__tests__/components/builder/extrinsic-builder.test.tsx index eeb2b23..f6053ab 100644 --- a/__tests__/components/builder/extrinsic-builder.test.tsx +++ b/__tests__/components/builder/extrinsic-builder.test.tsx @@ -222,12 +222,10 @@ describe("ExtrinsicBuilder", () => { }); }); - it("renders the Extrinsic Builder heading", () => { + it("renders the Section and Method selectors", () => { render(); - expect(screen.getByText("Extrinsic Builder")).toBeInTheDocument(); - expect( - screen.getByText("Build and analyze extrinsics for Polkadot") - ).toBeInTheDocument(); + expect(screen.getByText("Section")).toBeInTheDocument(); + expect(screen.getByText("Method")).toBeInTheDocument(); }); it("renders Section combobox", () => { diff --git a/app/api/og/builder/route.tsx b/app/api/og/builder/route.tsx new file mode 100644 index 0000000..ddcecbd --- /dev/null +++ b/app/api/og/builder/route.tsx @@ -0,0 +1,301 @@ +import { ImageResponse } from "next/og"; +import { loadFonts } from "../fonts"; + +export const runtime = "edge"; + +const ICON_PATH = + "m51 12.804-21-12a5.999 5.999 0 0 0-6 0l-21 12a6 6 0 0 0-3 5.21v24a5.998 5.998 0 0 0 3 5.21l21 12a5.999 5.999 0 0 0 6 0l21-12a5.998 5.998 0 0 0 3-5.21v-24a6 6 0 0 0-3-5.21Zm-24-6.79 9.88 5.65a30.8 30.8 0 0 1-5.25 14.78 37 37 0 0 0-20-11.63L27 6.014ZM6 20.084a31.003 31.003 0 0 1 21.73 11A30.896 30.896 0 0 1 6 40.014v-19.93Zm21 33.93-14.9-8.51a36.893 36.893 0 0 0 19.06-9.4A30.786 30.786 0 0 1 35 49.464l-8 4.55Zm21-12-7.32 4.18a36.752 36.752 0 0 0-5.29-14.74 36.822 36.822 0 0 0 7.14-16.57l5.47 3.13v24Z"; + +export async function GET() { + const fonts = await loadFonts(); + + return new ImageResponse( + ( +
+ {/* Subtle dot pattern */} +
+ + {/* Top bar: logo at top-left */} +
+
+ + + + + Relaycode + +
+
+ + {/* Main content — split layout */} +
+ {/* Left: text */} +
+
+ Builder +
+
+ Visual extrinsic builder for the Polkadot ecosystem. Build, + encode, and submit any pallet call. +
+
+ {["All Pallets", "All Chains", "SCALE Codec"].map((tech) => ( +
+ {tech} +
+ ))} +
+
+ + {/* Right: builder form mockup */} +
+
+ {/* Header */} +
+ + Extrinsic Builder + +
+ {/* Form rows */} +
+ {[ + { label: "Section", value: "balances", mono: false }, + { label: "Method", value: "transferKeepAlive", mono: false }, + { label: "dest", value: "5GrwvaEF5z...", mono: true }, + { label: "value", value: "10.0 DOT", mono: false }, + ].map((row) => ( +
+ + {row.label} + +
+ {row.value} +
+
+ ))} + {/* Submit button — black */} +
+
+ Sign and Submit +
+
+
+
+
+
+ + {/* Bottom row: website URL */} +
+ + relaycode.org + +
+ + {/* Bottom gradient bar */} +
+
+ ), + { + width: 1200, + height: 630, + fonts, + } + ); +} diff --git a/app/api/og/docs/route.tsx b/app/api/og/docs/route.tsx new file mode 100644 index 0000000..48f6ca7 --- /dev/null +++ b/app/api/og/docs/route.tsx @@ -0,0 +1,386 @@ +import { ImageResponse } from "next/og"; +import { loadFonts } from "../fonts"; + +export const runtime = "edge"; + +const ICON_PATH = + "m51 12.804-21-12a5.999 5.999 0 0 0-6 0l-21 12a6 6 0 0 0-3 5.21v24a5.998 5.998 0 0 0 3 5.21l21 12a5.999 5.999 0 0 0 6 0l21-12a5.998 5.998 0 0 0 3-5.21v-24a6 6 0 0 0-3-5.21Zm-24-6.79 9.88 5.65a30.8 30.8 0 0 1-5.25 14.78 37 37 0 0 0-20-11.63L27 6.014ZM6 20.084a31.003 31.003 0 0 1 21.73 11A30.896 30.896 0 0 1 6 40.014v-19.93Zm21 33.93-14.9-8.51a36.893 36.893 0 0 0 19.06-9.4A30.786 30.786 0 0 1 35 49.464l-8 4.55Zm21-12-7.32 4.18a36.752 36.752 0 0 0-5.29-14.74 36.822 36.822 0 0 0 7.14-16.57l5.47 3.13v24Z"; + +export async function GET() { + const fonts = await loadFonts(); + + return new ImageResponse( + ( +
+ {/* Subtle dot pattern */} +
+ + {/* Top bar: logo at top-left */} +
+
+ + + + + Relaycode + +
+
+ + {/* Main content — split layout */} +
+ {/* Left: text */} +
+
+ Docs +
+
+ Developer documentation for the Relaycode toolkit. Guides, + references, and examples. +
+
+ + {/* Right: docs page mockup */} +
+
+ {/* Sidebar */} +
+ + Getting Started + + + Introduction + + + Installation + + + Quick Start + + + Builder + + + Pallets + + + Encoding + + + Studio + + + Contracts + +
+ {/* Content area */} +
+ + Introduction + +
+
+
+
+ {/* Code block */} +
+
+ import { DedotClient } +
+
+   from 'dedot'; +
+
+
+
+
+
+
+
+ + {/* Bottom row: website URL */} +
+ + relaycode.org + +
+ + {/* Bottom gradient bar */} +
+
+ ), + { + width: 1200, + height: 630, + fonts, + } + ); +} diff --git a/app/api/og/fonts.ts b/app/api/og/fonts.ts new file mode 100644 index 0000000..8fe2dfe --- /dev/null +++ b/app/api/og/fonts.ts @@ -0,0 +1,42 @@ +export async function loadFonts() { + const [nunitoBold, nunitoRegular] = await Promise.all([ + loadGoogleFont("Nunito", 700), + loadGoogleFont("Nunito", 400), + ]); + return [ + { + name: "Nunito", + data: nunitoBold, + weight: 700 as const, + style: "normal" as const, + }, + { + name: "Nunito", + data: nunitoRegular, + weight: 400 as const, + style: "normal" as const, + }, + ]; +} + +async function loadGoogleFont( + family: string, + weight: number +): Promise { + const css = await fetch( + `https://fonts.googleapis.com/css2?family=${family.replace(" ", "+")}:wght@${weight}`, + { + headers: { + "User-Agent": + "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_8; de-at) AppleWebKit/533.21.1 (KHTML, like Gecko) Version/5.0.5 Safari/533.21.1", + }, + } + ).then((r) => r.text()); + + const url = css.match( + /src: url\((.+?)\) format\('(opentype|truetype)'\)/ + )?.[1]; + if (!url) + throw new Error(`Could not find font URL for ${family} ${weight}`); + return fetch(url).then((r) => r.arrayBuffer()); +} diff --git a/app/api/og/home/route.tsx b/app/api/og/home/route.tsx new file mode 100644 index 0000000..3b7f7a3 --- /dev/null +++ b/app/api/og/home/route.tsx @@ -0,0 +1,250 @@ +import { ImageResponse } from "next/og"; +import { loadFonts } from "../fonts"; + +export const runtime = "edge"; + +const ICON_PATH = + "m51 12.804-21-12a5.999 5.999 0 0 0-6 0l-21 12a6 6 0 0 0-3 5.21v24a5.998 5.998 0 0 0 3 5.21l21 12a5.999 5.999 0 0 0 6 0l21-12a5.998 5.998 0 0 0 3-5.21v-24a6 6 0 0 0-3-5.21Zm-24-6.79 9.88 5.65a30.8 30.8 0 0 1-5.25 14.78 37 37 0 0 0-20-11.63L27 6.014ZM6 20.084a31.003 31.003 0 0 1 21.73 11A30.896 30.896 0 0 1 6 40.014v-19.93Zm21 33.93-14.9-8.51a36.893 36.893 0 0 0 19.06-9.4A30.786 30.786 0 0 1 35 49.464l-8 4.55Zm21-12-7.32 4.18a36.752 36.752 0 0 0-5.29-14.74 36.822 36.822 0 0 0 7.14-16.57l5.47 3.13v24Z"; + +export async function GET() { + const fonts = await loadFonts(); + + return new ImageResponse( + ( +
+ {/* Subtle dot pattern */} +
+ + {/* Top bar: logo at top-left */} +
+
+ + + + + Relaycode + +
+
+ + {/* Main content — centered */} +
+ {/* Title */} +
+ The Developer Toolkit +
+ + {/* Subtitle with gradient "Polkadot" */} +
+ for + + Polkadot + +
+ + {/* Tool badges — 2x2 grid */} +
+ {[ + [ + { + name: "Contract Studio", + icon: ( + + + + + + + ), + }, + { + name: "Extrinsic Builder", + icon: ( + + + + + ), + }, + ], + [ + { + name: "Component Docs", + icon: ( + + + + ), + }, + { + name: "Substrate Utilities", + icon: ( + + + + ), + }, + ], + ].map((row, i) => ( +
+ {row.map((tool) => ( +
+ {tool.name} + {tool.icon} +
+ ))} +
+ ))} +
+
+ + {/* Bottom row: website URL at bottom-right */} +
+ + relaycode.org + +
+ + {/* Bottom gradient accent bar */} +
+
+ ), + { + width: 1200, + height: 630, + fonts, + } + ); +} diff --git a/app/api/og/studio/route.tsx b/app/api/og/studio/route.tsx new file mode 100644 index 0000000..64022c9 --- /dev/null +++ b/app/api/og/studio/route.tsx @@ -0,0 +1,331 @@ +import { ImageResponse } from "next/og"; +import { loadFonts } from "../fonts"; + +export const runtime = "edge"; + +const ICON_PATH = + "m51 12.804-21-12a5.999 5.999 0 0 0-6 0l-21 12a6 6 0 0 0-3 5.21v24a5.998 5.998 0 0 0 3 5.21l21 12a5.999 5.999 0 0 0 6 0l21-12a5.998 5.998 0 0 0 3-5.21v-24a6 6 0 0 0-3-5.21Zm-24-6.79 9.88 5.65a30.8 30.8 0 0 1-5.25 14.78 37 37 0 0 0-20-11.63L27 6.014ZM6 20.084a31.003 31.003 0 0 1 21.73 11A30.896 30.896 0 0 1 6 40.014v-19.93Zm21 33.93-14.9-8.51a36.893 36.893 0 0 0 19.06-9.4A30.786 30.786 0 0 1 35 49.464l-8 4.55Zm21-12-7.32 4.18a36.752 36.752 0 0 0-5.29-14.74 36.822 36.822 0 0 0 7.14-16.57l5.47 3.13v24Z"; + +export async function GET() { + const fonts = await loadFonts(); + + return new ImageResponse( + ( +
+ {/* Subtle dot pattern */} +
+ + {/* Top bar: logo at top-left */} +
+
+ + + + + Relaycode + +
+
+ + {/* Main content — split layout */} +
+ {/* Left: text */} +
+
+ Studio +
+
+ Smart contract IDE for Polkadot Hub. Write Solidity, compile to + PVM, deploy from your browser. +
+
+ {["Solidity", "EVM", "PVM", "PolkaVM"].map((tech) => ( +
+ {tech} +
+ ))} +
+
+ + {/* Right: code editor mockup */} +
+
+ {/* Title bar */} +
+
+
+
+ + Contract.sol + +
+ {/* Code lines */} +
+
+ {"// SPDX-License-Identifier: MIT"} +
+
+ pragma solidity ^0.8.20; +
+
+
+ contract + + MyToken + + + is ERC20 { + +
+
+ string public name; +
+
+ uint256 public totalSupply; +
+
+
+ constructor + () + + { + +
+
+ name = "PolkaSwap"; +
+
+ } +
+
+ } +
+
+
+
+
+ + {/* Bottom row: website URL */} +
+ + relaycode.org + +
+ + {/* Bottom gradient bar */} +
+
+ ), + { + width: 1200, + height: 630, + fonts, + } + ); +} diff --git a/app/builder/layout.tsx b/app/builder/layout.tsx index 84a14f2..44a6033 100644 --- a/app/builder/layout.tsx +++ b/app/builder/layout.tsx @@ -7,6 +7,13 @@ export const metadata: Metadata = { title: "Relaycode Builder — Polkadot Extrinsic Builder", description: "Build, encode, decode, and submit any Substrate extrinsic visually. Supports all pallets across the Polkadot ecosystem.", + openGraph: { + images: [{ url: "/api/og/builder", width: 1200, height: 630 }], + }, + twitter: { + card: "summary_large_image", + images: ["/api/og/builder"], + }, }; interface BuilderLayoutProps { diff --git a/app/docs/layout.tsx b/app/docs/layout.tsx index d752f84..91eff0d 100644 --- a/app/docs/layout.tsx +++ b/app/docs/layout.tsx @@ -1,7 +1,21 @@ import type { ReactNode } from "react"; +import type { Metadata } from "next"; import { DocsLayout } from "fumadocs-ui/layouts/docs"; import { source } from "@/lib/source"; +export const metadata: Metadata = { + title: "Relaycode Docs — Developer Documentation", + description: + "Developer documentation for the Relaycode toolkit. Guides, references, and examples for building on Polkadot.", + openGraph: { + images: [{ url: "/api/og/docs", width: 1200, height: 630 }], + }, + twitter: { + card: "summary_large_image", + images: ["/api/og/docs"], + }, +}; + export default function Layout({ children }: { children: ReactNode }) { return ( Relaycode Studio — Polkadot Smart Contract IDE + + + + + } />
{children}
diff --git a/config/site.ts b/config/site.ts index 1c23710..cf692d9 100644 --- a/config/site.ts +++ b/config/site.ts @@ -8,7 +8,7 @@ export const siteConfig: SiteConfig = { description: "Relaycode: Intuitive extrinsics builder for Polkadot ecosystem. Simplify complex pallet interactions with real-time encoding, wallet integration, and shareable snippets.", url: site_url, - ogImage: `${site_url}/_static/og.jpg`, + ogImage: `${site_url}/api/og/home`, links: { twitter: "https://twitter.com/itsyogesh18", github: "https://github.com/itsyogesh", diff --git a/lib/utils.ts b/lib/utils.ts index 6821db4..fbb6350 100644 --- a/lib/utils.ts +++ b/lib/utils.ts @@ -46,6 +46,7 @@ export function constructMetadata({ title, description, siteName: title, + images: [{ url: image, width: 1200, height: 630 }], }, twitter: { card: "summary_large_image",