// src/App.jsx — Audical Services main app
const { useState, useEffect, useContext, createContext, useCallback, useRef, useMemo } = React;
// ── Context ──────────────────────────────────────────────────
const AppContext = createContext(null);
const useApp = () => useContext(AppContext);
// ── i18n ─────────────────────────────────────────────────────
const TRANSLATIONS = {
en: {
home: 'Home', products: 'Products', cart: 'Cart', login: 'Login',
logout: 'Logout', admin: 'Admin', search: 'Search products…',
addToCart: 'Add to Cart', checkout: 'Checkout', total: 'Total',
empty_cart: 'Your cart is empty', proceed: 'Proceed to Checkout',
otp_sent: 'OTP sent to your email', verify: 'Verify', resend: 'Resend',
name: 'Name', email: 'Email', price: 'Price', stock: 'In Stock',
dashboard: 'Dashboard', orders: 'Orders', customers: 'Customers',
settings: 'Settings', products_admin: 'Products', upload: 'Upload',
save: 'Save', cancel: 'Cancel', delete: 'Delete', edit: 'Edit',
add_product: 'Add Product', order_id: 'Order #', status: 'Status',
revenue: 'Revenue', welcome: 'Welcome back,', theme_toggle: 'Toggle theme',
language: 'Language', filter: 'Filter', all: 'All', category: 'Category',
description: 'Description', quantity: 'Qty', remove: 'Remove',
continue_shopping: 'Continue Shopping', place_order: 'Place Order',
payment_method: 'Payment Method', shipping: 'Shipping Info',
address: 'Address', phone: 'Phone', city: 'City', zip: 'ZIP',
export_pdf: 'Export PDF', new: 'New', active: 'Active', inactive: 'Inactive',
pending: 'Pending', completed: 'Completed', cancelled: 'Cancelled',
upload_image: 'Upload Image', drag_drop: 'Drag & drop or click to upload',
notifications: 'Notifications', mark_read: 'Mark all read',
no_notifications: 'No new notifications', role: 'Role',
password: 'Password', confirm_password: 'Confirm Password',
search_placeholder: 'Search by name, category…',
},
es: {
home: 'Inicio', products: 'Productos', cart: 'Carrito', login: 'Iniciar',
logout: 'Salir', admin: 'Admin', search: 'Buscar productos…',
addToCart: 'Agregar', checkout: 'Pagar', total: 'Total',
empty_cart: 'Tu carrito está vacío', proceed: 'Proceder al pago',
otp_sent: 'OTP enviado a tu correo', verify: 'Verificar', resend: 'Reenviar',
name: 'Nombre', email: 'Correo', price: 'Precio', stock: 'Disponible',
dashboard: 'Panel', orders: 'Pedidos', customers: 'Clientes',
settings: 'Ajustes', products_admin: 'Productos', upload: 'Subir',
save: 'Guardar', cancel: 'Cancelar', delete: 'Eliminar', edit: 'Editar',
add_product: 'Agregar Producto', order_id: 'Pedido #', status: 'Estado',
revenue: 'Ingresos', welcome: 'Bienvenido,', theme_toggle: 'Cambiar tema',
language: 'Idioma', filter: 'Filtro', all: 'Todo', category: 'Categoría',
description: 'Descripción', quantity: 'Cant', remove: 'Quitar',
continue_shopping: 'Seguir comprando', place_order: 'Hacer pedido',
payment_method: 'Método de pago', shipping: 'Envío',
address: 'Dirección', phone: 'Teléfono', city: 'Ciudad', zip: 'CP',
export_pdf: 'Exportar PDF', new: 'Nuevo', active: 'Activo', inactive: 'Inactivo',
pending: 'Pendiente', completed: 'Completado', cancelled: 'Cancelado',
upload_image: 'Subir imagen', drag_drop: 'Arrastra o haz clic',
notifications: 'Notificaciones', mark_read: 'Marcar todo leído',
no_notifications: 'Sin notificaciones', role: 'Rol',
password: 'Contraseña', confirm_password: 'Confirmar contraseña',
search_placeholder: 'Buscar por nombre, categoría…',
},
vi: {
home: 'Trang chủ', products: 'Sản phẩm', cart: 'Giỏ hàng', login: 'Đăng nhập',
logout: 'Đăng xuất', admin: 'Quản trị', search: 'Tìm sản phẩm…',
addToCart: 'Thêm vào giỏ', checkout: 'Thanh toán', total: 'Tổng',
empty_cart: 'Giỏ hàng trống', proceed: 'Tiến hành thanh toán',
otp_sent: 'OTP đã gửi', verify: 'Xác nhận', resend: 'Gửi lại',
name: 'Tên', email: 'Email', price: 'Giá', stock: 'Còn hàng',
dashboard: 'Bảng điều khiển', orders: 'Đơn hàng', customers: 'Khách hàng',
settings: 'Cài đặt', products_admin: 'Sản phẩm', upload: 'Tải lên',
save: 'Lưu', cancel: 'Hủy', delete: 'Xóa', edit: 'Sửa',
add_product: 'Thêm sản phẩm', order_id: 'Đơn #', status: 'Trạng thái',
revenue: 'Doanh thu', welcome: 'Chào mừng,', theme_toggle: 'Đổi giao diện',
language: 'Ngôn ngữ', filter: 'Lọc', all: 'Tất cả', category: 'Danh mục',
description: 'Mô tả', quantity: 'SL', remove: 'Xóa',
continue_shopping: 'Tiếp tục mua', place_order: 'Đặt hàng',
payment_method: 'Phương thức', shipping: 'Vận chuyển',
address: 'Địa chỉ', phone: 'SĐT', city: 'Thành phố', zip: 'Bưu chính',
export_pdf: 'Xuất PDF', new: 'Mới', active: 'Hoạt động', inactive: 'Không hoạt động',
pending: 'Chờ xử lý', completed: 'Hoàn thành', cancelled: 'Đã hủy',
upload_image: 'Tải ảnh', drag_drop: 'Kéo thả hoặc nhấn để tải',
notifications: 'Thông báo', mark_read: 'Đánh dấu đã đọc',
no_notifications: 'Không có thông báo', role: 'Vai trò',
password: 'Mật khẩu', confirm_password: 'Xác nhận mật khẩu',
search_placeholder: 'Tìm theo tên, danh mục…',
}
};
// ── Mock Data ─────────────────────────────────────────────────
const MOCK_PRODUCTS = [
{ id:1, name:'Digital Audiometer DA-500', category:'Audiometry', price:2499, image:'https://picsum.photos/seed/aud1/480/360', description:'Professional-grade digital audiometer for comprehensive hearing assessments.', stock:14, active:true },
{ id:2, name:'Hearing Aid HX Pro Max', category:'Hearing Aids', price:899, image:'https://picsum.photos/seed/aud2/480/360', description:'Advanced behind-the-ear hearing aid with Bluetooth connectivity.', stock:31, active:true },
{ id:3, name:'Otoscope OtoVision 3', category:'Diagnostic', price:349, image:'https://picsum.photos/seed/aud3/480/360', description:'LED fiber-optic otoscope with HD video capture.', stock:8, active:true },
{ id:4, name:'Tympanometer TM-200', category:'Audiometry', price:3199, image:'https://picsum.photos/seed/aud4/480/360', description:'Multi-frequency tympanometer with automated test sequences.', stock:5, active:true },
{ id:5, name:'Ear Cleaning Kit Pro', category:'Accessories', price:49, image:'https://picsum.photos/seed/aud5/480/360', description:'Professional irrigation kit with 3 tip sizes and pressure control.', stock:200, active:true },
{ id:6, name:'Sound Booth SB-Mini', category:'Audiometry', price:7499, image:'https://picsum.photos/seed/aud6/480/360', description:'Portable sound-attenuation booth for in-clinic audiometric testing.', stock:2, active:true },
{ id:7, name:'OAE Screener Swift', category:'Diagnostic', price:1850, image:'https://picsum.photos/seed/aud7/480/360', description:'Automated OAE hearing screening for newborn and pediatric use.', stock:12, active:true },
{ id:8, name:'Earmold Impression Kit', category:'Accessories', price:89, image:'https://picsum.photos/seed/aud8/480/360', description:'Silicone impression material set with mixing tips, 20 pairs.', stock:75, active:true },
{ id:9, name:'ABR System NeuroAud', category:'Diagnostic', price:12500, image:'https://picsum.photos/seed/aud9/480/360', description:'Auditory Brainstem Response system for neurodiagnostic testing.', stock:3, active:true },
];
const MOCK_ORDERS = [
{ id:'ORD-0041', customer:'Maria Lopez', email:'m.lopez@email.com', total:2499, status:'completed', date:'2025-05-18', items:1 },
{ id:'ORD-0042', customer:'James Kim', email:'j.kim@email.com', total:349, status:'pending', date:'2025-05-19', items:2 },
{ id:'ORD-0043', customer:'Sarah Chen', email:'s.chen@email.com', total:7499, status:'completed', date:'2025-05-20', items:1 },
{ id:'ORD-0044', customer:'Ahmed Osei', email:'a.osei@email.com', total:938, status:'cancelled', date:'2025-05-21', items:3 },
{ id:'ORD-0045', customer:'Lin Nguyen', email:'l.nguyen@email.com', total:1850, status:'pending', date:'2025-05-22', items:1 },
{ id:'ORD-0046', customer:'Tom Blake', email:'t.blake@email.com', total:138, status:'completed', date:'2025-05-23', items:4 },
];
const MOCK_CUSTOMERS = [
{ id:1, name:'Maria Lopez', email:'m.lopez@email.com', role:'customer', orders:4, joined:'2024-11-02', active:true },
{ id:2, name:'James Kim', email:'j.kim@email.com', role:'customer', orders:2, joined:'2025-01-15', active:true },
{ id:3, name:'Sarah Chen', email:'s.chen@email.com', role:'customer', orders:7, joined:'2024-08-30', active:true },
{ id:4, name:'Ahmed Osei', email:'a.osei@email.com', role:'customer', orders:1, joined:'2025-03-10', active:false },
{ id:5, name:'Lin Nguyen', email:'l.nguyen@email.com', role:'admin', orders:0, joined:'2024-06-01', active:true },
];
const CATEGORIES = ['All', 'Audiometry', 'Hearing Aids', 'Diagnostic', 'Accessories'];
// ── Icons ─────────────────────────────────────────────────────
const Icon = ({ name, size = 18, cls = '' }) => {
const icons = {
home: ,
products: ,
cart: ,
user: ,
sun: ,
moon: ,
search: ,
plus: ,
minus: ,
trash: ,
edit: ,
x: ,
check: ,
chevron_right: ,
chevron_left: ,
bell: ,
upload: ,
download: ,
bar_chart: ,
users: ,
settings: ,
credit_card: ,
package: ,
globe: ,
arrow_up: ,
arrow_down: ,
filter: ,
menu: ,
mail: ,
lock: ,
star: ,
activity: ,
trending_up: ,
};
return icons[name] || {name};
};
// ── Toast System ──────────────────────────────────────────────
let toastCounter = 0;
const toastListeners = [];
const addToast = (msg, type = 'info', duration = 3500) => {
const id = ++toastCounter;
toastListeners.forEach(fn => fn({ id, msg, type, duration }));
};
const ToastContainer = () => {
const [toasts, setToasts] = useState([]);
useEffect(() => {
const fn = t => setToasts(prev => [...prev, t]);
toastListeners.push(fn);
return () => toastListeners.splice(toastListeners.indexOf(fn), 1);
}, []);
const remove = id => setToasts(prev => prev.filter(t => t.id !== id));
const colors = { success: '#22c55e', error: '#ef4444', info: '#01c7fc', warning: '#f59e0b' };
return (
{toasts.map(t => (
{t.type === 'success' ? : t.type === 'error' ? : }
{t.msg}
{setTimeout(() => remove(t.id), t.duration) && null}
))}
);
};
// ── Navbar ────────────────────────────────────────────────────
const Navbar = () => {
const { page, setPage, cart, dark, setDark, lang, setLang, user, setUser, t, notifications } = useApp();
const [mobileOpen, setMobileOpen] = useState(false);
const [notifOpen, setNotifOpen] = useState(false);
const cartCount = cart.reduce((s, i) => s + i.qty, 0);
const unread = notifications.filter(n => !n.read).length;
return (
);
};
// ── Home Page ─────────────────────────────────────────────────
const HomePage = () => {
const { t, setPage } = useApp();
const features = [
{ icon:'package', title:'Professional Equipment', desc:'Sourced directly from leading manufacturers with full warranty.' },
{ icon:'check', title:'Fast Shipping', desc:'Next-day delivery on most items to your clinic or practice.' },
{ icon:'star', title:'Expert Support', desc:'Certified audiologists available for product guidance.' },
{ icon:'credit_card', title:'Flexible Payment', desc:'Accept Stripe and Square with installment options.' },
];
return (
{/* Hero */}
Professional Audiological Supplies
Premium Audiology Equipment & Supplies
Complete solutions for hearing clinics, hospitals, and private practices. Shop our curated catalog of audiometric tools and accessories.
{/* Features */}
{features.map((f, i) => (
))}
{/* Featured Products */}
Featured Products
{MOCK_PRODUCTS.slice(0,3).map(p =>
)}
{/* CTA */}
Ready to Upgrade Your Practice?
Create an account for exclusive pricing and expedited ordering.
);
};
// ── Product Card ──────────────────────────────────────────────
const ProductCard = ({ product }) => {
const { addToCart, t } = useApp();
return (
{product.category}
{product.name}
{product.description.slice(0, 80)}…
${product.price.toLocaleString()}
{t.stock}: {product.stock}
);
};
// ── Products Page ─────────────────────────────────────────────
const ProductsPage = () => {
const { t } = useApp();
const [query, setQuery] = useState('');
const [category, setCategory] = useState('All');
const [page, setPage] = useState(1);
const PER_PAGE = 6;
const filtered = useMemo(() => MOCK_PRODUCTS.filter(p =>
(category === 'All' || p.category === category) &&
p.name.toLowerCase().includes(query.toLowerCase())
), [query, category]);
const pages = Math.ceil(filtered.length / PER_PAGE);
const paginated = filtered.slice((page-1)*PER_PAGE, page*PER_PAGE);
return (
{t.products}
{/* Search & Filter */}
{/* Results */}
{filtered.length} product{filtered.length !== 1 ? 's' : ''} found
{paginated.length > 0 ? (
) : (
No products match your search.
)}
{/* Pagination */}
{pages > 1 && (
{Array.from({length:pages},(_,i)=>i+1).map(n => (
))}
)}
);
};
// ── Cart Page ─────────────────────────────────────────────────
const CartPage = () => {
const { cart, setCart, t, setPage } = useApp();
const total = cart.reduce((s, i) => s + i.price * i.qty, 0);
const updateQty = (id, qty) => {
if (qty < 1) return setCart(c => c.filter(i => i.id !== id));
setCart(c => c.map(i => i.id === id ? {...i, qty} : i));
};
if (cart.length === 0) return (
{t.empty_cart}
Add some products to get started
);
return (
{t.cart}
{/* Items */}
{cart.map(item => (
{item.name}
${item.price.toLocaleString()}
{item.qty}
${(item.price * item.qty).toLocaleString()}
))}
{/* Summary */}
Order Summary
{cart.map(i => (
{i.name} × {i.qty}
${(i.price * i.qty).toLocaleString()}
))}
{t.total}
${total.toLocaleString()}
);
};
// ── Checkout Page ─────────────────────────────────────────────
const CheckoutPage = () => {
const { cart, setCart, t, user, setPage, addNotification } = useApp();
const [step, setStep] = useState(1);
const [method, setMethod] = useState('stripe');
const [loading, setLoading] = useState(false);
const [form, setForm] = useState({ name: user?.name||'', email: user?.email||'', address:'', city:'', zip:'', phone:'' });
const total = cart.reduce((s, i) => s + i.price * i.qty, 0);
const field = (k, label, type='text') => (
setForm({...form,[k]:e.target.value})} />
);
const placeOrder = async () => {
setLoading(true);
await new Promise(r => setTimeout(r, 1800));
setLoading(false);
setCart([]);
addNotification(`Order placed for $${total.toLocaleString()}`);
addToast('Order placed successfully!', 'success');
setStep(3);
};
if (cart.length === 0 && step !== 3) return (
No items in cart
);
return (
{/* Steps */}
{[1,2,3].map((s,i) => (
= s ? 'var(--color-primary)' : 'var(--color-surface)', border: `2px solid ${step >= s ? 'var(--color-primary)' : 'var(--color-border)'}`, display:'flex', alignItems:'center', justifyContent:'center', color: step >= s ? '#fff' : 'var(--color-muted)', fontFamily:'Poppins', fontWeight:700, fontSize:14 }}>
{step > s ? : s}
= s ? 'var(--color-primary)' : 'var(--color-muted)', textTransform:'uppercase', letterSpacing:'0.06em' }}>
{['Shipping','Payment','Confirm'][i]}
{i < 2 && s+0.5 ? 'var(--color-primary)' : 'var(--color-border)', marginTop:17, alignSelf:'flex-start' }} />}
))}
{step === 1 && (
{t.shipping}
{field('name', t.name)}
{field('email', t.email, 'email')}
{field('phone', t.phone, 'tel')}
{field('address', t.address)}
{field('city', t.city)}
{field('zip', t.zip)}
)}
{step === 2 && (
{t.payment_method}
{['stripe','square'].map(m => (
))}
{/* Mock card inputs */}
{t.total}
${total.toLocaleString()}
)}
{step === 3 && (
Order Confirmed!
Thank you for your order. A confirmation email has been sent.
)}
);
};
// ── OTP Login ─────────────────────────────────────────────────
const LoginPage = () => {
const { setUser, setPage, t } = useApp();
const [stage, setStage] = useState('email');
const [email, setEmail] = useState('');
const [otp, setOtp] = useState(['','','','','','']);
const [loading, setLoading] = useState(false);
const [countdown, setCountdown] = useState(0);
const inputRefs = useRef([]);
const sendOtp = async (e) => {
e.preventDefault();
if (!email) return;
setLoading(true);
await new Promise(r => setTimeout(r, 1200));
setLoading(false);
setStage('otp');
setCountdown(60);
addToast(t.otp_sent, 'success');
};
useEffect(() => {
if (countdown > 0) {
const t = setTimeout(() => setCountdown(c => c-1), 1000);
return () => clearTimeout(t);
}
}, [countdown]);
const handleOtpKey = (i, val) => {
if (!/^\d?$/.test(val)) return;
const next = [...otp];
next[i] = val;
setOtp(next);
if (val && i < 5) inputRefs.current[i+1]?.focus();
};
const handleOtpPaste = (e) => {
const text = e.clipboardData.getData('text').replace(/\D/g, '').slice(0,6);
if (text.length === 6) {
setOtp(text.split(''));
inputRefs.current[5]?.focus();
}
};
const verifyOtp = async (e) => {
e.preventDefault();
const code = otp.join('');
if (code.length < 6) return;
setLoading(true);
await new Promise(r => setTimeout(r, 1000));
setLoading(false);
// Demo: any 6-digit code works; use "000000" for admin
const isAdmin = email.includes('admin') || code === '000000';
setUser({ name: email.split('@')[0], email, role: isAdmin ? 'admin' : 'customer' });
addToast(`Welcome, ${email.split('@')[0]}!`, 'success');
setPage(isAdmin ? 'admin' : 'home');
};
return (
{stage === 'email' ? t.login : 'Verify OTP'}
{stage === 'email' ? 'Enter your email to receive a one-time code' : `Code sent to ${email}`}
{stage === 'email' ? (
) : (
)}
);
};
// ── Admin Layout ──────────────────────────────────────────────
const AdminLayout = ({ children, activeTab, setActiveTab }) => {
const { t, user } = useApp();
const nav = [
{ key:'dashboard', icon:'bar_chart', label: t.dashboard },
{ key:'products', icon:'package', label: t.products_admin },
{ key:'orders', icon:'trending_up', label: t.orders },
{ key:'customers', icon:'users', label: t.customers },
{ key:'upload', icon:'upload', label: t.upload },
{ key:'settings', icon:'settings', label: t.settings },
];
return (
{children}
);
};
// ── Admin Dashboard ───────────────────────────────────────────
const AdminDashboard = () => {
const stats = [
{ label:'Revenue', value:'$48,230', change:'+12.4%', up:true, icon:'trending_up' },
{ label:'Orders', value:'184', change:'+8.1%', up:true, icon:'package' },
{ label:'Products', value:MOCK_PRODUCTS.length, change:'+2', up:true, icon:'products' },
{ label:'Customers', value:MOCK_CUSTOMERS.length, change:'+5.3%', up:true, icon:'users' },
];
return (
Dashboard
{/* Stats */}
{stats.map((s,i) => (
{s.value}
{s.up ? : } {s.change}
))}
{/* Recent Orders */}
Recent Orders
| Order | Customer | Total | Status | Date |
{MOCK_ORDERS.slice(0,5).map(o => (
| {o.id} |
{o.customer} |
${o.total.toLocaleString()} |
{o.status}
|
{o.date} |
))}
);
};
// ── Admin Products ────────────────────────────────────────────
const AdminProducts = () => {
const { t } = useApp();
const [products, setProducts] = useState(MOCK_PRODUCTS);
const [modal, setModal] = useState(null);
const [form, setForm] = useState({});
const openEdit = (p) => { setForm(p ? {...p} : { name:'', category:'Audiometry', price:'', description:'', stock:'', active:true }); setModal(p ? 'edit' : 'add'); };
const save = () => {
if (modal === 'edit') setProducts(ps => ps.map(p => p.id === form.id ? form : p));
else setProducts(ps => [...ps, { ...form, id: Date.now(), image: `https://picsum.photos/seed/${Date.now()}/480/360`, price: Number(form.price), stock: Number(form.stock) }]);
setModal(null);
addToast('Product saved', 'success');
};
const del = (id) => { setProducts(ps => ps.filter(p => p.id !== id)); addToast('Product deleted', 'error'); };
return (
{t.products_admin}
| Image | Name | Category | Price | Stock | Status | Actions |
{products.map(p => (
 |
{p.name} |
{p.category} |
${p.price.toLocaleString()} |
{p.stock} |
{p.active?'Active':'Inactive'} |
|
))}
{modal && (
e.target === e.currentTarget && setModal(null)}>
{modal === 'edit' ? t.edit : t.add_product}
)}
);
};
// ── Admin Orders ──────────────────────────────────────────────
const AdminOrders = () => {
const { t } = useApp();
const [orders, setOrders] = useState(MOCK_ORDERS);
const [filter, setFilter] = useState('all');
const filtered = filter === 'all' ? orders : orders.filter(o => o.status === filter);
const changeStatus = (id, status) => {
setOrders(os => os.map(o => o.id === id ? {...o, status} : o));
addToast(`Status updated to ${status}`, 'success');
};
return (
{t.orders}
{['all','pending','completed','cancelled'].map(f => (
))}
| Order | Customer | Items | Total | Status | Date | Action |
{filtered.map(o => (
| {o.id} |
{o.customer}
{o.email}
|
{o.items} |
${o.total.toLocaleString()} |
{o.status}
|
{o.date} |
|
))}
);
};
// ── Admin Customers ───────────────────────────────────────────
const AdminCustomers = () => {
const { t } = useApp();
return (
{t.customers}
| Name | Email | Role | Orders | Joined | Status |
{MOCK_CUSTOMERS.map(c => (
| {c.name} |
{c.email} |
{c.role} |
{c.orders} |
{c.joined} |
{c.active?'Active':'Inactive'} |
))}
);
};
// ── Admin Upload ──────────────────────────────────────────────
const AdminUpload = () => {
const { t } = useApp();
const [dragging, setDragging] = useState(false);
const [files, setFiles] = useState([]);
const inputRef = useRef();
const handleDrop = (e) => {
e.preventDefault();
setDragging(false);
const dropped = [...e.dataTransfer.files].filter(f => f.type.startsWith('image/'));
setFiles(prev => [...prev, ...dropped.map(f => ({ name:f.name, size:f.size, url:URL.createObjectURL(f) }))]);
addToast(`${dropped.length} file(s) added`, 'success');
};
const handleInput = (e) => {
const picked = [...e.target.files].filter(f => f.type.startsWith('image/'));
setFiles(prev => [...prev, ...picked.map(f => ({ name:f.name, size:f.size, url:URL.createObjectURL(f) }))]);
addToast(`${picked.length} file(s) added`, 'success');
};
return (
{t.upload}
{e.preventDefault();setDragging(true);}}
onDragLeave={()=>setDragging(false)}
onDrop={handleDrop}
onClick={()=>inputRef.current?.click()}>
{t.drag_drop}
PNG, JPG, WEBP up to 10MB
{files.length > 0 && (
Uploaded ({files.length})
{files.map((f,i) => (
{f.name}
{(f.size/1024).toFixed(1)}KB
))}
)}
);
};
// ── Admin Settings ────────────────────────────────────────────
const AdminSettings = () => {
const { dark, setDark, lang, setLang, t } = useApp();
const [saved, setSaved] = useState(false);
const save = () => { setSaved(true); addToast('Settings saved', 'success'); setTimeout(() => setSaved(false), 2000); };
return (
{t.settings}
Appearance
Dark Mode
Toggle dark/light theme
Language
Interface language
);
};
// ── Admin Page ────────────────────────────────────────────────
const AdminPage = () => {
const { user, setPage } = useApp();
const [tab, setTab] = useState('dashboard');
if (!user || user.role !== 'admin') return (
Admin Access Required
Please log in with admin credentials.
);
const tabs = { dashboard:
, products:
, orders:
, customers:
, upload:
, settings:
};
return
{tabs[tab] || };
};
// ── App Root ──────────────────────────────────────────────────
const App = () => {
const [page, setPage] = useState('home');
const [cart, setCart] = useState([]);
const [dark, setDark] = useState(false);
const [lang, setLang] = useState('en');
const [user, setUser] = useState(null);
const [notifications, setNotifications] = useState([
{ id:1, msg:'New order ORD-0045 received', time:'2 min ago', read:false },
{ id:2, msg:'Low stock: OAE Screener (2 left)', time:'1 hr ago', read:false },
{ id:3, msg:'Payment confirmed for ORD-0043', time:'3 hrs ago', read:true },
]);
const t = TRANSLATIONS[lang] || TRANSLATIONS.en;
const addToCart = useCallback((product) => {
setCart(prev => {
const existing = prev.find(i => i.id === product.id);
if (existing) {
addToast(`${product.name} qty updated`, 'success');
return prev.map(i => i.id === product.id ? {...i, qty: i.qty+1} : i);
}
addToast(`${product.name} added to cart`, 'success');
return [...prev, { ...product, qty: 1 }];
});
}, []);
const addNotification = useCallback((msg) => {
setNotifications(prev => [{ id: Date.now(), msg, time:'just now', read:false }, ...prev]);
}, []);
// Dark mode class on HTML
useEffect(() => {
document.documentElement.classList.toggle('dark', dark);
}, [dark]);
const PAGES = { home:
, products:
, cart:
, login:
, checkout:
, admin:
};
return (
{PAGES[page] ||
}
{/* Footer */}
);
};
// Mount
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
);