Frontend overhaul: manager dashboard restructure, waiter PWA rework, new order drawer and components
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
185
CLAUDE_DESIGN/order-app.jsx
Normal file
185
CLAUDE_DESIGN/order-app.jsx
Normal file
@@ -0,0 +1,185 @@
|
||||
// Main app — menu list in an iOS frame, tap an item to open the drawer
|
||||
|
||||
const { IOSDevice } = window;
|
||||
const { MENU, DIAVOLA, OrderDrawer } = window;
|
||||
|
||||
function MenuItemRow({ item, onTap, badge }) {
|
||||
const [pressed, setPressed] = React.useState(false);
|
||||
return (
|
||||
<div
|
||||
onMouseDown={() => setPressed(true)}
|
||||
onMouseUp={() => setPressed(false)}
|
||||
onMouseLeave={() => setPressed(false)}
|
||||
onClick={onTap}
|
||||
style={{
|
||||
display: 'flex', alignItems: 'center', gap: 14,
|
||||
padding: '14px 16px',
|
||||
background: pressed ? 'var(--ink-100)' : 'white',
|
||||
border: '1px solid var(--ink-100)',
|
||||
borderRadius: 14,
|
||||
cursor: 'pointer',
|
||||
transition: 'background 120ms ease, transform 100ms ease',
|
||||
transform: pressed ? 'scale(0.99)' : 'scale(1)',
|
||||
position: 'relative',
|
||||
}}
|
||||
>
|
||||
<div style={{
|
||||
width: 52, height: 52,
|
||||
borderRadius: 12,
|
||||
background: 'var(--brand-50)',
|
||||
display: 'flex', alignItems: 'center', justifyContent: 'center',
|
||||
fontSize: 28,
|
||||
flexShrink: 0,
|
||||
}}>{item.emoji}</div>
|
||||
<div style={{ flex: 1, minWidth: 0 }}>
|
||||
<div style={{
|
||||
fontSize: 15, fontWeight: 600, color: 'var(--ink-900)',
|
||||
overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap',
|
||||
}}>{item.name}</div>
|
||||
<div style={{
|
||||
fontSize: 13, color: 'var(--ink-500)', marginTop: 2,
|
||||
overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap',
|
||||
}}>{item.desc}</div>
|
||||
</div>
|
||||
<div style={{
|
||||
fontSize: 15, fontWeight: 600, color: 'var(--ink-900)',
|
||||
fontFamily: "'Geist Mono', monospace",
|
||||
flexShrink: 0,
|
||||
}}>€{item.price.toFixed(2)}</div>
|
||||
{badge > 0 && (
|
||||
<div style={{
|
||||
position: 'absolute', top: -6, right: -6,
|
||||
minWidth: 22, height: 22, padding: '0 6px',
|
||||
borderRadius: 11,
|
||||
background: 'var(--brand-500)',
|
||||
color: 'white',
|
||||
fontSize: 12, fontWeight: 700,
|
||||
fontFamily: "'Geist Mono', monospace",
|
||||
display: 'flex', alignItems: 'center', justifyContent: 'center',
|
||||
boxShadow: '0 2px 6px rgba(58, 88, 201, 0.35)',
|
||||
}}>{badge}</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function MenuScreen({ onTapItem, orderCounts }) {
|
||||
const categories = [
|
||||
{ label: 'All', active: true },
|
||||
{ label: 'Pizza' },
|
||||
{ label: 'Pasta' },
|
||||
{ label: 'Desserts' },
|
||||
{ label: 'Drinks' },
|
||||
];
|
||||
|
||||
return (
|
||||
<div style={{
|
||||
height: '100%',
|
||||
display: 'flex', flexDirection: 'column',
|
||||
background: 'var(--bg)',
|
||||
}}>
|
||||
{/* Top bar */}
|
||||
<div style={{
|
||||
padding: '12px 16px 8px',
|
||||
background: 'white',
|
||||
borderBottom: '1px solid var(--ink-100)',
|
||||
}}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 10, marginBottom: 12 }}>
|
||||
<div style={{
|
||||
padding: '6px 10px',
|
||||
background: 'var(--brand-50)',
|
||||
borderRadius: 8,
|
||||
fontSize: 12, fontWeight: 700, color: 'var(--brand-700)',
|
||||
fontFamily: "'Geist Mono', monospace",
|
||||
}}>TABLE B2</div>
|
||||
<div style={{ fontSize: 13, color: 'var(--ink-500)' }}>· 4 guests · Marco</div>
|
||||
<div style={{ flex: 1 }} />
|
||||
<div style={{
|
||||
fontSize: 13, fontWeight: 600, color: 'var(--ink-700)',
|
||||
}}>Cart <span style={{
|
||||
display: 'inline-flex', alignItems: 'center', justifyContent: 'center',
|
||||
minWidth: 20, height: 20, padding: '0 6px',
|
||||
borderRadius: 10, background: 'var(--ink-900)', color: 'white',
|
||||
fontSize: 11, fontWeight: 700, marginLeft: 4,
|
||||
fontFamily: "'Geist Mono', monospace",
|
||||
}}>3</span></div>
|
||||
</div>
|
||||
|
||||
<div style={{ fontSize: 22, fontWeight: 700, color: 'var(--ink-900)', marginBottom: 10 }}>Add item</div>
|
||||
|
||||
<div style={{ display: 'flex', gap: 6, overflowX: 'auto', scrollbarWidth: 'none', paddingBottom: 2 }}>
|
||||
{categories.map(c => (
|
||||
<button key={c.label} style={{
|
||||
padding: '8px 14px',
|
||||
borderRadius: 18,
|
||||
background: c.active ? 'var(--ink-900)' : 'white',
|
||||
border: '1px solid ' + (c.active ? 'var(--ink-900)' : 'var(--ink-200)'),
|
||||
color: c.active ? 'white' : 'var(--ink-700)',
|
||||
fontSize: 14, fontWeight: 600,
|
||||
cursor: 'pointer',
|
||||
fontFamily: 'inherit',
|
||||
whiteSpace: 'nowrap',
|
||||
}}>{c.label}</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Menu list */}
|
||||
<div style={{ flex: 1, overflowY: 'auto', padding: 14, display: 'flex', flexDirection: 'column', gap: 8 }}>
|
||||
{MENU.map(item => (
|
||||
<MenuItemRow
|
||||
key={item.id}
|
||||
item={item}
|
||||
badge={orderCounts[item.id] || 0}
|
||||
onTap={() => onTapItem(item)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Hint banner when drawer not open */}
|
||||
<div style={{
|
||||
padding: '10px 16px',
|
||||
background: 'var(--brand-50)',
|
||||
borderTop: '1px solid var(--brand-200)',
|
||||
textAlign: 'center',
|
||||
fontSize: 13,
|
||||
color: 'var(--brand-700)',
|
||||
fontWeight: 600,
|
||||
}}>Tap "Pizza Diavola" to open the drawer</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function App() {
|
||||
const [drawerOpen, setDrawerOpen] = React.useState(true); // open by default so the design is immediately visible
|
||||
const [orderCounts, setOrderCounts] = React.useState({ margherita: 2, coke: 1 });
|
||||
|
||||
// Tapping a menu item: for this demo only Diavola has a full spec,
|
||||
// so we always feed the drawer the Diavola config but show the tap behavior.
|
||||
const openDrawer = (_item) => setDrawerOpen(true);
|
||||
const closeDrawer = () => setDrawerOpen(false);
|
||||
|
||||
const handleAdd = ({ product, qty }) => {
|
||||
setOrderCounts(prev => ({ ...prev, [product.id]: (prev[product.id] || 0) + qty }));
|
||||
setDrawerOpen(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<div style={{ width: '100%', height: '100%', background: 'var(--bg)' }}>
|
||||
<IOSDevice>
|
||||
<div style={{ position: 'relative', width: '100%', height: '100%', overflow: 'hidden' }}>
|
||||
<MenuScreen onTapItem={openDrawer} orderCounts={orderCounts} />
|
||||
<OrderDrawer
|
||||
product={DIAVOLA}
|
||||
isOpen={drawerOpen}
|
||||
onClose={closeDrawer}
|
||||
onAddToOrder={handleAdd}
|
||||
/>
|
||||
</div>
|
||||
</IOSDevice>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const root = ReactDOM.createRoot(document.getElementById('root'));
|
||||
root.render(<App />);
|
||||
Reference in New Issue
Block a user