This commit is contained in:
karyamanswasta
2025-08-27 07:41:31 +07:00
parent dae7fb9221
commit 3fbe0bb870
2 changed files with 178 additions and 18 deletions

View File

@@ -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 {

View File

@@ -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) &&
<div className="StickyCartBar" style={{ marginTop: '10px', height: '40px', position: 'sticky', bottom: '40px', display: 'flex', justifyContent: 'center', alignItems: 'center', textAlign: 'center' }}>
{showBar &&
<div className={`StickyCartBar${barIntro ? ' intro' : ''}`}>
{(lastTransaction != null || cartItemsLength > 0) &&
<div onClick={goToCart} style={{ backgroundColor: '#73a585', width: user.username ? '55vw' : '70vw', height: '40px', borderRadius: '30px', display: 'flex', justifyContent: 'space-between', padding: '0 20px' }}>
<div style={{ display: 'flex', flexWrap: 'wrap', alignContent: 'center' }}>{lastTransaction != null && '+'}{cartItemsLength} item</div>
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'flex-end', width: '130px' }}>
<div role="button" tabIndex={0} aria-label="Buka keranjang" onKeyDown={(e)=>{ if(e.key==='Enter'||e.key===' ') { e.preventDefault(); goToCart(); } }} onClick={goToCart} className={`cartBtn${cartBump ? ' bump' : ''}`}>
<div className="summary">
{(lastTransaction != null) && <span>+</span>}
<span>{cartItemsLength} item</span>
</div>
<div className="summary" style={{ gap: 6 }}>
{((lastTransaction == null || lastTransaction?.payment_type != 'paylater')) ?
<span style={{ whiteSpace: 'nowrap' }}>Rp{totalPrice}</span>
<span className="value">{`Rp ${Number(totalPrice || 0).toLocaleString('id-ID')}`}</span>
:
<span style={{ whiteSpace: 'nowrap' }}>Open bill</span>
<span className="value">Open bill</span>
}
<div style={{ display: 'flex', alignItems: 'center', marginLeft: '5px', width: '20px' }}>
<div className="icon">
<svg viewBox="0 0 34 34" style={{ fill: 'white', marginTop: '4px' }}>
<path d="M9.79175 24.75C8.09591 24.75 6.72383 26.1375 6.72383 27.8333C6.72383 29.5292 8.09591 30.9167 9.79175 30.9167C11.4876 30.9167 12.8751 29.5292 12.8751 27.8333C12.8751 26.1375 11.4876 24.75 9.79175 24.75ZM0.541748 0.0833435V3.16668H3.62508L9.17508 14.8679L7.09383 18.645C6.84717 19.0767 6.70842 19.5854 6.70842 20.125C6.70842 21.8208 8.09591 23.2083 9.79175 23.2083H28.2917V20.125H10.4392C10.2234 20.125 10.0538 19.9554 10.0538 19.7396L10.1001 19.5546L11.4876 17.0417H22.973C24.1292 17.0417 25.1467 16.4096 25.6709 15.4538L31.1901 5.44834C31.3134 5.23251 31.3751 4.97043 31.3751 4.70834C31.3751 3.86043 30.6813 3.16668 29.8334 3.16668H7.03217L5.583 0.0833435H0.541748ZM25.2084 24.75C23.5126 24.75 22.1405 26.1375 22.1405 27.8333C22.1405 29.5292 23.5126 30.9167 25.2084 30.9167C26.9042 30.9167 28.2917 29.5292 28.2917 27.8333C28.2917 26.1375 26.9042 24.75 25.2084 24.75Z"></path>
</svg>
{cartItemsLength > 0 && <span className={`badge${cartBump ? ' pop' : ''}`}>{cartItemsLength > 9 ? '9+' : cartItemsLength}</span>}
</div>
</div>
</div>
}
{user.username &&
<div onClick={goToTransactions} style={{ backgroundColor: '#73a585', width: '15vw', height: '40px', borderRadius: '30px', display: 'flex', justifyContent: 'center', marginLeft: (lastTransaction != null || cartItemsLength > 0) ? '6px' : '0px' }}>
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', width: '38px', marginRight: '5px' }}>
<div style={{ display: 'flex', alignItems: 'center', marginLeft: '5px', width: '20px' }}>
<div role="button" tabIndex={0} aria-label="Lihat riwayat" onKeyDown={(e)=>{ if(e.key==='Enter'||e.key===' ') { e.preventDefault(); goToTransactions(); } }} onClick={goToTransactions} className={`historyBtn${historyPulse ? ' pulse' : ''}`}>
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', width: 38, marginRight: 6 }}>
<div className="icon">
<svg viewBox="0 0 512 512">
<g
transform="translate(0 460) scale(0.09 -0.09)"
@@ -391,3 +429,4 @@ function CafePage({
}
export default CafePage;