project-nomad/admin/inertia/app/app.tsx
Chris Sherwood e8d775dfe4 feat(UI): add Night Ops dark mode with theme toggle
Add a warm charcoal dark mode ("Night Ops") using CSS variable swapping
under [data-theme="dark"]. All 23 desert palette variables are overridden
with dark-mode counterparts, and ~313 generic Tailwind classes (bg-white,
text-gray-*, border-gray-*) are replaced with semantic tokens.

Infrastructure:
- CSS variable overrides in app.css for both themes
- ThemeProvider + useTheme hook (localStorage + KV store sync)
- ThemeToggle component (moon/sun icons, "Night Ops"/"Day Ops" labels)
- FOUC prevention script in inertia_layout.edge
- Toggle placed in StyledSidebar and Footer for access on every page

Color replacements across 50 files:
- bg-white → bg-surface-primary
- bg-gray-50/100 → bg-surface-secondary
- text-gray-900/800 → text-text-primary
- text-gray-600/500 → text-text-secondary/text-text-muted
- border-gray-200/300 → border-border-subtle/border-border-default
- text-desert-white → text-white (fixes invisible text on colored bg)
- Button hover/active states use dedicated btn-green-hover/active vars

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-16 09:17:05 -07:00

56 lines
2.0 KiB
TypeScript

/// <reference path="../../adonisrc.ts" />
/// <reference path="../../config/inertia.ts" />
import '../css/app.css'
import { createRoot } from 'react-dom/client'
import { createInertiaApp } from '@inertiajs/react'
import { resolvePageComponent } from '@adonisjs/inertia/helpers'
import ModalsProvider from '~/providers/ModalProvider'
import { TransmitProvider } from 'react-adonis-transmit'
import { generateUUID } from '~/lib/util'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { ReactQueryDevtools } from '@tanstack/react-query-devtools'
import NotificationsProvider from '~/providers/NotificationProvider'
import { ThemeProvider } from '~/providers/ThemeProvider'
import { UsePageProps } from '../../types/system'
const appName = import.meta.env.VITE_APP_NAME || 'Project N.O.M.A.D.'
const queryClient = new QueryClient()
// Patch the global crypto object for non-HTTPS/localhost contexts
if (!window.crypto?.randomUUID) {
// @ts-ignore
if (!window.crypto) window.crypto = {}
// @ts-ignore
window.crypto.randomUUID = generateUUID
}
createInertiaApp({
progress: { color: '#424420' },
title: (title) => `${title} - ${appName}`,
resolve: (name) => {
return resolvePageComponent(`../pages/${name}.tsx`, import.meta.glob('../pages/**/*.tsx'))
},
setup({ el, App, props }) {
const environment = (props.initialPage.props as unknown as UsePageProps).environment
const showDevtools = ['development', 'staging'].includes(environment)
createRoot(el).render(
<QueryClientProvider client={queryClient}>
<ThemeProvider>
<TransmitProvider baseUrl={window.location.origin} enableLogging={true}>
<NotificationsProvider>
<ModalsProvider>
<App {...props} />
{showDevtools && <ReactQueryDevtools initialIsOpen={false} buttonPosition='bottom-left' />}
</ModalsProvider>
</NotificationsProvider>
</TransmitProvider>
</ThemeProvider>
</QueryClientProvider>
)
},
})