diff --git a/client/src/App.jsx b/client/src/App.jsx
index 458076d..18d17a9 100644
--- a/client/src/App.jsx
+++ b/client/src/App.jsx
@@ -14,8 +14,8 @@ import heroCup from "../../assets/ranaze-bubble-tea-single-cup-transparent.png";
const queueStatuses = ["new", "making", "ready"];
const orderIdleTimeoutMs = 90_000;
const adminUnlockPressMs = 3_000;
+const adminPassword = "admin";
const tabLabels = {
- order: "Order",
queue: "Order Status",
menu: "Menu Admin"
};
@@ -31,6 +31,7 @@ export default function App() {
const adminPressTimerRef = useRef(null);
const [view, setView] = useState("order");
const [orderMode, setOrderMode] = useState(null);
+ const [showCart, setShowCart] = useState(false);
const [adminUnlocked, setAdminUnlocked] = useState(false);
const [logoPressing, setLogoPressing] = useState(false);
const [menu, setMenu] = useState([]);
@@ -90,6 +91,12 @@ export default function App() {
};
}, [view, orderMode]);
+ useEffect(() => {
+ if (!orderMode) {
+ setShowCart(false);
+ }
+ }, [orderMode]);
+
useEffect(() => {
if (!settings) {
return;
@@ -149,7 +156,7 @@ export default function App() {
return selectedCategory === "All" || item.category === selectedCategory;
});
const selectedDrink = menu.find((item) => item._id === selectedDrinkId) || null;
- const visibleTabs = adminUnlocked ? ["order", "queue", "menu"] : ["order"];
+ const visibleTabs = adminUnlocked ? ["queue", "menu"] : [];
const modeDetails = {
premade: {
stepLabel: "House favorite",
@@ -176,7 +183,17 @@ export default function App() {
window.clearTimeout(adminPressTimerRef.current);
setLogoPressing(true);
adminPressTimerRef.current = window.setTimeout(() => {
- setAdminUnlocked((current) => !current);
+ if (adminUnlocked) {
+ setAdminUnlocked(false);
+ setView("order");
+ } else {
+ const password = window.prompt("Enter admin password");
+ if (password === adminPassword) {
+ setAdminUnlocked(true);
+ } else if (password !== null && password !== "") {
+ window.alert("Incorrect password");
+ }
+ }
setLogoPressing(false);
}, adminUnlockPressMs);
}
@@ -186,6 +203,18 @@ export default function App() {
setLogoPressing(false);
}
+ function addLineToTicket(drink, lineBuilder) {
+ setTicket((current) => [
+ ...current,
+ {
+ menuItemId: drink._id,
+ name: drink.name,
+ basePrice: drink.basePrice,
+ ...lineBuilder
+ }
+ ]);
+ }
+
return (
@@ -209,34 +238,50 @@ export default function App() {
-
+ )}
{view === "order" && (
-
+
{orderMode && (
-
)}
@@ -276,7 +321,7 @@ export default function App() {
)}
- {orderMode && (
+ {orderMode && !showCart && (
@@ -286,7 +331,9 @@ export default function App() {
setOrderMode(null)}>Change order style
-
{formatCurrency(ticketTotal(ticket, settings))}
+ {ticket.length > 0 && (
+
setShowCart(true)}>Go to order
+ )}
@@ -306,6 +353,10 @@ export default function App() {
key={drink._id}
className={`drink-card ${selectedDrinkId === drink._id ? "is-selected" : ""}`}
onClick={() => {
+ if (orderMode === "premade") {
+ addLineToTicket(drink, initialBuilder(settings));
+ return;
+ }
setSelectedDrinkId(drink._id);
setBuilder(initialBuilder(settings));
}}
@@ -322,6 +373,16 @@ export default function App() {
))}
+ {orderMode === "premade" && (
+
+ Tap any drink to add it to your order. Use the order button whenever you are ready to review.
+
+ )}
+
+ )}
+
+ {orderMode && !showCart && orderMode !== "premade" && (
+
{selectedDrink && builder ? (
@@ -398,24 +459,11 @@ export default function App() {
/>
)}
- {orderMode === "premade" && (
-
- This option keeps the drink exactly as our signature recipe.
-
- )}
{
- setTicket((current) => [
- ...current,
- {
- menuItemId: selectedDrink._id,
- name: selectedDrink.name,
- basePrice: selectedDrink.basePrice,
- ...builder
- }
- ]);
+ addLineToTicket(selectedDrink, builder);
setBuilder(initialBuilder(settings));
}}
>
@@ -424,20 +472,23 @@ export default function App() {
) : (
-
Add menu items to start taking orders.
+
Choose a drink to customize it before adding it to your order.
)}
)}
- {orderMode && (
-
+ {orderMode && showCart && (
+
Your order
Review your drinks
-
setTicket([])}>Clear all
+
+ setShowCart(false)}>Add more drinks
+ setTicket([])}>Clear all
+
@@ -502,6 +553,8 @@ export default function App() {
setTicket([]);
setCustomerName("");
setPaymentMethod("cash");
+ setShowCart(false);
+ setOrderMode(null);
} catch (requestError) {
setError(requestError.message);
}
diff --git a/client/src/styles.css b/client/src/styles.css
index 7aaca24..fb11a99 100644
--- a/client/src/styles.css
+++ b/client/src/styles.css
@@ -169,6 +169,10 @@ button {
gap: 1rem;
}
+.order-screen.is-single {
+ grid-template-columns: 1fr;
+}
+
.order-toolbar {
grid-column: 1 / -1;
display: flex;
@@ -183,6 +187,14 @@ button {
font-size: 0.92rem;
}
+.order-toolbar-actions,
+.ticket-header-actions {
+ display: flex;
+ align-items: center;
+ gap: 0.75rem;
+ flex-wrap: wrap;
+}
+
.queue-screen,
.menu-screen {
display: grid;
@@ -341,6 +353,11 @@ button {
box-shadow: 0 12px 24px rgba(223, 162, 50, 0.28);
}
+.subtle-btn {
+ box-shadow: none;
+ padding-inline: 1rem;
+}
+
.primary-btn:disabled {
opacity: 0.6;
cursor: not-allowed;
@@ -458,6 +475,12 @@ select {
border-top: 1px solid var(--line);
}
+.cart-panel .ticket {
+ margin-top: 0;
+ padding-top: 0;
+ border-top: 0;
+}
+
.ticket-line header {
display: flex;
justify-content: space-between;
@@ -574,6 +597,12 @@ select {
flex-direction: column;
}
+ .order-toolbar-actions,
+ .ticket-header-actions {
+ align-items: flex-start;
+ flex-direction: column;
+ }
+
.inline-builder-header {
flex-direction: column;
}