ultisuite-client/components/mobile/mobile-layout-root.tsx
R3D347HR4Y d6d18f911b
Some checks failed
E2E / Playwright e2e (push) Has been cancelled
Lots of stuff and mobile app
2026-06-17 00:13:28 +02:00

104 lines
2.9 KiB
TypeScript

"use client"
import { Component, useEffect, useState, type ReactNode } from "react"
import { ClientThemeShell } from "@/components/suite/client-theme-shell"
import { runThemeInit } from "@/components/theme-init-script"
import { FirstLaunchSplash } from "@/components/first-launch-splash"
import { QueryProvider } from "@/lib/api/query-provider"
import { AuthProvider } from "@/components/auth/auth-provider"
import { SessionGuard } from "@/components/auth/session-guard"
import { NativeBridgeProvider } from "@/components/mobile/native-bridge-provider"
import { NativeAuthGate } from "@/components/mobile/native-auth-gate"
import { NativeShellChrome } from "@/components/mobile/native-shell-chrome"
import { MailToaster } from "@/components/gmail/mail-toaster"
function MobileBootScreen({ hint }: { hint?: string }) {
return (
<div className="fixed inset-0 flex h-dvh flex-col items-center justify-center gap-3 bg-background px-6 text-center">
<img
src="/ultisuite-mark.svg"
alt=""
width={64}
height={64}
className="h-14 w-14 select-none"
aria-hidden
/>
<p className="text-sm text-muted-foreground">UltiMail</p>
{hint ? (
<p className="text-xs text-muted-foreground/80">{hint}</p>
) : null}
</div>
)
}
class MobileErrorBoundary extends Component<
{ children: ReactNode },
{ error: Error | null }
> {
state = { error: null as Error | null }
static getDerivedStateFromError(error: Error) {
return { error }
}
render() {
if (this.state.error) {
return (
<MobileBootScreen
hint={this.state.error.message || "Erreur au démarrage."}
/>
)
}
return this.props.children
}
}
/**
* Tauri mobile root. SSR renders a static boot screen; the interactive app
* mounts on the client so Android WebView never hydrates complex SSR trees.
*/
export function MobileLayoutRoot({ children }: { children: ReactNode }) {
const [clientReady, setClientReady] = useState(false)
const [bootHint, setBootHint] = useState<string | undefined>()
useEffect(() => {
setClientReady(true)
}, [])
useEffect(() => {
if (!clientReady) return
runThemeInit()
}, [clientReady])
useEffect(() => {
if (clientReady) return
const timer = window.setTimeout(() => {
setBootHint("Chargement JavaScript…")
}, 8_000)
return () => window.clearTimeout(timer)
}, [clientReady])
if (!clientReady) {
return <MobileBootScreen hint={bootHint} />
}
return (
<MobileErrorBoundary>
<ClientThemeShell>
<QueryProvider>
<AuthProvider>
<SessionGuard />
<NativeBridgeProvider>
<NativeShellChrome />
<NativeAuthGate>
<FirstLaunchSplash>{children}</FirstLaunchSplash>
</NativeAuthGate>
</NativeBridgeProvider>
</AuthProvider>
<MailToaster />
</QueryProvider>
</ClientThemeShell>
</MobileErrorBoundary>
)
}