fix: harden upload resilience and contracts

This commit is contained in:
Tom Boullay
2026-05-12 23:49:30 +02:00
parent 101af23418
commit 606df93b69
19 changed files with 479 additions and 159 deletions
+61 -7
View File
@@ -1,6 +1,7 @@
'use client'
import { useEffect, useState } from 'react'
import { Component, useEffect, useState } from 'react'
import type { ComponentType, ReactNode } from 'react'
import type { ModelStats } from './SceneViewer'
interface ModelViewerProps {
@@ -10,10 +11,51 @@ interface ModelViewerProps {
size: string
}
function getPreviewErrorMessage(error: unknown) {
return error instanceof Error ? error.message : 'Erreur preview inconnue'
}
function PreviewFallback({ message }: { message?: string }) {
return (
<div className="flex h-full w-full items-center justify-center px-6 text-center">
<div className="max-w-sm space-y-2">
<p className="text-sm font-medium text-gray-300">Preview 3D indisponible pour ce modele.</p>
<p className="text-xs text-gray-500">
L&apos;upload reste possible. {message ? `Detail technique : ${message}` : ''}
</p>
</div>
</div>
)
}
class PreviewErrorBoundary extends Component<
{ children: ReactNode },
{ message: string | null }
> {
state = { message: null }
static getDerivedStateFromError(error: unknown) {
return { message: getPreviewErrorMessage(error) }
}
componentDidCatch(error: unknown) {
console.error('[ERROR] Preview 3D indisponible', error)
}
render() {
if (this.state.message) {
return <PreviewFallback message={this.state.message} />
}
return this.props.children
}
}
export default function ModelViewer({ url, assetUrls, filename, size }: ModelViewerProps) {
const canPreview = filename.toLowerCase().endsWith('.gltf')
const [stats, setStats] = useState<ModelStats | null>(null)
const [Scene, setScene] = useState<React.ComponentType<{
const [sceneError, setSceneError] = useState<string | null>(null)
const [Scene, setScene] = useState<ComponentType<{
url: string
assetUrls: Record<string, string>
onStatsReady: (stats: ModelStats) => void
@@ -23,13 +65,19 @@ export default function ModelViewer({ url, assetUrls, filename, size }: ModelVie
if (!canPreview) return
let cancel = false
setSceneError(null)
setStats(null)
import('./SceneViewer').then((mod) => {
if (!cancel) setScene(() => mod.default)
})
import('./SceneViewer')
.then((mod) => {
if (!cancel) setScene(() => mod.default)
})
.catch((error: unknown) => {
if (!cancel) setSceneError(getPreviewErrorMessage(error))
})
return () => { cancel = true }
}, [canPreview])
}, [canPreview, url])
if (!canPreview) {
return (
@@ -87,7 +135,13 @@ export default function ModelViewer({ url, assetUrls, filename, size }: ModelVie
</span>
</div>
)}
<Scene url={url} assetUrls={assetUrls} onStatsReady={setStats} />
{sceneError ? (
<PreviewFallback message={sceneError} />
) : (
<PreviewErrorBoundary key={url}>
<Scene url={url} assetUrls={assetUrls} onStatsReady={setStats} />
</PreviewErrorBoundary>
)}
</div>
)
}