Files
wiggleverse-ecomm-content/ui/designs/ecomm-login-and-create-storefront-designs/project/wf-admin.jsx
T
ben.stull afe36fd347 add ui design ./ui/designs/ecomm-login-and-create-storefront-designs/
Claude Design export for SD-0001 (login + create-storefront surfaces): wf-*/hf-*
screens, design-system bundle, offline preview. First artifact in the ui/designs/
collection (ecomm#8). Also ignore .DS_Store.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-10 21:53:43 -07:00

129 lines
5.9 KiB
React

// wf-admin.jsx — Screen 4: Admin shell (PUC-8, PUC-9). A/B + states.
const { Screen: AScreen, Wordmark: AWordmark, TopBar: ATopBar, AccountChip: AAccountChip,
Banner: ABanner, EmptyAdmin: AEmptyAdmin, StateBlock: AStateBlock, WF_T: AT, WF_DS: ADS } = window;
const { Eyebrow: AEyebrow } = ADS;
const STORE = 'Studio Fern';
// storefront identity lockup — the admin's anchor (left of top bar in A)
function StoreId({ size = 17 }) {
return (
<span style={{ display: 'inline-flex', alignItems: 'center', gap: 11 }}>
<img src="assets/mark-tile.svg" width={size + 6} height={size + 6} style={{ borderRadius: 6, display: 'block' }} alt="" />
<span style={{ display: 'inline-flex', flexDirection: 'column', lineHeight: 1.1 }}>
<span style={{ fontFamily: AT.disp, fontWeight: 700, fontSize: size, color: AT.star, letterSpacing: '-0.01em' }}>{STORE}</span>
<span style={{ fontFamily: AT.body, fontSize: 11, color: AT.mute }}>ecomm storefront</span>
</span>
</span>
);
}
// ── Direction A — top bar + centered honest-empty ──────────────────────────
function AdminA({ device = 'desktop' }) {
const desktop = device === 'desktop';
return (
<AScreen>
<ATopBar pad={desktop ? '0 32px' : '0 18px'} left={<StoreId size={desktop ? 17 : 15} />}
right={desktop ? <AAccountChip /> : <button style={signOutBtn}>Sign out</button>} />
<AEmptyAdmin storefront={STORE} compact={!desktop} />
</AScreen>
);
}
// ── Direction B — durable app shell (sidebar + content) ────────────────────
function AdminB({ device = 'desktop' }) {
const desktop = device === 'desktop';
if (!desktop) {
return (
<AScreen>
<ATopBar pad="0 18px" left={<StoreId size={15} />} right={<button style={signOutBtn}>Sign out</button>} />
<div style={{ flex: '0 0 auto', padding: '12px 18px', borderBottom: `1px solid ${AT.hair}`, display: 'flex', gap: 8 }}>
<NavItem active>Home</NavItem>
</div>
<AEmptyAdmin storefront={STORE} compact />
</AScreen>
);
}
return (
<AScreen style={{ flexDirection: 'row' }}>
<Sidebar />
<div style={{ flex: 1, display: 'flex', flexDirection: 'column' }}>
<header style={{ flex: '0 0 auto', height: 60, padding: '0 36px', display: 'flex', alignItems: 'center', borderBottom: `1px solid ${AT.hair}` }}>
<span style={{ fontFamily: AT.disp, fontWeight: 500, fontSize: 15, color: AT.soft }}>Home</span>
</header>
<div style={{ flex: 1, display: 'flex' }}>
<AEmptyAdmin storefront={STORE} />
</div>
</div>
</AScreen>
);
}
function Sidebar() {
return (
<aside style={{ flex: '0 0 258px', background: 'var(--wv-night)', borderRight: `1px solid ${AT.hair}`, display: 'flex', flexDirection: 'column', padding: '22px 16px' }}>
<div style={{ padding: '0 8px 18px' }}><StoreId /></div>
<div style={{ borderTop: `1px solid ${AT.hair}`, margin: '0 8px 16px' }} />
<nav style={{ display: 'flex', flexDirection: 'column', gap: 4 }}>
<NavItem active full>Home</NavItem>
</nav>
<div style={{ flex: 1 }} />
<p style={{ fontFamily: AT.body, fontSize: 11.5, lineHeight: 1.5, color: 'var(--wv-starlight-55)', padding: '0 10px', margin: '0 0 16px' }}>
Catalog, orders &amp; settings join this rail as ecomm grows. Nothing is shown here before it exists.
</p>
<div style={{ borderTop: `1px solid ${AT.hair}`, padding: '14px 10px 2px', display: 'flex', flexDirection: 'column', gap: 8 }}>
<span style={{ fontFamily: AT.body, fontSize: 13, color: AT.soft }}>mara@studiofern.com</span>
<button style={{ ...signOutBtn, fontSize: 13, textAlign: 'left' }}>Sign out</button>
</div>
</aside>
);
}
function NavItem({ children, active, full }) {
return (
<span style={{
display: 'inline-flex', alignItems: 'center', gap: 9, padding: full ? '9px 12px' : '7px 13px',
borderRadius: 9, fontFamily: AT.disp, fontWeight: 500, fontSize: 14,
color: active ? AT.star : AT.mute,
background: active ? 'var(--wv-lilac-12)' : 'transparent',
border: full ? `1px solid ${active ? 'var(--wv-lilac-18)' : 'transparent'}` : 'none',
}}>
<span aria-hidden="true" style={{ width: 6, height: 6, borderRadius: 3, background: active ? AT.lilac : 'transparent' }} />
{children}
</span>
);
}
const signOutBtn = { fontFamily: AT.disp, fontWeight: 500, fontSize: 14, color: AT.star, background: 'transparent', border: 'none', cursor: 'pointer', padding: 0 };
// ── States stack ───────────────────────────────────────────────────────────
function AdminStates() {
return (
<div style={{ display: 'flex', flexDirection: 'column', gap: 22 }}>
<AStateBlock label="Loading" note="minimal skeleton while /me resolves">
<div style={{ display: 'flex', flexDirection: 'column', gap: 12 }}>
<Skel w={90} h={12} />
<Skel w={200} h={28} />
<Skel w={280} h={12} />
<Skel w={240} h={12} />
</div>
</AStateBlock>
<AStateBlock label="Session expired" note="redirect to sign-in">
<ABanner tone="info" title="Your session ended">
For your security you've been signed out. <a href="#" style={{ color: AT.lilac, textDecoration: 'none' }}>Sign in again →</a>
</ABanner>
</AStateBlock>
<AStateBlock label="Sign out" note="account menu open (PUC-9)">
<div style={{ position: 'relative', height: 96 }}>
<AAccountChip menu />
</div>
</AStateBlock>
</div>
);
}
function Skel({ w, h }) {
return <div style={{ width: w, height: h, borderRadius: 6, background: 'rgba(237,234,255,.08)' }} />;
}
Object.assign(window, { AdminA, AdminB, AdminStates });