Dashboard search made functional
This commit is contained in:
BIN
Ritz/.DS_Store
vendored
BIN
Ritz/.DS_Store
vendored
Binary file not shown.
BIN
Ritz/Ritz.psd
BIN
Ritz/Ritz.psd
Binary file not shown.
BIN
Ritz/download.png
Normal file
BIN
Ritz/download.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 13 KiB |
BIN
Ritz/image-removebg-preview (6) copy.png
Normal file
BIN
Ritz/image-removebg-preview (6) copy.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 348 KiB |
BIN
frontend/src/assets/front.png
Normal file
BIN
frontend/src/assets/front.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.0 MiB |
@@ -1,7 +1,58 @@
|
|||||||
import { Bell, Search, Settings, HelpCircle, Calendar } from 'lucide-react';
|
import { useState, useEffect, useRef } from 'react';
|
||||||
import { Link } from 'react-router-dom';
|
import {
|
||||||
|
Bell,
|
||||||
|
Search,
|
||||||
|
Settings,
|
||||||
|
HelpCircle,
|
||||||
|
Calendar,
|
||||||
|
LayoutDashboard,
|
||||||
|
Store,
|
||||||
|
ShoppingBag,
|
||||||
|
History,
|
||||||
|
Users,
|
||||||
|
Package,
|
||||||
|
Layers,
|
||||||
|
FileText,
|
||||||
|
MessageSquare,
|
||||||
|
Wallet,
|
||||||
|
Ticket,
|
||||||
|
Cpu,
|
||||||
|
UserCheck,
|
||||||
|
TrendingUp,
|
||||||
|
ChevronRight
|
||||||
|
} from 'lucide-react';
|
||||||
|
import { Link, useNavigate } from 'react-router-dom';
|
||||||
|
import { motion, AnimatePresence } from 'framer-motion';
|
||||||
|
|
||||||
|
const PAGES = [
|
||||||
|
{ label: 'General Dashboard', path: '/dashboard', category: 'Analytics', icon: <LayoutDashboard size={14} /> },
|
||||||
|
{ label: 'Store Dashboard', path: '/store-dashboard', category: 'Analytics', icon: <Store size={14} /> },
|
||||||
|
{ label: 'Active Orders', path: '/sale/orders', category: 'Sales', icon: <ShoppingBag size={14} /> },
|
||||||
|
{ label: 'Archived Bills', path: '/sale/archived-bills', category: 'Sales', icon: <History size={14} /> },
|
||||||
|
{ label: 'Manage Products', path: '/inventory/products', category: 'Inventory', icon: <Package size={14} /> },
|
||||||
|
{ label: 'Base Menu', path: '/inventory/base', category: 'Inventory', icon: <Layers size={14} /> },
|
||||||
|
{ label: 'Ritz Overview', path: '/ritz/overview', category: 'Finance', icon: <Wallet size={14} /> },
|
||||||
|
{ label: 'Manage Wallets', path: '/ritz/wallets', category: 'Finance', icon: <Users size={14} /> },
|
||||||
|
{ label: 'Coupon Codes', path: '/ritz/coupons', category: 'Finance', icon: <Ticket size={14} /> },
|
||||||
|
{ label: 'Reports & Analytics', path: '/reports', category: 'Support', icon: <FileText size={14} /> },
|
||||||
|
{ label: 'Customer Feedback', path: '/feedback', category: 'Support', icon: <MessageSquare size={14} /> },
|
||||||
|
{ label: 'Staff Management', path: '/stores/staffs', category: 'Team', icon: <UserCheck size={14} /> },
|
||||||
|
{ label: 'Store Terminals', path: '/stores/terminals', category: 'System', icon: <Cpu size={14} /> },
|
||||||
|
{ label: 'System Settings', path: '/settings', category: 'System', icon: <Settings size={14} /> },
|
||||||
|
];
|
||||||
|
|
||||||
const Header = () => {
|
const Header = () => {
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const searchRef = useRef<HTMLDivElement>(null);
|
||||||
|
const [searchTerm, setSearchTerm] = useState('');
|
||||||
|
const [showResults, setShowResults] = useState(false);
|
||||||
|
const [selectedIndex, setSelectedIndex] = useState(0);
|
||||||
|
|
||||||
|
const filteredPages = PAGES.filter(page =>
|
||||||
|
page.label.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
||||||
|
page.category.toLowerCase().includes(searchTerm.toLowerCase())
|
||||||
|
).slice(0, 8);
|
||||||
|
|
||||||
const currentDate = new Date().toLocaleDateString('en-US', {
|
const currentDate = new Date().toLocaleDateString('en-US', {
|
||||||
weekday: 'long',
|
weekday: 'long',
|
||||||
year: 'numeric',
|
year: 'numeric',
|
||||||
@@ -9,16 +60,97 @@ const Header = () => {
|
|||||||
day: 'numeric',
|
day: 'numeric',
|
||||||
});
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const handleClickOutside = (event: MouseEvent) => {
|
||||||
|
if (searchRef.current && !searchRef.current.contains(event.target as Node)) {
|
||||||
|
setShowResults(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
document.addEventListener('mousedown', handleClickOutside);
|
||||||
|
return () => document.removeEventListener('mousedown', handleClickOutside);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleKeyDown = (e: React.KeyboardEvent) => {
|
||||||
|
if (e.key === 'ArrowDown') {
|
||||||
|
setSelectedIndex(prev => (prev + 1) % filteredPages.length);
|
||||||
|
} else if (e.key === 'ArrowUp') {
|
||||||
|
setSelectedIndex(prev => (prev - 1 + filteredPages.length) % filteredPages.length);
|
||||||
|
} else if (e.key === 'Enter' && filteredPages[selectedIndex]) {
|
||||||
|
handleNavigate(filteredPages[selectedIndex].path);
|
||||||
|
} else if (e.key === 'Escape') {
|
||||||
|
setShowResults(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleNavigate = (path: string) => {
|
||||||
|
navigate(path);
|
||||||
|
setSearchTerm('');
|
||||||
|
setShowResults(false);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<header className="h-16 bg-white/80 backdrop-blur-md border-b border-[#e2e8f0] px-8 flex items-center justify-between sticky top-0 z-40">
|
<header className="h-16 bg-white/80 backdrop-blur-md border-b border-[#e2e8f0] px-8 flex items-center justify-between sticky top-0 z-50">
|
||||||
<div className="flex items-center gap-4 flex-1">
|
<div className="flex items-center gap-4 flex-1">
|
||||||
<div className="relative group w-full max-w-md">
|
<div className="relative group w-full max-w-md" ref={searchRef}>
|
||||||
<Search className="absolute left-3 top-1/2 -translate-y-1/2 text-[#64748b] group-focus-within:text-[#231651] transition-colors" size={18} />
|
<Search className="absolute left-3 top-1/2 -translate-y-1/2 text-[#64748b] group-focus-within:text-[#0f4475] transition-colors" size={18} />
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="Search dashboard..."
|
placeholder="Search pages, settings, or analytics..."
|
||||||
className="w-full bg-gray-50 border border-transparent rounded-lg py-2 pl-10 pr-4 text-sm focus:outline-none focus:bg-white focus:border-[#231651]/30 transition-all shadow-sm"
|
className="w-full bg-gray-50 border border-transparent rounded-lg py-2 pl-10 pr-4 text-sm focus:outline-none focus:bg-white focus:border-[#0f4475]/30 transition-all shadow-sm"
|
||||||
|
value={searchTerm}
|
||||||
|
onChange={(e) => {
|
||||||
|
setSearchTerm(e.target.value);
|
||||||
|
setShowResults(true);
|
||||||
|
setSelectedIndex(0);
|
||||||
|
}}
|
||||||
|
onFocus={() => setShowResults(true)}
|
||||||
|
onKeyDown={handleKeyDown}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<AnimatePresence>
|
||||||
|
{showResults && searchTerm && (
|
||||||
|
<motion.div
|
||||||
|
initial={{ opacity: 0, y: 10, scale: 0.95 }}
|
||||||
|
animate={{ opacity: 1, y: 0, scale: 1 }}
|
||||||
|
exit={{ opacity: 0, y: 5, scale: 0.95 }}
|
||||||
|
transition={{ duration: 0.1 }}
|
||||||
|
className="absolute top-full left-0 right-0 mt-2 bg-white/95 backdrop-blur-xl border border-[#e2e8f0] rounded-xl shadow-2xl overflow-hidden z-50"
|
||||||
|
>
|
||||||
|
{filteredPages.length > 0 ? (
|
||||||
|
<div className="py-2">
|
||||||
|
{filteredPages.map((page, index) => (
|
||||||
|
<button
|
||||||
|
key={page.path}
|
||||||
|
onClick={() => handleNavigate(page.path)}
|
||||||
|
onMouseEnter={() => setSelectedIndex(index)}
|
||||||
|
className={`w-full px-4 py-2.5 flex items-center justify-between transition-colors ${index === selectedIndex ? 'bg-[#0f4475]/5' : ''}`}
|
||||||
|
>
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
<div className={`p-1.5 rounded-lg transition-colors ${index === selectedIndex ? 'bg-[#0f4475] text-white' : 'bg-gray-100 text-gray-400'}`}>
|
||||||
|
{page.icon}
|
||||||
|
</div>
|
||||||
|
<div className="text-left">
|
||||||
|
<div className={`text-sm font-bold ${index === selectedIndex ? 'text-[#0f4475]' : 'text-slate-700'}`}>
|
||||||
|
{page.label}
|
||||||
|
</div>
|
||||||
|
<div className="text-[10px] uppercase tracking-widest font-black opacity-40">
|
||||||
|
{page.category}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<ChevronRight className={`transition-transform duration-300 ${index === selectedIndex ? 'translate-x-0 opacity-100' : '-translate-x-2 opacity-0'}`} size={14} />
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="p-8 text-center">
|
||||||
|
<HelpCircle className="mx-auto text-slate-200 mb-2" size={32} />
|
||||||
|
<p className="text-xs font-bold text-slate-400 uppercase tracking-widest">No matching pages found</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</motion.div>
|
||||||
|
)}
|
||||||
|
</AnimatePresence>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -34,7 +166,7 @@ const Header = () => {
|
|||||||
<span className="absolute top-2 right-2 w-2 h-2 bg-red-500 rounded-full border-2 border-white"></span>
|
<span className="absolute top-2 right-2 w-2 h-2 bg-red-500 rounded-full border-2 border-white"></span>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<Link to="/settings" className="p-2 text-[#64748b] hover:text-[#231651] hover:bg-indigo-50 rounded-lg transition-all">
|
<Link to="/settings" className="p-2 text-[#64748b] hover:text-[#0f4475] hover:bg-indigo-50 rounded-lg transition-all">
|
||||||
<Settings size={20} />
|
<Settings size={20} />
|
||||||
</Link>
|
</Link>
|
||||||
|
|
||||||
|
|||||||
@@ -38,43 +38,66 @@
|
|||||||
cursor: default;
|
cursor: default;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ritz-payment-card.insufficient {
|
.ritz-payment-card.insufficient-state {
|
||||||
opacity: 0.8;
|
border: 1px solid rgba(244, 63, 94, 0.3);
|
||||||
border-style: dashed;
|
background: rgba(244, 63, 94, 0.03);
|
||||||
background: rgba(239, 68, 68, 0.02);
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ritz-icon {
|
.ritz-payment-card.insufficient-state::before {
|
||||||
background: #6366f1;
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
width: 4px;
|
||||||
|
background: #f43f5e;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-badge-premium {
|
||||||
|
font-size: 0.65rem;
|
||||||
|
font-weight: 900;
|
||||||
|
padding: 0.25rem 0.75rem;
|
||||||
|
border-radius: 99px;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.05em;
|
||||||
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-badge-premium.error {
|
||||||
|
background: #f43f5e;
|
||||||
color: white;
|
color: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.payment-icon.ritz-token-avatar {
|
||||||
|
background: transparent;
|
||||||
|
width: 54px;
|
||||||
|
height: 54px;
|
||||||
|
transform: scale(1.1);
|
||||||
|
filter: drop-shadow(0 4px 12px rgba(99, 102, 241, 0.2));
|
||||||
|
}
|
||||||
|
|
||||||
|
.token-image-main {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: contain;
|
||||||
|
}
|
||||||
|
|
||||||
.payment-name-row {
|
.payment-name-row {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
margin-bottom: 0.5rem;
|
margin-bottom: 4px;
|
||||||
}
|
|
||||||
|
|
||||||
.status-badge {
|
|
||||||
font-size: 0.7rem;
|
|
||||||
font-weight: 800;
|
|
||||||
padding: 0.2rem 0.6rem;
|
|
||||||
border-radius: 20px;
|
|
||||||
text-transform: uppercase;
|
|
||||||
}
|
|
||||||
|
|
||||||
.status-badge.error {
|
|
||||||
background: rgba(239, 68, 68, 0.1);
|
|
||||||
color: #ef4444;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.wallet-balance-info {
|
.wallet-balance-info {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 0.4rem;
|
gap: 6px;
|
||||||
font-size: 0.85rem;
|
font-size: 0.8rem;
|
||||||
color: var(--text-secondary);
|
color: #64748b;
|
||||||
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
|
|
||||||
.balance-val {
|
.balance-val {
|
||||||
@@ -82,40 +105,49 @@
|
|||||||
color: #6366f1;
|
color: #6366f1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.insufficient-val {
|
||||||
|
color: #f43f5e !important;
|
||||||
|
text-decoration: line-through;
|
||||||
|
opacity: 0.7;
|
||||||
|
}
|
||||||
|
|
||||||
.add-tokens-checkout-btn {
|
.add-tokens-checkout-btn {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
margin-top: 1rem;
|
margin-top: 12px;
|
||||||
background: white;
|
background: white;
|
||||||
border: 1px dashed #6366f1;
|
border: 1.5px dashed rgba(99, 102, 241, 0.4);
|
||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
border-radius: 16px;
|
border-radius: 18px;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
color: #6366f1;
|
color: #6366f1;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: all 0.2s ease;
|
transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
.add-tokens-checkout-btn:active {
|
.add-tokens-checkout-btn:active {
|
||||||
background: rgba(99, 102, 241, 0.05);
|
background: rgba(99, 102, 241, 0.05);
|
||||||
|
transform: scale(0.98);
|
||||||
}
|
}
|
||||||
|
|
||||||
.add-tokens-checkout-btn .btn-content {
|
.add-tokens-checkout-btn .btn-content {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 0.75rem;
|
gap: 10px;
|
||||||
font-weight: 700;
|
font-weight: 800;
|
||||||
|
font-size: 0.9rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.payment-security-note {
|
.payment-security-note {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
gap: 0.5rem;
|
gap: 6px;
|
||||||
margin-top: 1.5rem;
|
margin-top: 1.5rem;
|
||||||
color: var(--text-secondary);
|
color: #94a3b8;
|
||||||
font-size: 0.8rem;
|
font-size: 0.75rem;
|
||||||
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ritz-text {
|
.ritz-text {
|
||||||
@@ -123,26 +155,66 @@
|
|||||||
font-weight: 800;
|
font-weight: 800;
|
||||||
}
|
}
|
||||||
|
|
||||||
.insufficient-warning {
|
.insufficient-warning-premium {
|
||||||
margin: 1.5rem;
|
margin: 1.5rem;
|
||||||
padding: 1rem;
|
padding: 1.25rem;
|
||||||
background: rgba(255, 149, 0, 0.1);
|
background: white;
|
||||||
border-radius: 16px;
|
border: 1px solid rgba(244, 63, 94, 0.1);
|
||||||
|
border-radius: 24px;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 1rem;
|
gap: 1.25rem;
|
||||||
color: #cc7700;
|
box-shadow: 0 12px 30px -10px rgba(244, 63, 94, 0.15);
|
||||||
}
|
}
|
||||||
|
|
||||||
.warning-text h3 {
|
.token-warning-visual {
|
||||||
font-size: 0.95rem;
|
width: 60px;
|
||||||
font-weight: 700;
|
height: 60px;
|
||||||
margin-bottom: 0.1rem;
|
position: relative;
|
||||||
|
background: transparent;
|
||||||
|
filter: drop-shadow(0 6px 15px rgba(244, 63, 94, 0.2));
|
||||||
}
|
}
|
||||||
|
|
||||||
.warning-text p {
|
.token-image-mini {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: contain;
|
||||||
|
transform: rotate(-10deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.warning-icon-overlay {
|
||||||
|
position: absolute;
|
||||||
|
bottom: -2px;
|
||||||
|
right: -2px;
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
background: #f43f5e;
|
||||||
|
color: white;
|
||||||
|
border-radius: 8px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
border: 2px solid white;
|
||||||
|
box-shadow: 0 4px 8px rgba(244, 63, 94, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.shortfall-amount {
|
||||||
|
font-size: 1rem;
|
||||||
|
font-weight: 800;
|
||||||
|
color: #334155;
|
||||||
|
margin-bottom: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.shortfall-amount .highlight {
|
||||||
|
color: #f43f5e;
|
||||||
|
font-size: 1.1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.warning-instruction {
|
||||||
font-size: 0.8rem;
|
font-size: 0.8rem;
|
||||||
opacity: 0.9;
|
font-weight: 500;
|
||||||
|
color: #64748b;
|
||||||
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ritz-order-btn {
|
.ritz-order-btn {
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import {
|
|||||||
ShieldCheck,
|
ShieldCheck,
|
||||||
PlusCircle
|
PlusCircle
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
|
import tokenImage from '../assets/display_ritz.png';
|
||||||
import { motion, AnimatePresence } from 'framer-motion';
|
import { motion, AnimatePresence } from 'framer-motion';
|
||||||
import Header from '../components/Header';
|
import Header from '../components/Header';
|
||||||
import StockConflictModal from '../components/StockConflictModal';
|
import StockConflictModal from '../components/StockConflictModal';
|
||||||
@@ -139,21 +140,29 @@ const CheckoutScreen: React.FC = () => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="payment-options">
|
<div className="payment-options">
|
||||||
<div className={`payment-card ritz-payment-card ${isInsufficient ? 'insufficient' : 'selected'}`}>
|
<div className={`payment-card ritz-payment-card ${isInsufficient ? 'insufficient-state' : 'selected'}`}>
|
||||||
<div className="payment-icon ritz-icon">
|
<div className="payment-icon ritz-token-avatar">
|
||||||
<CircleDollarSign size={24} />
|
<img src={tokenImage} alt="Ritz Token" className="token-image-main" />
|
||||||
</div>
|
</div>
|
||||||
<div className="payment-info">
|
<div className="payment-info">
|
||||||
<div className="payment-name-row">
|
<div className="payment-name-row">
|
||||||
<span className="payment-name">Pay with Ritz Tokens</span>
|
<span className="payment-name">Pay with Ritz Tokens</span>
|
||||||
{isInsufficient && (
|
{isInsufficient && (
|
||||||
<span className="status-badge error">Insufficient Balance</span>
|
<motion.span
|
||||||
|
initial={{ scale: 0.9, opacity: 0 }}
|
||||||
|
animate={{ scale: 1, opacity: 1 }}
|
||||||
|
className="status-badge-premium error"
|
||||||
|
>
|
||||||
|
Insufficient
|
||||||
|
</motion.span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="wallet-balance-info">
|
<div className="wallet-balance-info">
|
||||||
<Wallet size={14} />
|
<Wallet size={14} className={isInsufficient ? 'text-rose-400' : ''} />
|
||||||
<span>Current Balance: </span>
|
<span>Your Balance: </span>
|
||||||
<span className="balance-val">R{currentBalance.toLocaleString()}</span>
|
<span className={`balance-val ${isInsufficient ? 'insufficient-val' : ''}`}>
|
||||||
|
R{currentBalance.toLocaleString()}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{!isInsufficient && (
|
{!isInsufficient && (
|
||||||
@@ -194,13 +203,24 @@ const CheckoutScreen: React.FC = () => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{isInsufficient && (
|
{isInsufficient && (
|
||||||
<div className="insufficient-warning">
|
<motion.div
|
||||||
<AlertCircle size={20} />
|
className="insufficient-warning-premium"
|
||||||
<div className="warning-text">
|
initial={{ opacity: 0, y: 20 }}
|
||||||
<strong>Short by R{(totalPrice - currentBalance).toLocaleString()}</strong>
|
animate={{ opacity: 1, y: 0 }}
|
||||||
<p>Add more tokens to complete your order.</p>
|
>
|
||||||
|
<div className="warning-icon-wrapper token-warning-visual">
|
||||||
|
<img src={tokenImage} alt="" className="token-image-mini" />
|
||||||
|
<div className="warning-icon-overlay">
|
||||||
|
<AlertCircle size={16} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="warning-content">
|
||||||
|
<div className="shortfall-amount">
|
||||||
|
Short by <span className="highlight">R{(totalPrice - currentBalance).toLocaleString()}</span>
|
||||||
|
</div>
|
||||||
|
<p className="warning-instruction">Add tokens to your wallet to complete this order.</p>
|
||||||
|
</div>
|
||||||
|
</motion.div>
|
||||||
)}
|
)}
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user