All files / web/src/app layout.tsx

96.79% Statements 151/156
33.33% Branches 3/9
100% Functions 3/3
96.79% Lines 151/156

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 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 1571x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x           2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x  
import { dehydrate } from '@tanstack/react-query'
import type { Metadata, Viewport } from 'next'
import './globals.css'
import { auth } from '@/auth'
import { ClientProviders } from '@/components/ClientProviders'
import { fraunces, inter } from '@/lib/fonts'
import { getMessages } from '@/i18n/messages'
import { getRequestLocale } from '@/i18n/request'
import { resolveUserRole } from '@/lib/auth/roles'
import { getAllFlags } from '@/lib/feature-flags'
import { getQueryClient } from '@/lib/queryClient'
import { billingKeys, featureFlagKeys } from '@/lib/queryKeys'
import { getTierForUser } from '@/lib/subscription'
import { TIER_LIMITS } from '@/lib/tier-limits'
 
export const metadata: Metadata = {
  metadataBase: new URL('https://abaci.one'),
  title: {
    default: 'Abaci One — Adaptive Abacus Math Practice for Kids',
    template: '%s | Abaci One',
  },
  description:
    "Screen time that builds real math skills. Adaptive daily practice, printable materials, and multiplayer math games — all built around the world's most effective mental math tool.",
  keywords: [
    'soroban',
    'abacus',
    'Japanese abacus',
    'mental arithmetic',
    'math games',
    'abacus tutorial',
    'soroban learning',
    'arithmetic practice',
    'educational games',
    'math practice for kids',
    'adaptive math',
    'mental math for kids',
    'abacus for kids',
    'math screen time',
  ],
  authors: [{ name: 'Abaci One' }],
  creator: 'Abaci One',
  publisher: 'Abaci One',
 
  // Open Graph
  openGraph: {
    type: 'website',
    locale: 'en_US',
    alternateLocale: ['de_DE', 'ja_JP', 'hi_IN', 'es_ES', 'la'],
    url: 'https://abaci.one',
    title: 'Abaci One — Adaptive Abacus Math Practice for Kids',
    description:
      "Screen time that builds real math skills. Adaptive daily practice, printable materials, and multiplayer math games — all built around the world's most effective mental math tool.",
    siteName: 'Abaci One',
  },
 
  // Twitter
  twitter: {
    card: 'summary_large_image',
    title: 'Abaci One — Adaptive Abacus Math Practice for Kids',
    description:
      'Adaptive abacus math practice for kids. Daily practice that adapts, multiplayer math games, and printable materials.',
  },
 
  // Icons
  icons: {
    icon: [
      { url: '/favicon.ico', sizes: 'any' },
      { url: '/icon', type: 'image/svg+xml' },
    ],
    apple: '/apple-touch-icon.png',
  },
 
  // App-specific
  applicationName: 'Abaci One',
  appleWebApp: {
    capable: true,
    statusBarStyle: 'default',
    title: 'Abaci One',
  },
 
  // Modern web app capable meta tag (non-Apple browsers)
  other: {
    'mobile-web-app-capable': 'yes',
  },
 
  // Category
  category: 'education',
}
 
export const viewport: Viewport = {
  width: 'device-width',
  initialScale: 1,
  maximumScale: 1,
  userScalable: false,
  viewportFit: 'cover',
}
 
export default async function RootLayout({ children }: { children: React.ReactNode }) {
  const locale = await getRequestLocale()
  const messages = await getMessages(locale)
 
  // Prefetch feature flags server-side so they're instantly available
  // on the client without an extra API request.
  // Session-aware: logged-in users get their per-user overrides merged in.
  const session = await auth()
  const userRole = await resolveUserRole({
    userId: session?.user?.id,
    email: session?.user?.email,
  })
  const queryClient = getQueryClient()
  await Promise.all([
    queryClient.prefetchQuery({
      queryKey: featureFlagKeys.all,
      queryFn: async () => ({ flags: await getAllFlags(session?.user?.id, userRole) }),
      staleTime: 60_000,
    }),
    queryClient.prefetchQuery({
      queryKey: billingKeys.tier(),
      queryFn: async () => {
        const tier =
          userRole === 'guest' ? ('guest' as const) : await getTierForUser(session?.user?.id)
        const limits = TIER_LIMITS[tier]
        return {
          tier,
          limits: {
            maxPracticeStudents:
              limits.maxPracticeStudents === Infinity ? null : limits.maxPracticeStudents,
            maxSessionMinutes: limits.maxSessionMinutes,
            maxSessionsPerWeek:
              limits.maxSessionsPerWeek === Infinity ? null : limits.maxSessionsPerWeek,
            maxOfflineParsingPerMonth: limits.maxOfflineParsingPerMonth,
          },
        }
      },
      staleTime: 60_000,
    }),
  ])
 
  return (
    <html
      lang={locale}
      suppressHydrationWarning
      className={`${inter.variable} ${fraunces.variable}`}
    >
      <body data-deploy-test="argocd-2026-01-31">
        <ClientProviders
          initialLocale={locale}
          initialMessages={messages}
          dehydratedState={dehydrate(queryClient)}
        >
          {children}
        </ClientProviders>
      </body>
    </html>
  )
}