From 3fbe0bb870d27c5202aa2487b72326faa87aa1ff Mon Sep 17 00:00:00 2001 From: karyamanswasta Date: Wed, 27 Aug 2025 07:41:31 +0700 Subject: [PATCH] ok --- src/App.css | 133 ++++++++++++++++++++++++++++++++++++++++-- src/pages/CafePage.js | 63 ++++++++++++++++---- 2 files changed, 178 insertions(+), 18 deletions(-) diff --git a/src/App.css b/src/App.css index fd59631..e4ecb8f 100644 --- a/src/App.css +++ b/src/App.css @@ -67,15 +67,136 @@ body { /* Ensure sticky cart bar stays above item overlays */ .StickyCartBar { - z-index: 0 !important; /* align with item lister */ + z-index: 120 !important; /* above content, below modal */ position: fixed; - bottom: 0; left: 0; right: 0; - background-color: white; - box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.1); - padding: 15px 20px; - border-radius: 20px 20px 0 0; + bottom: 24px; + background: transparent; + box-shadow: none; + pointer-events: none; /* only children capture clicks */ + display: flex; + justify-content: center; + align-items: center; + padding: 0 12px; +} + +/* Intro slide/fade when bar first appears */ +.StickyCartBar.intro { animation: bar-in 450ms ease-out; } +@keyframes bar-in { + 0% { opacity: 0; transform: translateY(12px); } + 100% { opacity: 1; transform: translateY(0); } +} + +/* Floating cart buttons inside sticky bar */ +.StickyCartBar .cartBtn { + pointer-events: auto; + display: flex; + align-items: center; + justify-content: space-between; + height: 44px; + padding: 0 14px; + border-radius: 999px; + border: none; + background: rgba(115,165,133,0.96); + color: #fff; + backdrop-filter: blur(3px); + box-shadow: 0 12px 28px rgba(115,165,133,0.35); + width: clamp(200px, 60vw, 420px); + transition: transform 0.15s ease, box-shadow 0.15s ease, background 0.2s ease; + position: relative; +} +.StickyCartBar .cartBtn:hover { box-shadow: 0 14px 32px rgba(115,165,133,0.4); transform: translateY(-1px); } +.StickyCartBar .cartBtn:active { transform: translateY(0); } +.StickyCartBar .cartBtn:focus-visible { outline: 3px solid rgba(115,165,133,0.4); outline-offset: 2px; } + +/* Bump + ping animation when items are added */ +.StickyCartBar .cartBtn.bump { animation: cart-bump 300ms ease; } +.StickyCartBar .cartBtn.bump::after { + content: ''; + position: absolute; + inset: 0; + border-radius: inherit; + box-shadow: 0 0 0 0 rgba(115,165,133,0.35); + animation: cart-ping 600ms ease-out; +} + +@keyframes cart-bump { + 0% { transform: scale(1); } + 10% { transform: scale(0.98); } + 30% { transform: scale(1.04); } + 50% { transform: scale(1.02); } + 100% { transform: scale(1); } +} +@keyframes cart-ping { + 0% { box-shadow: 0 0 0 0 rgba(115,165,133,0.35); } + 100% { box-shadow: 0 0 0 14px rgba(115,165,133,0); } +} + +.StickyCartBar .historyBtn { + pointer-events: auto; + display: flex; + align-items: center; + justify-content: center; + width: 48px; + height: 44px; + margin-left: 8px; + border-radius: 999px; + border: none; + background: rgba(115,165,133,0.96); + color: #fff; + backdrop-filter: blur(3px); + box-shadow: 0 12px 28px rgba(115,165,133,0.35); + transition: transform 0.15s ease, box-shadow 0.15s ease, background 0.2s ease; +} +.StickyCartBar .historyBtn:hover { box-shadow: 0 14px 32px rgba(115,165,133,0.4); transform: translateY(-1px); } +.StickyCartBar .historyBtn:active { transform: translateY(0); } +.StickyCartBar .historyBtn:focus-visible { outline: 3px solid rgba(115,165,133,0.4); outline-offset: 2px; } + +/* Subtle pulse to draw attention on new transaction */ +.StickyCartBar .historyBtn.pulse { animation: btn-pulse 900ms ease-out; } +@keyframes btn-pulse { + 0% { box-shadow: 0 12px 28px rgba(115,165,133,0.35); } + 60% { box-shadow: 0 18px 38px rgba(115,165,133,0.5); } + 100% { box-shadow: 0 12px 28px rgba(115,165,133,0.35); } +} + +.StickyCartBar .summary { + display: flex; + align-items: center; + gap: 8px; + font-weight: 700; +} +.StickyCartBar .value { white-space: nowrap; font-weight: 800; } +.StickyCartBar .icon { display: inline-flex; align-items: center; justify-content: center; width: 22px; height: 22px; margin-left: 8px; } +.StickyCartBar .icon { position: relative; } +.StickyCartBar .icon .badge { + position: absolute; + top: -6px; + right: -6px; + width: 18px; + height: 18px; + border-radius: 50%; + background: #ff6b6b; + color: #fff; + font-size: 11px; + font-weight: 800; + line-height: 18px; + text-align: center; + padding: 0; + box-shadow: 0 2px 6px rgba(255,107,107,0.35); +} +.StickyCartBar .icon .badge.pop { animation: badge-pop 280ms ease-out; } +@keyframes badge-pop { + 0% { transform: scale(0.8); } + 50% { transform: scale(1.2); } + 100% { transform: scale(1); } +} + +@media (max-width: 420px) { + .StickyCartBar { bottom: 16px; } + .StickyCartBar .cartBtn { height: 46px; } + .StickyCartBar .historyBtn { height: 46px; width: 48px; } } .App-link { diff --git a/src/pages/CafePage.js b/src/pages/CafePage.js index 4e80c72..df056d7 100644 --- a/src/pages/CafePage.js +++ b/src/pages/CafePage.js @@ -1,6 +1,6 @@ // src/CafePage.js -import React, { useState, useEffect } from "react"; +import React, { useState, useEffect, useRef } from "react"; import { useParams, useSearchParams, @@ -77,6 +77,40 @@ function CafePage({ const [config, setConfig] = useState({}); const [beingEditedType, setBeingEditedType] = useState(0); + // Sticky floating cart bar animations and visibility + const [cartBump, setCartBump] = useState(false); + const [barIntro, setBarIntro] = useState(false); + const [historyPulse, setHistoryPulse] = useState(false); + const prevShowRef = useRef(false); + const prevTxnRef = useRef(null); + const showBar = !isEditMode && (user.username || cartItemsLength > 0); + + useEffect(() => { + if (cartItemsLength > 0) { + setCartBump(true); + const t = setTimeout(() => setCartBump(false), 450); + return () => clearTimeout(t); + } + }, [cartItemsLength]); + + useEffect(() => { + if (showBar && !prevShowRef.current) { + setBarIntro(true); + const t = setTimeout(() => setBarIntro(false), 500); + prevShowRef.current = true; + return () => clearTimeout(t); + } + if (!showBar) prevShowRef.current = false; + }, [showBar]); + + useEffect(() => { + if (lastTransaction && lastTransaction !== prevTxnRef.current) { + setHistoryPulse(true); + const t = setTimeout(() => setHistoryPulse(false), 900); + prevTxnRef.current = lastTransaction; + return () => clearTimeout(t); + } + }, [lastTransaction]); // const checkWelcomePageConfig = () => { // const parsedConfig = JSON.parse(welcomePageConfig); @@ -321,29 +355,33 @@ function CafePage({ } /> ))} - {!isEditMode && (user.username || cartItemsLength > 0) && -
+ {showBar && +
{(lastTransaction != null || cartItemsLength > 0) && -
-
{lastTransaction != null && '+'}{cartItemsLength} item
-
+
{ if(e.key==='Enter'||e.key===' ') { e.preventDefault(); goToCart(); } }} onClick={goToCart} className={`cartBtn${cartBump ? ' bump' : ''}`}> +
+ {(lastTransaction != null) && +} + {cartItemsLength} item +
+
{((lastTransaction == null || lastTransaction?.payment_type != 'paylater')) ? - Rp{totalPrice} + {`Rp ${Number(totalPrice || 0).toLocaleString('id-ID')}`} : - Open bill + Open bill } -
+
+ {cartItemsLength > 0 && {cartItemsLength > 9 ? '9+' : cartItemsLength}}
} {user.username && -
0) ? '6px' : '0px' }}> -
-
+
{ if(e.key==='Enter'||e.key===' ') { e.preventDefault(); goToTransactions(); } }} onClick={goToTransactions} className={`historyBtn${historyPulse ? ' pulse' : ''}`}> +
+