Hide admin tabs behind logo long press

This commit is contained in:
Zeeshaun
2026-05-05 19:46:42 +00:00
parent 443eb20b89
commit 0574e580fb
2 changed files with 185 additions and 18 deletions
+73 -9
View File
@@ -1,4 +1,4 @@
import { useEffect, useState } from "react";
import { useEffect, useRef, useState } from "react";
import {
createMenuItem,
createOrder,
@@ -8,16 +8,17 @@ import {
resetMenu,
updateOrderStatus
} from "./api.js";
import logo from "../../assets/ranaze-logo.png";
import logo from "../../assets/ranaze-logo-transparent.png";
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 tabLabels = {
order: "Order",
queue: "Order Status",
menu: "Menu Admin"
};
const initialBuilder = (settings) => ({
size: settings.sizeOptions[0]?.name ?? "Regular",
sweetness: settings.sweetnessOptions[3] ?? settings.sweetnessOptions[0] ?? "100%",
@@ -27,8 +28,11 @@ const initialBuilder = (settings) => ({
});
export default function App() {
const adminPressTimerRef = useRef(null);
const [view, setView] = useState("order");
const [orderMode, setOrderMode] = useState(null);
const [adminUnlocked, setAdminUnlocked] = useState(false);
const [logoPressing, setLogoPressing] = useState(false);
const [menu, setMenu] = useState([]);
const [orders, setOrders] = useState([]);
const [settings, setSettings] = useState(null);
@@ -51,6 +55,16 @@ export default function App() {
loadBootstrap();
}, []);
useEffect(() => {
return () => window.clearTimeout(adminPressTimerRef.current);
}, []);
useEffect(() => {
if (!adminUnlocked && view !== "order") {
setView("order");
}
}, [adminUnlocked, view]);
useEffect(() => {
if (view !== "order" || !orderMode) {
return;
@@ -135,6 +149,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 modeDetails = {
premade: {
stepLabel: "House favorite",
@@ -157,13 +172,35 @@ export default function App() {
};
const activeMode = orderMode ? modeDetails[orderMode] : null;
function startAdminPress() {
window.clearTimeout(adminPressTimerRef.current);
setLogoPressing(true);
adminPressTimerRef.current = window.setTimeout(() => {
setAdminUnlocked((current) => !current);
setLogoPressing(false);
}, adminUnlockPressMs);
}
function cancelAdminPress() {
window.clearTimeout(adminPressTimerRef.current);
setLogoPressing(false);
}
return (
<div className="app-shell">
<header className="topbar">
<div className="brand-lockup">
<div className="brand-mark">
<button
className={`brand-mark ${logoPressing ? "is-pressing" : ""}`}
onPointerDown={startAdminPress}
onPointerUp={cancelAdminPress}
onPointerLeave={cancelAdminPress}
onPointerCancel={cancelAdminPress}
onContextMenu={(event) => event.preventDefault()}
type="button"
>
<img src={logo} alt="Ranaze Bubble Tea & Slush" />
</div>
</button>
<div className="brand-copy">
<p className="eyebrow">Ranaze Bubble Tea & Slush</p>
<h1>Choose your drink</h1>
@@ -173,7 +210,7 @@ export default function App() {
</div>
</div>
<nav className="tabs">
{["order", "queue", "menu"].map((tab) => (
{visibleTabs.map((tab) => (
<button
key={tab}
className={`tab ${view === tab ? "is-active" : ""}`}
@@ -182,6 +219,11 @@ export default function App() {
{tabLabels[tab]}
</button>
))}
{adminUnlocked && (
<button className="tab admin-lock-btn" onClick={() => setAdminUnlocked(false)}>
Hide Admin
</button>
)}
</nav>
</header>
@@ -207,6 +249,13 @@ export default function App() {
Start with a signature favorite, make a few quick changes, or fully customize your drink.
</p>
</div>
<div className="mode-hero">
<div className="mode-hero-copy">
<p className="eyebrow">Freshly made for you</p>
<h3>Bubble tea, fruit tea, and slushes with easy customization.</h3>
</div>
<img src={heroCup} alt="Ranaze signature bubble tea" className="mode-hero-image" />
</div>
<div className="mode-grid">
<button className="mode-card" onClick={() => setOrderMode("premade")}>
<p className="eyebrow">Fastest</p>
@@ -261,9 +310,14 @@ export default function App() {
setBuilder(initialBuilder(settings));
}}
>
<p className="eyebrow">{drink.category}</p>
<h3>{drink.name}</h3>
<p className="muted">{drink.description}</p>
<div className="drink-card-badge">
<span>{categoryMonogram(drink.category)}</span>
</div>
<div className="drink-card-copy">
<p className="eyebrow">{drink.category}</p>
<h3>{drink.name}</h3>
<p className="muted">{drink.description}</p>
</div>
<strong>{formatCurrency(drink.basePrice)}</strong>
</button>
))}
@@ -675,6 +729,16 @@ function SummaryCard({ label, value }) {
);
}
function categoryMonogram(category) {
const map = {
"Milk Tea": "MT",
"Fruit Tea": "FT",
Specialty: "SP"
};
return map[category] || category.slice(0, 2).toUpperCase();
}
function nextStatusButton(order, onAdvance) {
const map = {
new: "making",
+112 -9
View File
@@ -14,6 +14,20 @@
font-family: "Avenir Next", "Trebuchet MS", sans-serif;
}
body::before {
content: "";
position: fixed;
inset: 0;
background:
linear-gradient(180deg, rgba(255, 249, 239, 0.88), rgba(245, 231, 208, 0.94)),
radial-gradient(circle at 10% 18%, rgba(246, 179, 62, 0.15), transparent 18%),
radial-gradient(circle at 88% 16%, rgba(244, 221, 174, 0.22), transparent 20%),
radial-gradient(circle at 16% 82%, rgba(244, 221, 174, 0.16), transparent 18%),
radial-gradient(circle at 84% 86%, rgba(246, 179, 62, 0.12), transparent 18%);
pointer-events: none;
z-index: -1;
}
* {
box-sizing: border-box;
}
@@ -72,22 +86,36 @@ button {
}
.brand-mark {
width: 104px;
height: 104px;
width: 120px;
height: 120px;
flex: 0 0 auto;
border-radius: 28px;
background: rgba(255, 255, 255, 0.88);
border: 1px solid rgba(16, 16, 16, 0.06);
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.7);
border-radius: 32px;
background: radial-gradient(circle at center, rgba(255, 255, 255, 0.9), rgba(255, 248, 232, 0.72));
border: 1px solid rgba(16, 16, 16, 0.04);
box-shadow:
inset 0 1px 0 rgba(255, 255, 255, 0.7),
0 16px 32px rgba(83, 61, 24, 0.1);
display: grid;
place-items: center;
overflow: hidden;
padding: 0;
appearance: none;
cursor: default;
transition: transform 180ms ease, box-shadow 180ms ease, border-color 180ms ease;
}
.brand-mark.is-pressing {
transform: scale(0.98);
border-color: rgba(246, 179, 62, 0.45);
box-shadow:
inset 0 1px 0 rgba(255, 255, 255, 0.7),
0 10px 20px rgba(83, 61, 24, 0.14);
}
.brand-mark img {
width: 100%;
height: 100%;
object-fit: cover;
width: 88%;
height: 88%;
object-fit: contain;
}
.brand-copy {
@@ -198,6 +226,36 @@ button {
gap: 1rem;
}
.mode-hero {
display: grid;
grid-template-columns: 1fr 220px;
gap: 1rem;
align-items: center;
padding: 1.1rem 1.25rem;
border-radius: 26px;
background: linear-gradient(135deg, rgba(255, 255, 255, 0.76), rgba(247, 221, 161, 0.36));
border: 1px solid rgba(16, 16, 16, 0.06);
}
.mode-hero-copy {
display: grid;
gap: 0.4rem;
}
.mode-hero-copy h3 {
margin: 0;
font-size: clamp(1.2rem, 2vw, 1.8rem);
line-height: 1.05;
}
.mode-hero-image {
width: 100%;
max-height: 220px;
object-fit: contain;
justify-self: end;
filter: drop-shadow(0 22px 24px rgba(83, 61, 24, 0.16));
}
.mode-card {
display: grid;
gap: 0.65rem;
@@ -265,6 +323,10 @@ button {
box-shadow: 0 8px 18px rgba(16, 16, 16, 0.18);
}
.admin-lock-btn {
color: var(--muted);
}
.text-btn {
background: transparent;
}
@@ -309,6 +371,37 @@ button {
text-align: left;
}
.drink-card {
display: grid;
grid-template-columns: 74px 1fr auto;
align-items: center;
gap: 0.9rem;
}
.drink-card-badge {
width: 74px;
height: 74px;
border-radius: 20px;
background: linear-gradient(135deg, rgba(255, 255, 255, 0.96), rgba(248, 236, 210, 0.9));
border: 1px solid rgba(16, 16, 16, 0.06);
display: grid;
place-items: center;
overflow: hidden;
}
.drink-card-badge span {
font-family: Georgia, "Times New Roman", serif;
font-size: 1.15rem;
font-weight: 700;
letter-spacing: 0.08em;
color: #191512;
}
.drink-card-copy {
display: grid;
gap: 0.2rem;
}
.drink-card h3,
.ticket-line strong,
.queue-card h3 {
@@ -448,6 +541,7 @@ select {
@media (max-width: 980px) {
.topbar,
.screen,
.mode-hero,
.mode-grid,
.queue-columns,
.menu-item,
@@ -466,6 +560,15 @@ select {
height: 88px;
}
.drink-card {
grid-template-columns: 1fr;
}
.drink-card-badge {
width: 64px;
height: 64px;
}
.order-toolbar {
align-items: flex-start;
flex-direction: column;