Stream linining

This commit is contained in:
Zeeshaun
2026-05-05 18:41:34 +00:00
parent 36a484bc3e
commit 4c95327d94
2 changed files with 74 additions and 38 deletions
+44 -38
View File
@@ -12,6 +12,11 @@ import logo from "../../assets/ranaze-logo.png";
const queueStatuses = ["new", "making", "ready"]; const queueStatuses = ["new", "making", "ready"];
const orderIdleTimeoutMs = 90_000; const orderIdleTimeoutMs = 90_000;
const tabLabels = {
order: "Order",
queue: "Order Status",
menu: "Menu Admin"
};
const initialBuilder = (settings) => ({ const initialBuilder = (settings) => ({
size: settings.sizeOptions[0]?.name ?? "Regular", size: settings.sizeOptions[0]?.name ?? "Regular",
@@ -132,22 +137,22 @@ export default function App() {
const selectedDrink = menu.find((item) => item._id === selectedDrinkId) || null; const selectedDrink = menu.find((item) => item._id === selectedDrinkId) || null;
const modeDetails = { const modeDetails = {
premade: { premade: {
stepLabel: "Quick pick", stepLabel: "House favorite",
title: "Pick a house favorite", title: selectedDrink?.name || "Pick a house favorite",
description: "Choose a ready-to-go drink and add it to the ticket in one tap.", description: "Choose one of our ready-to-enjoy drinks and add it in one tap.",
buttonLabel: "Add this drink" buttonLabel: "Add to my order"
}, },
simple: { simple: {
stepLabel: "Quick customize", stepLabel: "Quick customize",
title: selectedDrink?.name || "Choose a drink", title: selectedDrink?.name || "Choose your drink",
description: "Make a few easy choices and keep the line moving.", description: "Pick your drink, then make a few simple choices like size and sweetness.",
buttonLabel: "Add customized drink" buttonLabel: "Add to my order"
}, },
detailed: { detailed: {
stepLabel: "Full customize", stepLabel: "Build it your way",
title: selectedDrink?.name || "Choose a drink", title: selectedDrink?.name || "Build your drink",
description: "Adjust every detail, including toppings and special instructions.", description: "Customize every detail, including ice, toppings, and special requests.",
buttonLabel: "Add fully customized drink" buttonLabel: "Add to my order"
} }
}; };
const activeMode = orderMode ? modeDetails[orderMode] : null; const activeMode = orderMode ? modeDetails[orderMode] : null;
@@ -161,9 +166,9 @@ export default function App() {
</div> </div>
<div className="brand-copy"> <div className="brand-copy">
<p className="eyebrow">Ranaze Bubble Tea & Slush</p> <p className="eyebrow">Ranaze Bubble Tea & Slush</p>
<h1>Tablet POS</h1> <h1>Choose your drink</h1>
<p className="muted"> <p className="muted">
Fast ordering for house favorites, easy edits, and detailed custom drinks. Fresh bubble tea, fruit slushes, and easy customizations made just the way you like them.
</p> </p>
</div> </div>
</div> </div>
@@ -174,7 +179,7 @@ export default function App() {
className={`tab ${view === tab ? "is-active" : ""}`} className={`tab ${view === tab ? "is-active" : ""}`}
onClick={() => setView(tab)} onClick={() => setView(tab)}
> >
{titleCase(tab)} {tabLabels[tab]}
</button> </button>
))} ))}
</nav> </nav>
@@ -185,10 +190,10 @@ export default function App() {
{orderMode && ( {orderMode && (
<div className="order-toolbar"> <div className="order-toolbar">
<button className="text-btn" onClick={() => setOrderMode(null)}> <button className="text-btn" onClick={() => setOrderMode(null)}>
Back to order options Back to drink options
</button> </button>
<p className="muted order-toolbar-note"> <p className="muted order-toolbar-note">
This screen returns to the start menu after 90 seconds of inactivity. This screen returns to the drink menu after 90 seconds of inactivity.
</p> </p>
</div> </div>
)} )}
@@ -196,27 +201,27 @@ export default function App() {
{!orderMode && ( {!orderMode && (
<section className="panel mode-launcher"> <section className="panel mode-launcher">
<div className="mode-launcher-copy"> <div className="mode-launcher-copy">
<p className="eyebrow">Start a new order</p> <p className="eyebrow">Welcome to Ranaze</p>
<h2>How would you like to order today?</h2> <h2>How would you like to choose your drink?</h2>
<p className="muted"> <p className="muted">
Pick the path that feels easiest for your guest. You can switch anytime during the order. Start with a signature favorite, make a few quick changes, or fully customize your drink.
</p> </p>
</div> </div>
<div className="mode-grid"> <div className="mode-grid">
<button className="mode-card" onClick={() => setOrderMode("premade")}> <button className="mode-card" onClick={() => setOrderMode("premade")}>
<p className="eyebrow">Fastest</p> <p className="eyebrow">Fastest</p>
<h3>Pre-Made Recipes</h3> <h3>House Favorites</h3>
<p className="muted">Best for guests who want a house favorite with no extra decisions.</p> <p className="muted">Perfect if you want one of our signature drinks with no extra steps.</p>
</button> </button>
<button className="mode-card" onClick={() => setOrderMode("simple")}> <button className="mode-card" onClick={() => setOrderMode("simple")}>
<p className="eyebrow">Easy</p> <p className="eyebrow">Easy</p>
<h3>Simple Customization</h3> <h3>Quick Customization</h3>
<p className="muted">Choose a drink, pick a size and sweetness level, and place it quickly.</p> <p className="muted">Choose your drink, then adjust size and sweetness in just a few taps.</p>
</button> </button>
<button className="mode-card" onClick={() => setOrderMode("detailed")}> <button className="mode-card" onClick={() => setOrderMode("detailed")}>
<p className="eyebrow">Flexible</p> <p className="eyebrow">Flexible</p>
<h3>Detailed Customization</h3> <h3>Build It Your Way</h3>
<p className="muted">Fine-tune ice, toppings, and special instructions for exact orders.</p> <p className="muted">Fine-tune ice, toppings, and special instructions for the exact drink you want.</p>
</button> </button>
</div> </div>
</section> </section>
@@ -267,7 +272,7 @@ export default function App() {
<div className="inline-builder"> <div className="inline-builder">
<div className="inline-builder-header"> <div className="inline-builder-header">
<div> <div>
<p className="eyebrow">Selected drink</p> <p className="eyebrow">Your drink</p>
<h3>{activeMode.title}</h3> <h3>{activeMode.title}</h3>
</div> </div>
<div className="line-price"> <div className="line-price">
@@ -341,7 +346,7 @@ export default function App() {
)} )}
{orderMode === "premade" && ( {orderMode === "premade" && (
<div className="empty-state mode-note"> <div className="empty-state mode-note">
This version uses the house recipe exactly as listed on the menu. This option keeps the drink exactly as our signature recipe.
</div> </div>
)} )}
<div className="builder-actions"> <div className="builder-actions">
@@ -375,14 +380,14 @@ export default function App() {
<section className="ticket"> <section className="ticket">
<div className="ticket-header"> <div className="ticket-header">
<div> <div>
<p className="eyebrow">Current order</p> <p className="eyebrow">Your order</p>
<h3>Current ticket</h3> <h3>Review your drinks</h3>
</div> </div>
<button className="text-btn" onClick={() => setTicket([])}>Clear</button> <button className="text-btn" onClick={() => setTicket([])}>Clear all</button>
</div> </div>
<div className="ticket-lines"> <div className="ticket-lines">
{ticket.length === 0 && <div className="empty-state">No items in the ticket.</div>} {ticket.length === 0 && <div className="empty-state">Your order is empty.</div>}
{ticket.map((line, index) => ( {ticket.map((line, index) => (
<article key={`${line.menuItemId}-${index}`} className="ticket-line"> <article key={`${line.menuItemId}-${index}`} className="ticket-line">
<header> <header>
@@ -397,10 +402,10 @@ export default function App() {
</button> </button>
</header> </header>
<p>{line.size} • {line.sweetness} • {line.ice}</p> <p>{line.size} • {line.sweetness} • {line.ice}</p>
<p className="muted">{line.toppings.length ? line.toppings.join(", ") : "No toppings"}</p> <p className="muted">{line.toppings.length ? line.toppings.join(", ") : "No extra toppings"}</p>
{line.notes && <p className="muted">Note: {line.notes}</p>} {line.notes && <p className="muted">Special request: {line.notes}</p>}
<div className="line-meta"> <div className="line-meta">
<span>Line total</span> <span>Drink total</span>
<strong>{formatCurrency(lineTotal({ basePrice: line.basePrice }, line, settings))}</strong> <strong>{formatCurrency(lineTotal({ basePrice: line.basePrice }, line, settings))}</strong>
</div> </div>
</article> </article>
@@ -409,21 +414,22 @@ export default function App() {
<div className="checkout-form"> <div className="checkout-form">
<label className="field"> <label className="field">
<span className="field-label">Customer name</span> <span className="field-label">Name for the order</span>
<input <input
placeholder="Optional"
value={customerName} value={customerName}
maxLength="40" maxLength="40"
onChange={(event) => setCustomerName(event.target.value)} onChange={(event) => setCustomerName(event.target.value)}
/> />
</label> </label>
<label className="field"> <label className="field">
<span className="field-label">Payment</span> <span className="field-label">How would you like to pay?</span>
<select <select
value={paymentMethod} value={paymentMethod}
onChange={(event) => setPaymentMethod(event.target.value)} onChange={(event) => setPaymentMethod(event.target.value)}
> >
<option value="cash">Cash</option> <option value="cash">Cash</option>
<option value="tab">House Tab</option> <option value="tab">Store Tab</option>
<option value="unpaid">Pay Later</option> <option value="unpaid">Pay Later</option>
</select> </select>
</label> </label>
@@ -447,7 +453,7 @@ export default function App() {
} }
}} }}
> >
Place order Place my order
</button> </button>
</div> </div>
</section> </section>
+30
View File
@@ -95,6 +95,11 @@ button {
gap: 0.2rem; gap: 0.2rem;
} }
.brand-copy h1 {
font-size: clamp(2rem, 3vw, 3rem);
line-height: 0.98;
}
.brand-copy p:last-child { .brand-copy p:last-child {
margin: 0; margin: 0;
max-width: 30rem; max-width: 30rem;
@@ -123,6 +128,13 @@ button {
flex-wrap: wrap; flex-wrap: wrap;
} }
.tabs {
padding: 0.4rem;
border-radius: 999px;
background: rgba(255, 255, 255, 0.55);
border: 1px solid rgba(16, 16, 16, 0.06);
}
.screen { .screen {
display: grid; display: grid;
grid-template-columns: 1.15fr 0.85fr; grid-template-columns: 1.15fr 0.85fr;
@@ -140,6 +152,7 @@ button {
.order-toolbar-note { .order-toolbar-note {
margin: 0; margin: 0;
font-size: 0.92rem;
} }
.queue-screen, .queue-screen,
@@ -170,6 +183,13 @@ button {
.mode-launcher-copy { .mode-launcher-copy {
max-width: 48rem; max-width: 48rem;
display: grid;
gap: 0.45rem;
}
.mode-launcher-copy h2 {
font-size: clamp(2rem, 3.4vw, 3.3rem);
line-height: 0.96;
} }
.mode-grid { .mode-grid {
@@ -181,7 +201,9 @@ button {
.mode-card { .mode-card {
display: grid; display: grid;
gap: 0.65rem; gap: 0.65rem;
align-content: start;
padding: 1.25rem; padding: 1.25rem;
min-height: 200px;
border: 1px solid rgba(16, 16, 16, 0.08); border: 1px solid rgba(16, 16, 16, 0.08);
border-radius: 24px; border-radius: 24px;
background: rgba(255, 255, 255, 0.9); background: rgba(255, 255, 255, 0.9);
@@ -240,6 +262,7 @@ button {
background: var(--accent); background: var(--accent);
color: #fff6e8; color: #fff6e8;
border-color: var(--accent); border-color: var(--accent);
box-shadow: 0 8px 18px rgba(16, 16, 16, 0.18);
} }
.text-btn { .text-btn {
@@ -286,6 +309,12 @@ button {
text-align: left; text-align: left;
} }
.drink-card h3,
.ticket-line strong,
.queue-card h3 {
line-height: 1.05;
}
.drink-card.is-selected { .drink-card.is-selected {
border-color: rgba(246, 179, 62, 0.7); border-color: rgba(246, 179, 62, 0.7);
box-shadow: box-shadow:
@@ -350,6 +379,7 @@ select {
.builder-intro { .builder-intro {
max-width: 28rem; max-width: 28rem;
margin-top: 0.35rem;
} }
.inline-builder { .inline-builder {