POS / src /App.jsx
ronylu's picture
Upload 2 files
e686f4f verified
Raw
History Blame Contribute Delete
31.4 kB
import React, { useEffect, useMemo, useState } from "react";
import {
LayoutDashboard,
ShoppingCart,
BarChart3,
Wallet,
Table2,
Settings,
LogOut,
RefreshCw,
Search,
ChevronDown,
Check,
X,
Pencil,
Trash2,
Percent,
BadgeCheck,
} from "lucide-react";
/**
* iStore POS (Apple-only) — single-file UI mock inspired by the provided POS layout.
* - Product grid with category tabs + search
* - Cart/order summary with totals, discount, and simple order meta
* - Clean, responsive layout
*/
// ---------- Helpers
const money = (n) =>
new Intl.NumberFormat(undefined, {
style: "currency",
currency: "USD",
}).format(n);
const nowLabel = () => {
const d = new Date();
const weekday = d.toLocaleDateString(undefined, { weekday: "long" });
const date = d.toLocaleDateString(undefined, { day: "2-digit", month: "short", year: "numeric" });
const time = d.toLocaleTimeString(undefined, { hour: "2-digit", minute: "2-digit" });
return `${weekday}, ${date}${time}`;
};
const pill = (active) =>
`inline-flex items-center gap-2 rounded-full px-3 py-1 text-sm transition ${
active
? "bg-slate-900 text-white shadow-sm"
: "bg-white text-slate-700 hover:bg-slate-50 border border-slate-200"
}`;
function dataUriCard({ title, subtitle, glyph = "" }) {
// Simple “photo-like” SVG card so images work offline.
const svg = `
<svg xmlns='http://www.w3.org/2000/svg' width='900' height='600'>
<defs>
<linearGradient id='g' x1='0' y1='0' x2='1' y2='1'>
<stop offset='0' stop-color='#0f172a'/>
<stop offset='0.45' stop-color='#111827'/>
<stop offset='1' stop-color='#1f2937'/>
</linearGradient>
<radialGradient id='r' cx='70%' cy='20%' r='70%'>
<stop offset='0' stop-color='#93c5fd' stop-opacity='0.75'/>
<stop offset='0.55' stop-color='#60a5fa' stop-opacity='0.25'/>
<stop offset='1' stop-color='#60a5fa' stop-opacity='0'/>
</radialGradient>
<filter id='noise'>
<feTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' stitchTiles='stitch'/>
<feColorMatrix type='matrix' values='1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 0.06 0'/>
</filter>
</defs>
<rect width='100%' height='100%' fill='url(#g)'/>
<rect width='100%' height='100%' fill='url(#r)'/>
<rect width='100%' height='100%' filter='url(#noise)'/>
<circle cx='120' cy='120' r='70' fill='rgba(255,255,255,0.09)'/>
<text x='120' y='140' text-anchor='middle' font-size='64' fill='rgba(255,255,255,0.95)' font-family='ui-sans-serif, -apple-system, Segoe UI, Roboto'>${glyph}</text>
<text x='40' y='520' font-size='44' fill='white' font-weight='700'
font-family='ui-sans-serif, -apple-system, Segoe UI, Roboto'>${escapeXml(title)}</text>
<text x='40' y='565' font-size='24' fill='rgba(255,255,255,0.78)'
font-family='ui-sans-serif, -apple-system, Segoe UI, Roboto'>${escapeXml(subtitle)}</text>
<path d='M820 70c-60 35-120 55-180 60' fill='none' stroke='rgba(255,255,255,0.16)' stroke-width='10' stroke-linecap='round'/>
<path d='M830 150c-70 40-140 62-210 66' fill='none' stroke='rgba(255,255,255,0.10)' stroke-width='10' stroke-linecap='round'/>
</svg>`;
return `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svg.trim())}`;
}
function escapeXml(s) {
return String(s)
.replaceAll("&", "&amp;")
.replaceAll("<", "&lt;")
.replaceAll(">", "&gt;")
.replaceAll('"', "&quot;")
.replaceAll("'", "&#39;");
}
// ---------- Data
const PRODUCTS = [
{
id: "iphone-15-pro",
name: "iPhone 15 Pro",
price: 999,
category: "iPhone",
variant: "128GB",
available: true,
glyph: "📱",
},
{
id: "iphone-15",
name: "iPhone 15",
price: 799,
category: "iPhone",
variant: "128GB",
available: true,
glyph: "📱",
},
{
id: "macbook-air-m3",
name: "MacBook Air",
price: 1099,
category: "Mac",
variant: "M3 • 13-inch",
available: true,
glyph: "💻",
},
{
id: "macbook-pro-m3",
name: "MacBook Pro",
price: 1599,
category: "Mac",
variant: "M3 • 14-inch",
available: true,
glyph: "💻",
},
{
id: "imac-m3",
name: "iMac",
price: 1299,
category: "Mac",
variant: "M3 • 24-inch",
available: true,
glyph: "🖥️",
},
{
id: "mac-mini",
name: "Mac mini",
price: 599,
category: "Mac",
variant: "M2",
available: true,
glyph: "🧊",
},
{
id: "ipad-pro",
name: "iPad Pro",
price: 999,
category: "iPad",
variant: "11-inch",
available: true,
glyph: "📟",
},
{
id: "ipad-air",
name: "iPad Air",
price: 599,
category: "iPad",
variant: "10.9-inch",
available: true,
glyph: "📟",
},
{
id: "apple-watch-ultra",
name: "Apple Watch Ultra",
price: 799,
category: "Watch",
variant: "49mm",
available: true,
glyph: "⌚",
},
{
id: "apple-watch-series",
name: "Apple Watch Series",
price: 399,
category: "Watch",
variant: "41mm",
available: true,
glyph: "⌚",
},
{
id: "airpods-pro",
name: "AirPods Pro",
price: 249,
category: "Audio",
variant: "2nd gen",
available: true,
glyph: "🎧",
},
{
id: "airpods-max",
name: "AirPods Max",
price: 549,
category: "Audio",
variant: "Over-ear",
available: true,
glyph: "🎧",
},
{
id: "homepod",
name: "HomePod",
price: 299,
category: "Audio",
variant: "2nd gen",
available: true,
glyph: "🔊",
},
{
id: "airtag-4pack",
name: "AirTag (4‑pack)",
price: 99,
category: "Accessories",
variant: "4 pack",
available: true,
glyph: "🏷️",
},
{
id: "magic-keyboard",
name: "Magic Keyboard",
price: 99,
category: "Accessories",
variant: "Wireless",
available: false,
glyph: "⌨️",
},
];
const CATEGORIES = ["All", "iPhone", "Mac", "iPad", "Watch", "Audio", "Accessories"];
const SEED_ORDER_ID = () => `#I${Math.floor(100000 + Math.random() * 900000)}`;
// ---------- UI bits
function Chip({ children, active, onClick }) {
return (
<button className={pill(active)} onClick={onClick}>
{children}
</button>
);
}
function IconDot({ ok }) {
return (
<span
className={`inline-flex items-center gap-2 rounded-full px-2 py-0.5 text-xs font-semibold ${
ok ? "bg-emerald-50 text-emerald-700" : "bg-rose-50 text-rose-700"
}`}
>
<span
className={`h-1.5 w-1.5 rounded-full ${ok ? "bg-emerald-500" : "bg-rose-500"}`}
/>
{ok ? "Available" : "Out of stock"}
</span>
);
}
function Divider() {
return <div className="h-px w-full bg-slate-200" />;
}
function SmallField({ label, children }) {
return (
<div className="space-y-1">
<div className="text-xs font-semibold text-slate-500">{label}</div>
{children}
</div>
);
}
// ---------- Main
export default function IStorePOS() {
const [activeNav, setActiveNav] = useState("Menu Order");
const [category, setCategory] = useState("All");
const [query, setQuery] = useState("");
const [cart, setCart] = useState(() => {
// Prefill like the screenshot (optional):
const pre = [
{ id: "airpods-pro", qty: 1, note: "—" },
{ id: "macbook-air-m3", qty: 1, note: "Silver" },
{ id: "iphone-15-pro", qty: 1, note: "Natural Titanium" },
];
return pre;
});
const [applyDiscount, setApplyDiscount] = useState(true);
const [orderId, setOrderId] = useState(SEED_ORDER_ID);
const [clock, setClock] = useState(nowLabel());
const [orderType, setOrderType] = useState("In-store");
const [counter, setCounter] = useState("Counter A");
useEffect(() => {
const t = setInterval(() => setClock(nowLabel()), 15_000);
return () => clearInterval(t);
}, []);
const productsWithImages = useMemo(() => {
return PRODUCTS.map((p) => ({
...p,
image:
p.image ||
dataUriCard({
title: p.name,
subtitle: `${p.variant}${money(p.price)}`,
glyph: p.glyph,
}),
}));
}, []);
const counts = useMemo(() => {
const c = { All: productsWithImages.length };
for (const cat of CATEGORIES.slice(1)) {
c[cat] = productsWithImages.filter((p) => p.category === cat).length;
}
return c;
}, [productsWithImages]);
const filtered = useMemo(() => {
const q = query.trim().toLowerCase();
return productsWithImages
.filter((p) => (category === "All" ? true : p.category === category))
.filter((p) =>
q
? `${p.name} ${p.variant} ${p.category}`.toLowerCase().includes(q)
: true
);
}, [productsWithImages, category, query]);
const cartLines = useMemo(() => {
return cart
.map((line) => {
const p = productsWithImages.find((x) => x.id === line.id);
if (!p) return null;
return {
...line,
product: p,
lineTotal: p.price * line.qty,
};
})
.filter(Boolean);
}, [cart, productsWithImages]);
const subtotal = useMemo(() => cartLines.reduce((s, l) => s + l.lineTotal, 0), [cartLines]);
const taxRate = 0.1;
const taxes = useMemo(() => subtotal * taxRate, [subtotal]);
const discountEligible = subtotal >= 500;
const discount = useMemo(() => (applyDiscount && discountEligible ? subtotal * 0.1 : 0), [applyDiscount, discountEligible, subtotal]);
const total = useMemo(() => Math.max(0, subtotal + taxes - discount), [subtotal, taxes, discount]);
const addToCart = (id) => {
setCart((prev) => {
const idx = prev.findIndex((x) => x.id === id);
if (idx >= 0) {
const next = [...prev];
next[idx] = { ...next[idx], qty: next[idx].qty + 1 };
return next;
}
return [...prev, { id, qty: 1, note: "—" }];
});
};
const setQty = (id, qty) => {
setCart((prev) =>
prev
.map((x) => (x.id === id ? { ...x, qty } : x))
.filter((x) => x.qty > 0)
);
};
const removeLine = (id) => setCart((prev) => prev.filter((x) => x.id !== id));
const randomizeOrder = () => {
setOrderId(SEED_ORDER_ID());
setQuery("");
setCategory("All");
};
const navItems = [
{ label: "Dashboard", icon: LayoutDashboard },
{ label: "Menu Order", icon: ShoppingCart },
{ label: "Analytics", icon: BarChart3 },
{ label: "Withdrawal", icon: Wallet },
{ label: "Manage Table", icon: Table2 },
];
return (
<div className="min-h-screen w-full bg-gradient-to-b from-slate-100 to-slate-50 p-4">
<div className="mx-auto max-w-[1400px] overflow-hidden rounded-2xl border border-slate-200 bg-white shadow-sm">
<div className="flex min-h-[820px]">
{/* Sidebar */}
<aside className="w-[260px] border-r border-slate-200 bg-white p-4">
<div className="flex items-center gap-3">
<div className="grid h-10 w-10 place-items-center rounded-xl bg-slate-900 text-white shadow-sm">
<span className="text-lg"></span>
</div>
<div>
<div className="text-sm font-semibold text-slate-900">iStore</div>
<div className="text-xs text-slate-500">POS • Apple Products</div>
</div>
</div>
<div className="mt-6 space-y-1">
{navItems.map((it) => {
const Icon = it.icon;
const active = activeNav === it.label;
return (
<button
key={it.label}
onClick={() => setActiveNav(it.label)}
className={`flex w-full items-center gap-3 rounded-xl px-3 py-2 text-sm font-semibold transition ${
active
? "bg-slate-900 text-white shadow-sm"
: "text-slate-600 hover:bg-slate-50"
}`}
>
<Icon className="h-4 w-4" />
{it.label}
</button>
);
})}
</div>
<div className="mt-6 rounded-2xl border border-slate-200 bg-slate-50 p-3">
<div className="flex items-start gap-3">
<div className="grid h-9 w-9 place-items-center rounded-xl bg-white shadow-sm">
<BadgeCheck className="h-5 w-5 text-slate-800" />
</div>
<div>
<div className="text-sm font-semibold text-slate-900">Store Status</div>
<div className="mt-1 inline-flex items-center gap-2 rounded-full bg-emerald-50 px-2.5 py-1 text-xs font-semibold text-emerald-700">
<span className="h-1.5 w-1.5 rounded-full bg-emerald-500" />
Open
</div>
<div className="mt-2 text-xs text-slate-500">Apple-only catalog • Demo UI</div>
</div>
</div>
</div>
<div className="mt-auto pt-6 space-y-2">
<button className="flex w-full items-center gap-3 rounded-xl px-3 py-2 text-sm font-semibold text-slate-600 hover:bg-slate-50">
<Settings className="h-4 w-4" />
Settings
</button>
<button className="flex w-full items-center gap-3 rounded-xl px-3 py-2 text-sm font-semibold text-slate-600 hover:bg-slate-50">
<LogOut className="h-4 w-4" />
Logout
</button>
</div>
</aside>
{/* Main */}
<main className="flex-1 bg-slate-50">
{/* Top bar */}
<div className="flex items-center justify-between gap-4 border-b border-slate-200 bg-white px-5 py-4">
<div className="flex items-center gap-3">
<div className="rounded-xl border border-slate-200 bg-slate-50 px-3 py-2">
<div className="text-xs font-semibold text-slate-500">Shop</div>
<div className="flex items-center gap-2 text-sm font-semibold text-slate-900">
iStore Downtown
<ChevronDown className="h-4 w-4 text-slate-500" />
</div>
</div>
<div className="hidden sm:flex items-center gap-2 rounded-xl border border-slate-200 bg-white px-3 py-2 text-sm font-semibold text-slate-700">
<span className="text-slate-500">{clock}</span>
</div>
</div>
<div className="flex items-center gap-2">
<button
onClick={randomizeOrder}
className="inline-flex items-center gap-2 rounded-xl border border-slate-200 bg-white px-3 py-2 text-sm font-semibold text-slate-700 hover:bg-slate-50"
title="Refresh"
>
<RefreshCw className="h-4 w-4" />
Refresh
</button>
<div className="relative w-[240px]">
<Search className="pointer-events-none absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-slate-400" />
<input
value={query}
onChange={(e) => setQuery(e.target.value)}
placeholder="Search products"
className="w-full rounded-xl border border-slate-200 bg-white py-2 pl-9 pr-3 text-sm font-semibold text-slate-800 placeholder:text-slate-400 focus:outline-none focus:ring-2 focus:ring-slate-900/10"
/>
</div>
<button className="inline-flex items-center gap-2 rounded-xl bg-slate-900 px-3 py-2 text-sm font-semibold text-white shadow-sm">
<span className="grid h-7 w-7 place-items-center rounded-lg bg-white/10">MO</span>
<span className="hidden md:block">Michael Olise</span>
<ChevronDown className="h-4 w-4 opacity-80" />
</button>
</div>
</div>
{/* Content */}
<div className="grid grid-cols-1 gap-4 p-5 lg:grid-cols-[1fr_360px]">
{/* Left: menu */}
<section className="space-y-4">
<div className="flex flex-wrap items-center justify-between gap-3">
<div className="flex flex-wrap items-center gap-2">
{CATEGORIES.map((c) => (
<Chip key={c} active={category === c} onClick={() => setCategory(c)}>
<span>{c}</span>
<span
className={`rounded-full px-2 py-0.5 text-xs font-bold ${
category === c ? "bg-white/15 text-white" : "bg-slate-100 text-slate-600"
}`}
>
{counts[c] ?? 0}
</span>
</Chip>
))}
</div>
<div className="text-sm font-semibold text-slate-600">
Showing <span className="text-slate-900">{filtered.length}</span> items
</div>
</div>
{/* Grid */}
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2 xl:grid-cols-4">
{filtered.map((p) => {
const inStock = p.available;
return (
<div
key={p.id}
className="overflow-hidden rounded-2xl border border-slate-200 bg-white shadow-sm"
>
<div className="relative">
<img
src={p.image}
alt={p.name}
className="h-36 w-full object-cover"
/>
<div className="absolute right-3 top-3">
<IconDot ok={inStock} />
</div>
</div>
<div className="space-y-2 p-3">
<div className="flex items-start justify-between gap-2">
<div>
<div className="text-sm font-bold text-slate-900">{p.name}</div>
<div className="text-xs font-semibold text-slate-500">{p.variant}</div>
</div>
<div className="text-sm font-extrabold text-slate-900">{money(p.price)}</div>
</div>
<button
disabled={!inStock}
onClick={() => addToCart(p.id)}
className={`w-full rounded-xl px-3 py-2 text-sm font-semibold transition ${
inStock
? "bg-slate-900 text-white hover:bg-slate-800"
: "bg-slate-100 text-slate-400 cursor-not-allowed"
}`}
>
{inStock ? "+ Add to Cart" : "× Not Available"}
</button>
</div>
</div>
);
})}
</div>
</section>
{/* Right: order summary */}
<aside className="rounded-2xl border border-slate-200 bg-white shadow-sm">
<div className="flex items-center justify-between gap-3 border-b border-slate-200 p-4">
<div>
<div className="text-sm font-extrabold text-slate-900">Order Summary</div>
<div className="text-xs font-semibold text-slate-500">Apple products only</div>
</div>
<div className="text-sm font-extrabold text-slate-900">{orderId}</div>
</div>
<div className="space-y-3 p-4">
{/* Lines */}
<div className="space-y-3">
{cartLines.length === 0 ? (
<div className="rounded-xl border border-dashed border-slate-200 bg-slate-50 p-4 text-center">
<div className="text-sm font-semibold text-slate-700">Cart is empty</div>
<div className="mt-1 text-xs text-slate-500">Add an Apple product from the grid</div>
</div>
) : (
cartLines.map((l) => (
<div key={l.id} className="flex gap-3">
<img
src={l.product.image}
alt={l.product.name}
className="h-14 w-14 rounded-xl object-cover"
/>
<div className="min-w-0 flex-1">
<div className="flex items-start justify-between gap-2">
<div className="min-w-0">
<div className="truncate text-sm font-bold text-slate-900">
{l.product.name} <span className="text-slate-500">({l.qty})</span>
</div>
<div className="text-xs font-semibold text-slate-500">
Note: {l.note} • {l.product.variant}
</div>
</div>
<div className="text-sm font-extrabold text-slate-900">
{money(l.product.price * l.qty)}
</div>
</div>
<div className="mt-2 flex items-center justify-between">
<div className="inline-flex items-center rounded-xl border border-slate-200 bg-white">
<button
className="px-2.5 py-1.5 text-sm font-bold text-slate-600 hover:bg-slate-50 rounded-l-xl"
onClick={() => setQty(l.id, Math.max(0, l.qty - 1))}
aria-label="Decrease"
>
</button>
<div className="px-2.5 py-1.5 text-sm font-bold text-slate-800">
{l.qty}
</div>
<button
className="px-2.5 py-1.5 text-sm font-bold text-slate-600 hover:bg-slate-50 rounded-r-xl"
onClick={() => setQty(l.id, l.qty + 1)}
aria-label="Increase"
>
</button>
</div>
<div className="flex items-center gap-2">
<button
className="inline-flex items-center gap-1.5 rounded-xl border border-slate-200 px-2.5 py-1.5 text-xs font-bold text-slate-700 hover:bg-slate-50"
onClick={() => {
const note = prompt(
`Edit note for ${l.product.name}:`,
l.note ?? "—"
);
if (note === null) return;
setCart((prev) =>
prev.map((x) => (x.id === l.id ? { ...x, note } : x))
);
}}
title="Edit note"
>
<Pencil className="h-3.5 w-3.5" />
Edit
</button>
<button
className="inline-flex items-center gap-1.5 rounded-xl border border-slate-200 px-2.5 py-1.5 text-xs font-bold text-slate-700 hover:bg-rose-50 hover:text-rose-700 hover:border-rose-200"
onClick={() => removeLine(l.id)}
title="Remove"
>
<Trash2 className="h-3.5 w-3.5" />
Remove
</button>
</div>
</div>
</div>
</div>
))
)}
</div>
<Divider />
{/* Totals */}
<div className="space-y-2">
<div className="flex items-center justify-between text-sm font-semibold text-slate-700">
<span>Subtotal</span>
<span className="text-slate-900">{money(subtotal)}</span>
</div>
<div className="flex items-center justify-between text-sm font-semibold text-slate-700">
<span>Taxes (10%)</span>
<span className="text-slate-900">{money(taxes)}</span>
</div>
<div className="flex items-center justify-between text-sm font-semibold text-slate-700">
<span className="inline-flex items-center gap-2">
<Percent className="h-4 w-4" /> Discount (10%)
</span>
<span className={`${discount > 0 ? "text-emerald-700" : "text-slate-900"}`}>
{discount > 0 ? `−${money(discount)}` : money(0)}
</span>
</div>
<div className="mt-2 flex items-center justify-between rounded-xl bg-slate-50 px-3 py-2">
<span className="text-sm font-extrabold text-slate-900">Total Payment</span>
<span className="text-sm font-extrabold text-slate-900">{money(total)}</span>
</div>
</div>
{/* Meta */}
<Divider />
<div className="grid grid-cols-2 gap-3">
<SmallField label="Order Type">
<select
value={orderType}
onChange={(e) => setOrderType(e.target.value)}
className="w-full rounded-xl border border-slate-200 bg-white px-3 py-2 text-sm font-semibold text-slate-800 focus:outline-none focus:ring-2 focus:ring-slate-900/10"
>
<option>In-store</option>
<option>Pickup</option>
<option>Delivery</option>
</select>
</SmallField>
<SmallField label="Select Counter">
<select
value={counter}
onChange={(e) => setCounter(e.target.value)}
className="w-full rounded-xl border border-slate-200 bg-white px-3 py-2 text-sm font-semibold text-slate-800 focus:outline-none focus:ring-2 focus:ring-slate-900/10"
>
<option>Counter A</option>
<option>Counter B</option>
<option>Counter C</option>
</select>
</SmallField>
</div>
<div className="rounded-2xl border border-slate-200 bg-slate-50 p-3">
<div className="flex items-center justify-between gap-3">
<div className="min-w-0">
<div className="text-sm font-extrabold text-slate-900">10% iStore Discount</div>
<div className="text-xs font-semibold text-slate-500">
{discountEligible
? "Eligible (subtotal ≥ $500)"
: "Add more items to reach $500"}
</div>
</div>
<button
onClick={() => setApplyDiscount((v) => !v)}
disabled={!discountEligible}
className={`inline-flex h-9 w-14 items-center rounded-full p-1 transition ${
applyDiscount && discountEligible
? "bg-slate-900"
: "bg-slate-200"
} ${!discountEligible ? "opacity-50 cursor-not-allowed" : ""}`}
aria-label="Toggle discount"
>
<span
className={`grid h-7 w-7 place-items-center rounded-full bg-white shadow-sm transition ${
applyDiscount && discountEligible ? "translate-x-5" : "translate-x-0"
}`}
>
{applyDiscount && discountEligible ? (
<Check className="h-4 w-4 text-slate-900" />
) : (
<X className="h-4 w-4 text-slate-500" />
)}
</span>
</button>
</div>
</div>
<button
onClick={() => {
if (cartLines.length === 0) {
alert("Add at least one product to the cart.");
return;
}
alert(
`Payment confirmed!\n\nOrder: ${orderId}\nTotal: ${money(total)}\nType: ${orderType}\nCounter: ${counter}`
);
setCart([]);
setOrderId(SEED_ORDER_ID());
}}
className="w-full rounded-xl bg-slate-900 px-4 py-3 text-sm font-extrabold text-white shadow-sm hover:bg-slate-800"
>
Confirm Payment
</button>
<button
onClick={() => setCart([])}
className="w-full rounded-xl border border-slate-200 bg-white px-4 py-3 text-sm font-extrabold text-slate-800 hover:bg-slate-50"
>
Clear Cart
</button>
<div className="pt-2 text-center text-xs font-semibold text-slate-500">
Tip: Click <span className="font-bold text-slate-700">Edit</span> to add notes like color or storage.
</div>
</div>
</aside>
</div>
</main>
</div>
</div>
<div className="mx-auto mt-4 max-w-[1400px] text-center text-xs font-semibold text-slate-500">
iStore POS UI mock • Frontend only (no backend)
</div>
</div>
);
}