Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 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 | 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 4x 4x 4x 4x 4x 4x 4x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 3x 3x 3x 3x 3x 2x 2x 2x 2x 1x 1x 2x 2x 1x 3x 3x 3x 3x 3x 3x 3x 3x 3x 3x 3x 3x 3x 3x 3x 3x 3x 3x 3x | import '@/app/globals.css'
import {ReactNode} from 'react'
import type {Metadata, Viewport} from 'next'
import {Archivo, JetBrains_Mono} from 'next/font/google'
import {getServerSession} from 'next-auth'
import {authOptions} from '@/lib/auth'
import {prisma} from '@/lib/db'
import {brandForRequest, currentBand, iconUrl, PRODUCT} from '@/lib/band'
import {chatLabel} from '@/lib/band-fields'
import Providers from '@/components/Providers'
import AppShell, {type ShellBand} from '@/components/AppShell'
import {DEMO} from '@/lib/demo'
import {THEME_COLORS, themeScript} from '@/lib/theme'
const archivo = Archivo({subsets: ['latin'], variable: '--font-archivo'})
const jetbrains = JetBrains_Mono({
subsets: ['latin'],
weight: ['500', '700'],
variable: '--font-jetbrains',
})
/** The band this page is shown as: the chosen band, else this address's. */
async function shellContext() {
const session = await getServerSession(authOptions)
const user = session?.user?.email
? await prisma.user.findUnique({where: {email: session.user.email}})
: null
const {band, bands} = user
? await currentBand(user.id)
: {band: null, bands: []}
const brand = band ? null : await brandForRequest()
return {session, user, band, bands, brand}
}
export async function generateMetadata(): Promise<Metadata> {
const {band, brand} = await shellContext()
const appName = band?.appName ?? brand?.appName ?? PRODUCT
const icon = band ? iconUrl(band) : (brand?.iconUrl ?? iconUrl(null))
const forWhom = band?.name ?? brand?.band?.name
return {
title: {default: appName, template: `%s ยท ${appName}`},
description: forWhom
? `Charts, setlists and rehearsals for ${forWhom}`
: 'Charts, setlists and rehearsals for bands',
applicationName: appName,
appleWebApp: {capable: true, title: appName, statusBarStyle: 'black'},
manifest: '/manifest.webmanifest',
icons: {icon, apple: icon},
// The public demo is a copy of one made-up band, not a place to land
...(DEMO ? {robots: {index: false, follow: false}} : {}),
}
}
export const viewport: Viewport = {
themeColor: THEME_COLORS.dark,
width: 'device-width',
initialScale: 1,
viewportFit: 'cover',
}
export default async function RootLayout({children}: {children: ReactNode}) {
const {session, user, band, bands, brand} = await shellContext()
const shell: ShellBand = {
appName: band?.appName ?? brand?.appName ?? PRODUCT,
band: band
? {
id: band.id,
name: band.name,
chat: band.chatUrl
? {url: band.chatUrl, label: chatLabel(band.chatUrl)}
: null,
isAdmin: band.isAdmin || Boolean(user?.isOwner),
}
: null,
bands: bands.map((b) => ({id: b.id, name: b.name})),
isOwner: Boolean(user?.isOwner),
}
return (
// data-theme is set by themeScript before React hydrates
<html
lang="en"
className={`${archivo.variable} ${jetbrains.variable}`}
suppressHydrationWarning
>
<head>
<script dangerouslySetInnerHTML={{__html: themeScript}} />
{/* Before anything can save: see scripts/demo/demo.js */}
{/* eslint-disable-next-line @next/next/no-sync-scripts */}
{DEMO && <script src="/demo.js" />}
</head>
<body>
<Providers session={session}>
<AppShell ctx={shell}>{children}</AppShell>
</Providers>
</body>
</html>
)
}
|