-
Notifications
You must be signed in to change notification settings - Fork 241
Expand file tree
/
Copy pathlayout.tsx
More file actions
198 lines (192 loc) · 6.94 KB
/
Copy pathlayout.tsx
File metadata and controls
198 lines (192 loc) · 6.94 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
import type { Metadata, Viewport } from "next";
import { Inter } from "next/font/google";
import localFont from "next/font/local";
import "./globals.css";
import { SidebarProvider } from "@/context/sidebar-context";
import { ThemeProvider } from "@/context/theme-context";
import { WalletProvider, WatchlistProvider } from "@/context/wallet-context";
import { OfflineBanner } from "@/components/common/offline-banner";
import { CookieConsentBanner } from "@/components/common/cookie-consent-banner";
import { Toaster } from "@/components/ui/toaster";
const inter = Inter({
variable: "--font-inter",
subsets: ["latin"],
// Keep the fallback visible for the remaining custom typeface as well.
display: "swap",
});
/**
* These first-viewport local fonts explicitly use Next.js preloading and a
* swap display policy. Next.js emits fingerprinted preload links, so there is
* no fragile hand-written URL that could become stale after a build.
*/
const clashDisplay = localFont({
src: "../public/font/clash-display-variable.ttf",
variable: "--font-clash",
weight: "500",
display: "swap",
preload: true,
});
const generalSans = localFont({
src: "../public/font/general-sans-variable.ttf",
variable: "--font-general-sans",
weight: "400",
display: "swap",
preload: true,
});
/**
* Global metadata configuration for the StelloPay application.
* Sets the default title templates and default OpenGraph and Twitter preview parameters.
*
* OG image
* ────────
* The `openGraph.images` entry references `/opengraph-image` — the dynamic
* route served by `app/opengraph-image.tsx` via the Next.js file convention.
* At build time Next.js rewrites this to an immutable, content-hashed URL so
* CDN and social-crawler caches are busted automatically on every deploy.
*
* To validate after deploying:
* • https://developers.facebook.com/tools/debug/
* • https://cards-dev.twitter.com/validator
* • https://www.opengraph.xyz/
*/
export const metadata: Metadata = {
title: {
default: "StelloPay",
template: "%s | StelloPay",
},
description:
"StelloPay — fast, secure blockchain payroll and payments powered by Stellar.",
openGraph: {
title: "StelloPay — The Future of Payroll on Blockchain",
description:
"Fast, secure blockchain payroll and payments powered by Stellar. Built for modern businesses.",
url: "https://stellopay.com",
siteName: "StelloPay",
images: [
{
// Dynamic OG image generated by app/opengraph-image.tsx
url: "/opengraph-image",
width: 1200,
height: 630,
alt: "StelloPay — The Future of Payroll on Blockchain. Brand gradient headline on white background.",
type: "image/png",
},
],
locale: "en_US",
type: "website",
},
manifest: "/manifest.json",
twitter: {
card: "summary_large_image",
title: "StelloPay — The Future of Payroll on Blockchain",
description:
"Fast, secure blockchain payroll and payments powered by Stellar. Built for modern businesses.",
// Next.js reads twitter.images from openGraph.images automatically when not
// explicitly set, but we specify it here for explicitness and compatibility
// with older crawlers that do not follow the og:image fallback.
images: [
{
url: "/opengraph-image",
alt: "StelloPay — The Future of Payroll on Blockchain. Brand gradient headline on white background.",
},
],
creator: "@stellopay",
},
};
/**
* Global viewport configuration per Next.js 15 conventions.
*/
export const viewport: Viewport = {
width: "device-width",
initialScale: 1,
themeColor: [
{ media: "(prefers-color-scheme: light)", color: "white" },
{ media: "(prefers-color-scheme: dark)", color: "black" },
],
};
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
<html lang="en" suppressHydrationWarning>
<head>
{/*
Pre-hydration theme initializer:
Executes immediately on load to determine the user's preferred theme
from localStorage or system settings and applies the 'dark' class to
the <html> element before React hydrates. This prevents light flash on page load.
*/}
<script
dangerouslySetInnerHTML={{
__html: `
(function() {
var stored = null;
try {
stored = window.localStorage.getItem('theme');
} catch (e) {}
try {
var prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
var useSystem = stored !== 'dark' && stored !== 'light';
if (stored === 'dark' || (useSystem && prefersDark)) {
document.documentElement.classList.add('dark');
} else {
document.documentElement.classList.remove('dark');
}
} catch (e) {}
})();
`,
}}
/>
{/*
Service-worker registration — client-side only.
Runs after the page has loaded so it never blocks the critical path.
The 'load' event guard is intentional: SW registration is deferred
until after the page is interactive so it does not compete with
first-paint resources.
*/}
<script
data-testid="sw-registration-script"
dangerouslySetInnerHTML={{
__html: `
if ('serviceWorker' in navigator) {
window.addEventListener('load', function() {
navigator.serviceWorker
.register('/sw.js', { scope: '/' })
.catch(function(err) {
// Registration failure is non-fatal — the app works
// normally without a service worker.
console.warn('[SW] Registration failed:', err);
});
});
}
`,
}}
/>
</head>
<body
className={`${inter.variable} ${clashDisplay.variable} ${generalSans.variable} antialiased`}
>
{/* Skip navigation link — first focusable element on every page.
Allows keyboard/screen-reader users to bypass repeated nav. */}
<a
href="#main-content"
className="sr-only focus:not-sr-only focus:fixed focus:top-4 focus:left-4 focus:z-[9999] focus:px-4 focus:py-2 focus:bg-white focus:text-black focus:rounded focus:shadow-lg focus:outline-none focus:ring-2 focus:ring-black"
>
Skip to main content
</a>
<OfflineBanner />
<CookieConsentBanner />
<ThemeProvider>
<WalletProvider>
<WatchlistProvider>
<SidebarProvider>{children}</SidebarProvider>
</WatchlistProvider>
</WalletProvider>
<Toaster />
</ThemeProvider>
</body>
</html>
);
}