feat: implement GLB file synchronization and add API for listing models
CI/CD / test-and-build (push) Waiting to run
CI/CD / deploy (push) Blocked by required conditions

This commit is contained in:
Najjar\NajjarV02
2026-04-14 10:16:56 +04:00
parent 51671b85b8
commit a501d94645
3 changed files with 89 additions and 1 deletions
+50 -1
View File
@@ -35,6 +35,8 @@ export default function AdminPage() {
const [rowGlbFiles, setRowGlbFiles] = useState<Record<string, File | null>>({});
const [rowGlbUploading, setRowGlbUploading] = useState<Record<string, boolean>>({});
const [rowGlbError, setRowGlbError] = useState<Record<string, string>>({});
const [syncingGlbs, setSyncingGlbs] = useState(false);
const [syncResult, setSyncResult] = useState('');
useEffect(() => { pricingStore.getState().hydrate(); }, []);
@@ -99,6 +101,49 @@ export default function AdminPage() {
}
};
/** Reads all .glb files already on the server and links them to matching personas/items */
const handleSyncGlbs = async () => {
setSyncingGlbs(true);
setSyncResult('');
try {
const res = await fetch('/api/admin/list-models/');
const data = await res.json();
const models: { id: string; modelPath: string; filename: string }[] = data.models ?? [];
let count = 0;
models.forEach(({ id, modelPath }) => {
// Update pricing item if it exists
const item = pricingStore.getState().items.find((i) => i.id === id);
if (item && item.modelPath !== modelPath) {
pricingStore.getState().updateItem(id, { modelPath });
count++;
}
// Update or create persona
const existing = personaStore.getState().personas.find((p) => p.id === id);
if (existing) {
if (existing.modelPath !== modelPath) {
personaStore.getState().updatePersona(id, { modelPath });
count++;
}
} else if (item) {
personaStore.getState().addPersona({
id,
label: item.label,
description: item.label,
colors: { torso: '#3b82f6', legs: '#3b82f6' },
modelPath,
});
count++;
}
});
setSyncResult(count > 0 ? `✓ Synced ${models.length} model(s)` : `✓ Already up to date (${models.length} model(s))`);
setTimeout(() => setSyncResult(''), 3000);
} catch {
setSyncResult('Sync failed');
} finally {
setSyncingGlbs(false);
}
};
const handleAddItem = async () => {
setAddItemError('');
const id = newItem.id.trim().toLowerCase().replace(/_/g, '-').replace(/\s+/g, '-');
@@ -480,7 +525,11 @@ export default function AdminPage() {
</div>
{addItemError && <p style={errorTextStyle}>{addItemError}</p>}
<div style={{ display: 'flex', gap: '0.5rem', marginTop: '0.75rem', justifyContent: 'flex-end' }}>
<div style={{ display: 'flex', gap: '0.5rem', marginTop: '0.75rem', justifyContent: 'flex-end', flexWrap: 'wrap', alignItems: 'center' }}>
{syncResult && <span style={{ fontSize: '0.75rem', color: syncResult.startsWith('✓') ? '#16a34a' : '#dc2626', marginRight: 'auto' }}>{syncResult}</span>}
<button onClick={handleSyncGlbs} disabled={syncingGlbs} style={{ ...ghostBtnStyle, fontSize: '0.75rem' }} title="Link all .glb files in public/models/ to their matching items">
{syncingGlbs ? 'Syncing…' : '⟳ Sync GLBs'}
</button>
<button onClick={() => pricingStore.getState().resetPrices()} style={dangerBtnStyle}>Reset to Defaults</button>
<button onClick={handleAddItem} disabled={addItemUploading} style={secondaryBtnStyle}>
{addItemUploading ? 'Uploading…' : 'Add Item'}