stack/apps/dashboard/src/app/layout.tsx
BilalG1 4f99c469fe
stack auth preview mode (#1307)
<!--

Make sure you've read the CONTRIBUTING.md guidelines:
https://github.com/stack-auth/stack-auth/blob/dev/CONTRIBUTING.md

-->


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Preview mode: sandboxed experience with mock projects, placeholder
data, and disabled external integrations (payments, webhooks, email
rendering, session replays).
* One-click preview project creation and automatic preview sign-in for
quick access.

* **New Features — Walkthrough**
* Interactive guided walkthroughs with spotlight, animated cursor,
step-driven navigation, and targeted element hooks.

* **Style**
* UI/UX adjustments for preview: theme behavior, conditional
banners/alerts, informational alerts, and walkthrough attributes added
across pages.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-04-08 16:57:42 -07:00

125 lines
5.3 KiB
TypeScript

import { DevErrorNotifier } from '@/components/dev-error-notifier';
import { RouterProvider } from '@/components/router';
import { SiteLoadingIndicatorDisplay } from '@/components/site-loading-indicator';
import { StyleLink } from '@/components/style-link';
import { Toaster, cn } from '@/components/ui';
import { getPublicEnvVar } from '@/lib/env';
import { stackServerApp } from '@/stack';
import { StackProvider, StackTheme } from '@stackframe/stack';
import { getEnvVariable, getNodeEnvironment } from '@stackframe/stack-shared/dist/utils/env';
import { Analytics } from "@vercel/analytics/react";
import { SpeedInsights } from "@vercel/speed-insights/next";
import { GeistMono } from "geist/font/mono";
import { GeistSans } from 'geist/font/sans';
import type { Metadata } from 'next';
import { Inter as FontSans } from "next/font/google";
import React from 'react';
// import { VersionAlerter } from '../components/version-alerter';
import { VersionAlerter } from '@/components/version-alerter';
import '../polyfills';
import { BackgroundShine } from './background-shine';
import { ClientPolyfill } from './client-polyfill';
import { DevelopmentPortDisplay } from './development-port-display';
import './globals.css';
import { UserIdentity } from './providers';
export const metadata: Metadata = {
metadataBase: new URL(getPublicEnvVar('NEXT_PUBLIC_STACK_API_URL') || ''),
title: {
default: 'Stack Auth Dashboard',
template: '%s | Stack Auth',
},
description: 'Stack Auth is the open-source Auth0 alternative, and the fastest way to add authentication to your web app.',
openGraph: {
title: 'Stack Auth Dashboard',
description: 'Stack Auth is the open-source Auth0 alternative, and the fastest way to add authentication to your web app.',
images: [`${getPublicEnvVar('NEXT_PUBLIC_STACK_API_URL')}/open-graph-image.png`]
},
twitter: {
title: 'Stack Auth Dashboard',
description: 'Stack Auth is the open-source Auth0 alternative, and the fastest way to add authentication to your web app.',
images: [`${getPublicEnvVar('NEXT_PUBLIC_STACK_API_URL')}/open-graph-image.png`]
},
};
const fontSans = FontSans({
subsets: ["latin"],
variable: "--font-sans",
});
type TagConfigJson = {
tagName: string,
attributes: { [key: string]: string },
innerHTML?: string,
};
export default function RootLayout({
children,
}: {
children: React.ReactNode,
}) {
const headTags: TagConfigJson[] = JSON.parse(getEnvVariable('NEXT_PUBLIC_STACK_HEAD_TAGS', '[]'));
const translationLocale = getEnvVariable('STACK_DEVELOPMENT_TRANSLATION_LOCALE', "") || undefined;
if (translationLocale !== undefined && getNodeEnvironment() !== 'development') {
throw new Error(`STACK_DEVELOPMENT_TRANSLATION_LOCALE can only be used in development mode (found: ${JSON.stringify(translationLocale)})`);
}
const enableReactScanInDevelopment = getPublicEnvVar('NEXT_PUBLIC_STACK_ENABLE_REACT_SCAN_IN_DEVELOPMENT') === 'true';
return (
<html suppressHydrationWarning lang="en" className={`${GeistSans.variable} ${GeistMono.variable}`}>
<head>
<link rel="preconnect" href="https://fonts.gstatic.com" />
<StyleLink href="https://fonts.googleapis.com/css2?family=Material+Symbols+Rounded&display=block" />
<StyleLink defer href="https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/katex.css" integrity="sha384-OH8qNTHoMMVNVcKdKewlipV4SErXqccxxlg6HC9Cwjr5oZu2AdBej1TndeCirael" crossOrigin="anonymous" />
{/* eslint-disable-next-line @next/next/no-sync-scripts */}
{process.env.NODE_ENV === 'development' && enableReactScanInDevelopment && <script
crossOrigin="anonymous"
src="//unpkg.com/react-scan/dist/auto.global.js"
/>}
{headTags.map((tag, index) => {
const { tagName, attributes, innerHTML } = tag;
return React.createElement(tagName, {
key: index,
dangerouslySetInnerHTML: { __html: innerHTML ?? "" },
...attributes,
});
})}
{/* eslint-disable-next-line @next/next/no-sync-scripts */}
<script dangerouslySetInnerHTML={{ __html: getPublicEnvVar("NEXT_PUBLIC_STACK_IS_PREVIEW") === "true"
? "(function(){try{var t=localStorage.getItem('theme');var d=document.documentElement;var r=t==='dark'||t==='light'?t:'light';d.classList.add(r);d.style.colorScheme=r}catch(e){}})()"
: "(function(){try{var t=localStorage.getItem('theme');var d=document.documentElement;var r=t==='dark'||t==='light'?t:window.matchMedia('(prefers-color-scheme:dark)').matches?'dark':'light';d.classList.add(r);d.style.colorScheme=r}catch(e){}})()"
}} />
</head>
<body
className={cn(
"min-h-screen bg-background font-sans antialiased",
fontSans.variable
)}
suppressHydrationWarning
>
<Analytics />
<SpeedInsights />
<StackProvider app={stackServerApp} lang={translationLocale as any}>
<StackTheme>
<ClientPolyfill />
<RouterProvider>
<UserIdentity />
<VersionAlerter />
<BackgroundShine />
{children}
<DevelopmentPortDisplay />
</RouterProvider>
</StackTheme>
</StackProvider>
<DevErrorNotifier />
<Toaster />
<SiteLoadingIndicatorDisplay />
</body>
</html>
);
}