diff --git a/dev.db b/dev.db new file mode 100644 index 0000000..e69de29 diff --git a/public/models/robot-doctor.glb b/public/models/robot-doctor.glb index 68772f7..ab6fd22 100644 Binary files a/public/models/robot-doctor.glb and b/public/models/robot-doctor.glb differ diff --git a/public/models/security-guard.glb b/public/models/security-guard.glb index 286b3f6..961c857 100644 Binary files a/public/models/security-guard.glb and b/public/models/security-guard.glb differ diff --git a/src/app/admin/page.tsx b/src/app/admin/page.tsx index d0f3387..fca186e 100644 --- a/src/app/admin/page.tsx +++ b/src/app/admin/page.tsx @@ -37,9 +37,36 @@ export default function AdminPage() { const [rowGlbError, setRowGlbError] = useState>({}); const [syncingGlbs, setSyncingGlbs] = useState(false); const [syncResult, setSyncResult] = useState(''); + // Local state for immediate badge update after upload (bypasses any store re-render delay) + const [uploadedPaths, setUploadedPaths] = useState>({}); useEffect(() => { pricingStore.getState().hydrate(); }, []); + // Auto-sync GLB files from server after hydration — ensures modelPaths are always + // populated regardless of which browser/session last uploaded the files. + useEffect(() => { + if (!isPricingHydrated) return; + fetch('/api/admin/list-models/') + .then((r) => r.json()) + .then((data) => { + const models: { id: string; modelPath: string }[] = data.models ?? []; + models.forEach(({ id, modelPath }) => { + const item = pricingStore.getState().items.find((i) => i.id === id); + // Only link if the item exists and has no modelPath yet (don't overwrite versioned upload URLs) + if (item && !item.modelPath) { + pricingStore.getState().updateItem(id, { modelPath }); + const existing = personaStore.getState().personas.find((p) => p.id === id); + if (existing) { + personaStore.getState().updatePersona(id, { modelPath }); + } else { + personaStore.getState().addPersona({ id, label: item.label, description: item.label, colors: { torso: '#3b82f6', legs: '#3b82f6' }, modelPath }); + } + } + }); + }) + .catch(() => {}); // silent — non-critical + }, [isPricingHydrated]); + useEffect(() => { const priceMap: Record = {}; const labelMap: Record = {}; @@ -93,6 +120,8 @@ export default function AdminPage() { modelPath, }); } + // Immediately reflect the new path in the badge regardless of store re-render timing + setUploadedPaths((p) => ({ ...p, [itemId]: modelPath })); setRowGlbFiles((p) => ({ ...p, [itemId]: null })); } catch (err) { setRowGlbError((p) => ({ ...p, [itemId]: err instanceof Error ? err.message : 'Upload failed' })); @@ -432,12 +461,12 @@ export default function AdminPage() { {/* 3D Model column */}
- {item.modelPath ? ( + {(uploadedPaths[item.id] || item.modelPath) ? ( - ✓ {item.modelPath.split('/').pop()} + ✓ {(uploadedPaths[item.id] || item.modelPath)!.split('/').pop()?.split('?')[0]} ) : null}