Skip to content

Commit a79dc10

Browse files
Merge pull request #153 from geturbackend/v0/dashboard-loading-slow-4a22ea74
fix: reduce loading latency on create-collection page
2 parents 6df8624 + 54f0a25 commit a79dc10

3 files changed

Lines changed: 64 additions & 27 deletions

File tree

apps/web-dashboard/src/pages/CreateCollection.jsx

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ function createEmptyField() {
2121
}
2222

2323
// FUNCTION - FIELD ROW COMPONENT
24-
function FieldRow({ field, index, depth, collections, onChange, onRemove }) {
24+
function FieldRow({ field, index, depth, collections, collectionsLoading, collectionsError, onChange, onRemove }) {
2525
const [expanded, setExpanded] = useState(true);
2626

2727
const handleChange = (prop, value) => {
@@ -283,11 +283,12 @@ function FieldRow({ field, index, depth, collections, onChange, onRemove }) {
283283
</span>
284284
<select
285285
value={field.ref || ''}
286+
disabled={collectionsLoading || collectionsError}
286287
onChange={(e) => handleChange('ref', e.target.value)}
287288
className="input-field"
288289
style={{ flex: 1, fontSize: '0.85rem', padding: '4px 8px', background: 'rgba(0,0,0,0.2)', border: '1px solid var(--color-border)', borderRadius: '4px' }}
289290
>
290-
<option value="">Select collection...</option>
291+
<option value="">{collectionsLoading ? 'Loading collections…' : collectionsError ? 'Failed to load' : 'Select collection...'}</option>
291292
{collections.map(c => (
292293
<option key={c.name} value={c.name}>{c.name}</option>
293294
))}
@@ -320,6 +321,8 @@ function FieldRow({ field, index, depth, collections, onChange, onRemove }) {
320321
index={subIdx}
321322
depth={depth + 1}
322323
collections={collections}
324+
collectionsLoading={collectionsLoading}
325+
collectionsError={collectionsError}
323326
onChange={handleSubFieldChange}
324327
onRemove={removeSubField}
325328
/>
@@ -370,6 +373,8 @@ function FieldRow({ field, index, depth, collections, onChange, onRemove }) {
370373
index={subIdx}
371374
depth={depth + 1}
372375
collections={collections}
376+
collectionsLoading={collectionsLoading}
377+
collectionsError={collectionsError}
373378
onChange={handleItemSubFieldChange}
374379
onRemove={removeItemSubField}
375380
/>
@@ -384,11 +389,12 @@ function FieldRow({ field, index, depth, collections, onChange, onRemove }) {
384389
</span>
385390
<select
386391
value={field.items?.ref || ''}
392+
disabled={collectionsLoading || collectionsError}
387393
onChange={(e) => handleItemsChange('ref', e.target.value)}
388394
className="input-field"
389395
style={{ flex: 1, fontSize: '0.85rem', padding: '4px 8px', background: 'rgba(0,0,0,0.2)', border: '1px solid var(--color-border)', borderRadius: '4px' }}
390396
>
391-
<option value="">Select collection...</option>
397+
<option value="">{collectionsLoading ? 'Loading collections…' : collectionsError ? 'Failed to load' : 'Select collection...'}</option>
392398
{collections.map(c => (
393399
<option key={c.name} value={c.name}>{c.name}</option>
394400
))}
@@ -454,15 +460,31 @@ function CreateCollection() {
454460
const [fields, setFields] = useState(getInitialFields());
455461
const [loading, setLoading] = useState(false);
456462
const [collections, setCollections] = useState([]);
463+
const [collectionsLoading, setCollectionsLoading] = useState(true);
464+
const [collectionsError, setCollectionsError] = useState(null);
457465

458-
// Fetch existing collections for Ref picker
466+
// Fetch existing collections for Ref picker — runs immediately on mount
467+
// so it fires in parallel with any other in-flight requests.
459468
useEffect(() => {
460469
let isMounted = true;
461470
const fetchCollections = async () => {
471+
if (isMounted) {
472+
setCollectionsLoading(true);
473+
setCollections([]);
474+
setCollectionsError(null);
475+
}
462476
try {
463477
const res = await api.get(`/api/projects/${projectId}`);
464478
if (isMounted) setCollections(res.data.collections || []);
465-
} catch { /* ignore */ }
479+
} catch (err) {
480+
console.error('Failed to fetch collections for Ref picker:', err);
481+
if (isMounted) {
482+
setCollectionsError('Failed to load collections');
483+
toast.error('Failed to load collections for references');
484+
}
485+
} finally {
486+
if (isMounted) setCollectionsLoading(false);
487+
}
466488
};
467489
fetchCollections();
468490
return () => { isMounted = false; };
@@ -598,6 +620,8 @@ function CreateCollection() {
598620
index={index}
599621
depth={1}
600622
collections={collections}
623+
collectionsLoading={collectionsLoading}
624+
collectionsError={collectionsError}
601625
onChange={handleFieldChange}
602626
onRemove={removeField}
603627
/>

apps/web-dashboard/src/utils/api.js

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@ const api = axios.create({
77
});
88

99
let csrfToken = null;
10+
// Eagerly start fetching the CSRF token as soon as this module loads so the
11+
// token is already available by the time the user submits any form.
12+
let csrfTokenPromise = null;
1013

1114
const fetchCsrfToken = async () => {
1215
try {
@@ -15,16 +18,26 @@ const fetchCsrfToken = async () => {
1518
return csrfToken;
1619
} catch (err) {
1720
console.error("Failed to fetch CSRF token:", err);
21+
// Clear the promise so subsequent requests will retry instead of reusing a failed result.
22+
csrfTokenPromise = null;
1823
return null;
1924
}
2025
};
2126

27+
// Kick off the fetch immediately — reuse the same promise to avoid duplicate requests.
28+
csrfTokenPromise = fetchCsrfToken();
29+
2230
api.interceptors.request.use(async (config) => {
23-
const method = config.method.toLowerCase();
31+
// Guard against undefined method (defaults to 'get')
32+
const method = (config.method || 'get').toLowerCase();
2433

2534
if (['post', 'put', 'delete', 'patch'].includes(method)) {
2635
if (!csrfToken) {
27-
csrfToken = await fetchCsrfToken();
36+
// If the eager fetch failed (csrfTokenPromise is null), trigger a fresh fetch.
37+
if (!csrfTokenPromise) {
38+
csrfTokenPromise = fetchCsrfToken();
39+
}
40+
csrfToken = await csrfTokenPromise;
2841
}
2942
if (csrfToken) {
3043
config.headers['X-CSRF-Token'] = csrfToken;

package-lock.json

Lines changed: 20 additions & 20 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)