| 1 | import { Component, Suspense, lazy, useState, type ComponentType, type ReactNode } from "react"; |
| 2 | import { useT } from "../lib/i18n"; |
| 3 | class SurfaceBoundary extends Component<{ fallback: ReactNode; children: ReactNode }, { failed: boolean }> { |
| 4 | state = { failed: false }; |
| 5 | static getDerivedStateFromError() { return { failed: true }; } |
| 6 | render() { return this.state.failed ? this.props.fallback : this.props.children; } |
| 7 | } |
| 8 | /** Recreate the lazy loader on retry; a rejected React.lazy promise is cached. */ |
| 9 | export function ManagementSurface<P extends object>({ loader, surfaceProps, active, onBack }: { |
| 10 | loader: () => Promise<{ default: ComponentType<P> }>; surfaceProps: P; active: boolean; onBack: () => void; |
| 11 | }) { |
| 12 | const t = useT(); |
| 13 | const [attempt, setAttempt] = useState(() => ({ key: 0, View: lazy(loader) })); |
| 14 | const { View } = attempt; |
| 15 | const fallback = (failed: boolean) => active ? <section aria-label={t("settings.title")} style={{ position: "fixed", inset: 0, zIndex: "var(--z-modal)", background: "var(--bg-soft)", padding: "60px 24px", color: "var(--fg)" }}> |
| 16 | <button className="btn" onClick={onBack}>{t("settings.backToWorkspace")}</button> |
| 17 | <p role={failed ? "alert" : "status"}>{t(failed ? "settings.loadFailed" : "common.loading")}</p> |
| 18 | {failed && <button className="btn" onClick={() => setAttempt((value) => ({ key: value.key + 1, View: lazy(loader) }))}>{t("common.retry")}</button>} |
| 19 | </section> : null; |
| 20 | return <SurfaceBoundary key={attempt.key} fallback={fallback(true)}><Suspense fallback={fallback(false)}><View {...surfaceProps} /></Suspense></SurfaceBoundary>; |
| 21 | } |
| 22 |