Files
upload-gltf/components/UploadZone.tsx
T
Tom Boullay 78f4aa83e0 refactor: full codebase audit — extract modules, fix type safety, clean dead code
- Extract API helpers from UploadZone into lib/upload-api.ts (FormData builder, checkFolderDiffs, uploadDrive, uploadGit)
- Extract upload orchestration into hooks/useUploadOrchestrator.ts (UploadZone: 489 → 162 lines)
- Extract file diff classification into lib/diff-files.ts (from git route)
- Extract shared SVG icons into components/ui/icons.tsx (7 icons, 0 duplication)
- Extract shared modal wrapper into components/ui/Modal.tsx + ModalActions
- Extract DriveStatusLine sub-component from FolderCard
- Fix checkFolderDiffs silently swallowing auth/network errors (now throws)
- Fix type safety: remove as never casts, add isHttpError type guard, use discriminated union for validateFolder
- Fix nextcloud: cache getConfig, add max bound to findNextVersion, optimize mkdirRecursive (skip PROPFIND)
- Fix drive route: remove req.clone(), extend parseMultiUpload to return extra fields
- Fix commit message: model shown as unchanged with ↔️ on updates (not falsely marked as modified)
- Clean dead code: unused folderExists import, FileStatus/DriveStatus exports, ParsedFile.textureName, getConfig basePath
- Add security headers in next.config.ts (HSTS, X-Content-Type-Options, X-Frame-Options, etc.)
- Update README with new project structure
2026-04-14 17:19:10 +02:00

163 lines
4.0 KiB
TypeScript

'use client'
import type { FolderEntry } from '@/lib/client-types'
import { useSecret } from '@/hooks/useSecret'
import { useFolderEntries } from '@/hooks/useFolderEntries'
import { useUploadOrchestrator } from '@/hooks/useUploadOrchestrator'
import SecretInput from './upload/SecretInput'
import DestinationPicker from './upload/DestinationPicker'
import FolderDropzone from './upload/FolderDropzone'
import FolderCard from './upload/FolderCard'
import ActionButtons from './upload/ActionButtons'
import OverwriteConfirmModal from './upload/OverwriteConfirmModal'
import NoChangesModal from './upload/NoChangesModal'
import DriveErrorModal from './upload/DriveErrorModal'
export default function UploadZone() {
const {
secret,
secretError,
secretVisible,
isSecretEmpty,
setSecretError,
handleSecretChange,
toggleSecretVisible,
} = useSecret()
const {
entries,
setEntries,
updateEntry,
removeEntry,
resetEntries,
allDone,
hasErrors,
} = useFolderEntries()
const {
isUploading,
globalError,
setGlobalError,
destination,
setDestination,
overwriteConfirm,
setOverwriteConfirm,
noChangesFolder,
setNoChangesFolder,
driveError,
handleUpload,
handleDriveContinue,
handleDriveCancel,
handleCancel,
handleReset,
proceedUpload,
} = useUploadOrchestrator({
secret,
setSecretError,
entries,
updateEntry,
resetEntries,
})
const handleFolderSelected = (entry: FolderEntry) => {
setGlobalError(null)
setEntries([entry])
}
const handleToggleViewer = (index: number) => {
const entry = entries[index]
if (entry?.modelUrl) {
updateEntry(index, { viewerOpen: !entry.viewerOpen })
}
}
const hasPendingOrErrors = entries.some((f) => f.status === 'pending' || f.status === 'error')
return (
<div className="w-full max-w-2xl space-y-4">
<SecretInput
secret={secret}
secretVisible={secretVisible}
secretError={secretError}
disabled={isUploading}
onChange={handleSecretChange}
onToggleVisible={toggleSecretVisible}
/>
<DestinationPicker
destination={destination}
disabled={isUploading}
onChange={setDestination}
/>
{entries.length === 0 && (
<FolderDropzone
isUploading={isUploading}
onFolderSelected={handleFolderSelected}
onError={setGlobalError}
/>
)}
{globalError && (
<p className="text-xs text-red-400 text-center">{globalError}</p>
)}
{entries.length > 0 && (
<div className="space-y-2">
{entries.map((entry, i) => (
<FolderCard
key={i}
entry={entry}
index={i}
onToggleViewer={handleToggleViewer}
onRemove={removeEntry}
/>
))}
</div>
)}
<ActionButtons
isUploading={isUploading}
isSecretEmpty={isSecretEmpty}
noDestination={!destination}
hasPendingOrErrors={hasPendingOrErrors}
allDone={allDone}
hasErrors={hasErrors}
onUpload={handleUpload}
onCancel={handleCancel}
onReset={handleReset}
/>
{overwriteConfirm && (
<OverwriteConfirmModal
destination={destination!}
folderName={overwriteConfirm.folderName}
diffs={overwriteConfirm.diffs}
onCancel={() => setOverwriteConfirm(null)}
onConfirm={proceedUpload}
/>
)}
{noChangesFolder && (
<NoChangesModal
destination={destination!}
folderName={noChangesFolder}
onCancel={() => {
setNoChangesFolder(null)
handleReset()
}}
onModify={() => setNoChangesFolder(null)}
/>
)}
{driveError && (
<DriveErrorModal
error={driveError.error}
onCancel={handleDriveCancel}
onContinue={handleDriveContinue}
/>
)}
</div>
)
}