104 lines
2.9 KiB
TypeScript
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>
|
|
)
|
|
}
|