mirror of
https://github.com/hanzoai/bootnode.git
synced 2026-10-25 02:31:30 +00:00
Remove the hardcoded 5-brand catalog from web/lib/brand.ts; resolve the active brand at runtime by hostname from an external catalog (window.__BRAND_CATALOG__ / /config.json), mirroring the @hanzo/id SPA. Ship one neutral built-in bootnode fallback. Add BrandProvider that loads the per-deploy catalog client-side after hydration. Co-authored-by: Hanzo Dev <[email protected]>
86 lines
3.0 KiB
TypeScript
86 lines
3.0 KiB
TypeScript
import type { Metadata } from "next"
|
|
import { GeistSans } from "geist/font/sans"
|
|
import { GeistMono } from "geist/font/mono"
|
|
import { ThemeProvider } from "@/components/theme-provider"
|
|
import { AuthProvider } from "@/lib/auth"
|
|
import { Providers } from "@/app/providers"
|
|
import { Toaster } from "@/components/ui/sonner"
|
|
import { BrandProvider } from "@/components/brand-provider"
|
|
import { getBrand } from "@/lib/brand"
|
|
import "@/app/globals.css"
|
|
|
|
// Static export: the prerendered HTML is brand-neutral. There is no
|
|
// request-time host detection here, and source carries no brand catalog — so
|
|
// SSG metadata uses bootnode's built-in neutral default. The real brand is
|
|
// resolved CLIENT-SIDE after hydration by BrandProvider, which loads the
|
|
// per-deploy catalog (window.__BRAND_CATALOG__ / /config.json) and re-resolves
|
|
// title/favicon/logos by hostname. Mirrors the @hanzo/id SPA.
|
|
function getServerBrand() {
|
|
const b = getBrand()
|
|
return { name: b.name, tagline: b.tagline, domain: b.domain, icon: b.favicon, defaultTheme: b.defaultTheme }
|
|
}
|
|
|
|
export function generateMetadata(): Metadata {
|
|
const b = getServerBrand()
|
|
return {
|
|
title: {
|
|
default: `${b.name} - ${b.tagline}`,
|
|
template: `%s | ${b.name}`,
|
|
},
|
|
description: `Enterprise blockchain infrastructure powered by ${b.name}. Multi-chain RPC, Token APIs, NFT APIs, Smart Wallets, Webhooks, and more.`,
|
|
keywords: ["blockchain", "RPC", "API", "Web3", "Ethereum", "Solana", "NFT", "DeFi", "smart contracts", "developer tools", b.name.toLowerCase()],
|
|
authors: [{ name: b.name }],
|
|
openGraph: {
|
|
type: "website",
|
|
locale: "en_US",
|
|
url: `https://${b.domain}`,
|
|
siteName: b.name,
|
|
title: `${b.name} - ${b.tagline}`,
|
|
description: `Enterprise blockchain infrastructure powered by ${b.name}. Multi-chain RPC, Token APIs, NFT APIs, Smart Wallets, and more.`,
|
|
},
|
|
twitter: {
|
|
card: "summary_large_image",
|
|
title: `${b.name} - ${b.tagline}`,
|
|
description: `Enterprise blockchain infrastructure powered by ${b.name}. Multi-chain RPC, Token APIs, NFT APIs, Smart Wallets, and more.`,
|
|
},
|
|
icons: {
|
|
icon: b.icon,
|
|
apple: b.icon,
|
|
shortcut: b.icon,
|
|
},
|
|
}
|
|
}
|
|
|
|
export default function RootLayout({
|
|
children,
|
|
}: {
|
|
children: React.ReactNode
|
|
}) {
|
|
const b = getServerBrand()
|
|
return (
|
|
<html
|
|
lang="en"
|
|
suppressHydrationWarning
|
|
className={`${GeistSans.variable} ${GeistMono.variable}`}
|
|
>
|
|
<body className="min-h-screen bg-background font-sans antialiased">
|
|
<Providers>
|
|
<BrandProvider>
|
|
<AuthProvider>
|
|
<ThemeProvider
|
|
attribute="class"
|
|
defaultTheme={b.defaultTheme}
|
|
enableSystem={false}
|
|
disableTransitionOnChange
|
|
>
|
|
{children}
|
|
<Toaster />
|
|
</ThemeProvider>
|
|
</AuthProvider>
|
|
</BrandProvider>
|
|
</Providers>
|
|
</body>
|
|
</html>
|
|
)
|
|
}
|