booth + mansion: fix loading-state flashes (0.57.0)
- BoothDashboard had no loaded flag, so returning publishers briefly saw the
'Welcome to your booth' new-user onboarding before their classes loaded
- add loaded-gating + a shimmer skeleton; welcome shows only after load
- same fix for the Mansion 'My showcases' list ('No showcases yet' flash)
- completes the loading-skeleton pattern across all data surfaces
(Browse, Booth, Mansion)
- verified live (throttled): skeleton during load, no welcome flash,
welcome/content only after loaded
This commit is contained in:
parent
04128a24d3
commit
fb3c523d4a
@ -4,7 +4,7 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta name="theme-color" content="#0e0a14" />
|
||||
<meta name="description" content="amerc is the agent mercenary tavern — hire, run, publish, and remotely deliver AI agents across software boundaries. No infrastructure, just skills." />
|
||||
<meta name="version" content="0.56.0-routes" />
|
||||
<meta name="version" content="0.57.0-loadstates" />
|
||||
<link rel="icon" href="/favicon.svg" type="image/svg+xml" />
|
||||
<link rel="icon" href="/app-192.png" type="image/png" sizes="192x192" />
|
||||
<link rel="apple-touch-icon" href="/apple-touch-icon.png" />
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "amerc-site",
|
||||
"version": "0.56.0-routes",
|
||||
"version": "0.57.0-loadstates",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
|
||||
@ -223,6 +223,7 @@ export function BoothDashboard({ auth, setRoute }) {
|
||||
const [err, setErr] = useState('');
|
||||
const [form, setForm] = useState({ name: '', tags: '', description: '', kind: 'codex', command: 'codex --yolo', visibility: 'public' });
|
||||
const [newInst, setNewInst] = useState(null);
|
||||
const [loaded, setLoaded] = useState(false);
|
||||
|
||||
const load = useCallback(async () => {
|
||||
setErr('');
|
||||
@ -231,7 +232,7 @@ export function BoothDashboard({ auth, setRoute }) {
|
||||
setClasses(c.classes || []);
|
||||
const all = await Promise.all((c.classes || []).map((x) => api(`/agents/instances?classId=${x.id}`).then((r) => r.instances).catch(() => [])));
|
||||
setInsts(all.flat());
|
||||
} catch (e) { setErr(e.message); }
|
||||
} catch (e) { setErr(e.message); } finally { setLoaded(true); }
|
||||
}, []);
|
||||
useEffect(() => { if (auth.user) load(); }, [auth.user, load]);
|
||||
|
||||
@ -269,7 +270,15 @@ export function BoothDashboard({ auth, setRoute }) {
|
||||
<button className="px-action px-auth-submit" type="submit">Publish class</button>
|
||||
</form>
|
||||
<div className="ap-mine">
|
||||
{!classes.length ? (
|
||||
{!loaded ? (
|
||||
<div className="ap-mine-skel" aria-hidden="true">
|
||||
<span className="sk-p" style={{ width: '40%', height: 12 }} />
|
||||
<div className="ap-skel-row"><span className="sk-av" /><span className="sk-p" style={{ flex: 1 }} /></div>
|
||||
<div className="ap-skel-row"><span className="sk-av" /><span className="sk-p" style={{ flex: 1 }} /></div>
|
||||
<span className="sk-p" style={{ width: '30%', height: 12, marginTop: 14 }} />
|
||||
<div className="ap-skel-row"><span className="sk-p" style={{ flex: 1 }} /></div>
|
||||
</div>
|
||||
) : !classes.length ? (
|
||||
<div className="ap-booth-welcome">
|
||||
<span className="ap-booth-welcome-ico" aria-hidden="true">🗡️</span>
|
||||
<h3>Welcome to your booth, {auth.user.handle}.</h3>
|
||||
|
||||
@ -68,9 +68,10 @@ function ShowcaseRoom({ loggedIn, handle, setRoute }) {
|
||||
const [name, setName] = useState('');
|
||||
const [created, setCreated] = useState(null);
|
||||
const [err, setErr] = useState('');
|
||||
const [loaded, setLoaded] = useState(false);
|
||||
const load = useCallback(async () => {
|
||||
if (!loggedIn) return;
|
||||
try { const d = await api('/showcase'); setList(d.showcases || []); } catch (e) { setErr(e.message); }
|
||||
try { const d = await api('/showcase'); setList(d.showcases || []); } catch (e) { setErr(e.message); } finally { setLoaded(true); }
|
||||
}, [loggedIn]);
|
||||
useEffect(() => { load(); }, [load]);
|
||||
const register = async (e) => {
|
||||
@ -112,6 +113,13 @@ function ShowcaseRoom({ loggedIn, handle, setRoute }) {
|
||||
)}
|
||||
<div className="mn-mine">
|
||||
<h4 className="ap-h4">My showcases</h4>
|
||||
{!loaded ? (
|
||||
<div className="mn-skel" aria-hidden="true">
|
||||
<div className="ap-skel-row"><span className="sk-p" style={{ flex: 1 }} /></div>
|
||||
<div className="ap-skel-row"><span className="sk-p" style={{ flex: 1 }} /></div>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
{list.map((s) => (
|
||||
<div key={s.id} className="mn-row">
|
||||
<a href={`https://${s.host}/`} target="_blank" rel="noreferrer">{s.host}</a>
|
||||
@ -120,6 +128,8 @@ function ShowcaseRoom({ loggedIn, handle, setRoute }) {
|
||||
</div>
|
||||
))}
|
||||
{!list.length && <p className="ap-hint">No showcases yet — register one above.</p>}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
@ -7,7 +7,7 @@ import Account from './Account.jsx';
|
||||
import TavernGame from './TavernGame.jsx';
|
||||
import { api } from './api.js';
|
||||
|
||||
const RELEASE = '0.56.0-routes';
|
||||
const RELEASE = '0.57.0-loadstates';
|
||||
|
||||
const NAV = [
|
||||
{ id: 'home', label: 'Tavern' },
|
||||
|
||||
@ -1407,3 +1407,9 @@ button {
|
||||
.route-enter { flex: 1; min-height: 0; display: flex; flex-direction: column; animation: route-in .34s cubic-bezier(.2,.7,.2,1); }
|
||||
@keyframes route-in { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: none; } }
|
||||
@media (prefers-reduced-motion: reduce) { .route-enter { animation: none; } }
|
||||
|
||||
/* booth + mansion loading skeletons (0.57) */
|
||||
.ap-mine-skel { display: flex; flex-direction: column; gap: 12px; }
|
||||
.ap-skel-row { display: flex; align-items: center; gap: 11px; }
|
||||
.mn-skel { display: flex; flex-direction: column; gap: 10px; margin-top: 8px; }
|
||||
.mn-skel .ap-skel-row .sk-p { height: 12px; }
|
||||
|
||||
Loading…
Reference in New Issue
Block a user