update: add gestion erreur si dossier est existant

This commit is contained in:
Tom Boullay
2026-04-14 13:26:49 +02:00
parent c795082ca4
commit 2b3d02e489
4 changed files with 287 additions and 27 deletions
+125
View File
@@ -28,6 +28,15 @@ interface FolderEntry {
const REQUIRED_TEXTURES = ['roughness', 'normal', 'metalness', 'color', 'displace']
const TEXTURE_EXTENSIONS = ['.png', '.jpg', '.jpeg', '.webp']
const DESTINATIONS = [
{ value: 'farm', label: 'Farm' },
{ value: 'map', label: 'Map' },
{ value: 'powergrid', label: 'Powergrid' },
{ value: 'workshop', label: 'Workshop' },
{ value: 'general', label: 'General' },
{ value: 'environment', label: 'Environment' },
] as const
function formatBytes(bytes: number): string {
if (bytes === 0) return '0 B'
const k = 1024
@@ -79,13 +88,35 @@ function validateFolder(files: File[]): { model?: File; textures: TextureFile[];
return result
}
async function checkFolderExists(
folderName: string,
destination: string,
secret: string,
): Promise<{ exists: boolean; files: string[] }> {
try {
const params = new URLSearchParams({ folderName, destination })
const res = await fetch(`/api/upload?${params}`, {
headers: { 'x-upload-secret': secret.trim() },
})
const data = await res.json()
if (data.success && data.exists) {
return { exists: true, files: data.files || [] }
}
return { exists: false, files: [] }
} catch {
return { exists: false, files: [] }
}
}
async function uploadFolder(
folder: FolderEntry,
secret: string,
destination: string,
onProgress: (pct: number) => void
): Promise<{ success: boolean; filename?: string; error?: string }> {
const formData = new FormData()
formData.append('folderName', folder.folderName)
formData.append('destination', destination)
// Model file
formData.append('files', folder.modelFile)
@@ -130,7 +161,9 @@ export default function UploadZone() {
const [secretVisible, setSecretVisible] = useState(false)
const [globalError, setGlobalError] = useState<string | null>(null)
const [secretError, setSecretError] = useState<string | null>(null)
const [destination, setDestination] = useState<typeof DESTINATIONS[number]['value']>(DESTINATIONS[0].value)
const [abortController, setAbortController] = useState<AbortController | null>(null)
const [overwriteConfirm, setOverwriteConfirm] = useState<{ folderName: string; files: string[] } | null>(null)
const isSecretEmpty = !secret.trim()
@@ -146,6 +179,21 @@ export default function UploadZone() {
if (files.length === 0) return
setSecretError(null)
setGlobalError(null)
// Check if folder already exists on remote
const folder = files[0]
const check = await checkFolderExists(folder.folderName, destination, secret)
if (check.exists) {
setOverwriteConfirm({ folderName: folder.folderName, files: check.files })
return
}
await proceedUpload()
}
const proceedUpload = async () => {
setOverwriteConfirm(null)
setIsUploading(true)
setGlobalError(null)
@@ -157,6 +205,7 @@ export default function UploadZone() {
const result = await uploadFolder(
files[i],
secret,
destination,
(pct) => updateFile(i, { progress: pct })
)
@@ -237,6 +286,28 @@ export default function UploadZone() {
)}
</div>
<div className="space-y-1.5">
<label className="block text-sm font-medium text-gray-300">Destination</label>
<div className="grid grid-cols-3 gap-2">
{DESTINATIONS.map((dest) => (
<button
key={dest.value}
type="button"
onClick={() => setDestination(dest.value)}
disabled={isUploading}
className={`px-3 py-2 rounded-xl text-sm font-medium transition-all duration-150 border
disabled:opacity-50 disabled:cursor-not-allowed
${destination === dest.value
? 'bg-white text-[#000000] border-white'
: 'bg-black-800 text-gray-400 border-white/20 hover:border-white/40 hover:text-gray-200'
}`}
>
{dest.label}
</button>
))}
</div>
</div>
<input
type="file"
id="folder-input"
@@ -448,6 +519,60 @@ export default function UploadZone() {
</button>
)}
</div>
{overwriteConfirm && (
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/60 backdrop-blur-sm">
<div className="bg-black-900 border border-white/20 rounded-2xl p-6 max-w-md w-full mx-4 space-y-4">
<div className="flex items-center gap-3">
<div className="w-10 h-10 rounded-full bg-yellow-900/30 flex items-center justify-center shrink-0">
<svg className="w-5 h-5 text-yellow-400" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
<path strokeLinecap="round" strokeLinejoin="round" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
</svg>
</div>
<div>
<h3 className="text-sm font-semibold text-gray-100">Dossier deja existant</h3>
<p className="text-xs text-gray-400 mt-0.5">
<span className="font-mono text-yellow-400">{destination}/{overwriteConfirm.folderName}</span> existe deja sur le repo.
</p>
</div>
</div>
{overwriteConfirm.files.length > 0 && (
<div className="bg-black-800 border border-white/10 rounded-xl p-3 max-h-32 overflow-y-auto">
<p className="text-xs text-gray-500 mb-1.5">Fichiers existants qui seront remplaces :</p>
<ul className="space-y-0.5">
{overwriteConfirm.files.map((f) => (
<li key={f} className="text-xs text-gray-400 font-mono">{f}</li>
))}
</ul>
</div>
)}
<p className="text-xs text-gray-400">
Les anciens fichiers seront supprimes et remplaces par les nouveaux. Cette action est irreversible.
</p>
<div className="flex gap-3">
<button
onClick={() => setOverwriteConfirm(null)}
className="flex-1 bg-black-700 text-gray-300 font-medium text-sm
py-2.5 px-4 rounded-xl border border-white/10 transition-colors duration-150
hover:bg-black-600"
>
Annuler
</button>
<button
onClick={proceedUpload}
className="flex-1 bg-yellow-600 text-[#000000] font-medium text-sm
py-2.5 px-4 rounded-xl transition-colors duration-150
hover:bg-yellow-500"
>
Remplacer
</button>
</div>
</div>
</div>
)}
</div>
)
}