// 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) => (

{f.title}

{f.desc}

))}
{/* 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.name}
{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 */}
{ setQuery(e.target.value); setPage(1); }} />
{CATEGORIES.map(c => ( ))}
{/* Results */}
{filtered.length} product{filtered.length !== 1 ? 's' : ''} found
{paginated.length > 0 ? (
{paginated.map(p => )}
) : (


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.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' ? (
setEmail(e.target.value)} placeholder="you@example.com" required />

Tip: Use "admin@…" or code "000000" for admin access

) : (
{otp.map((d,i) => ( inputRefs.current[i] = el} className={`otp-input${d?' filled':''}`} type="text" inputMode="numeric" maxLength={1} value={d} onChange={e => handleOtpKey(i, e.target.value)} onKeyDown={e => { if (e.key === 'Backspace' && !d && i > 0) inputRefs.current[i-1]?.focus(); }} /> ))}
{countdown > 0 ? ( Resend in {countdown}s ) : ( )}
)}
); }; // ── 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.label}
{s.value}
{s.up ? : } {s.change}
))}
{/* Recent Orders */}

Recent Orders

{MOCK_ORDERS.slice(0,5).map(o => ( ))}
OrderCustomerTotalStatusDate
{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}

{products.map(p => ( ))}
ImageNameCategoryPriceStockStatusActions
{p.name} {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}

setForm({...form,name:e.target.value})} />
setForm({...form,price:e.target.value})} />
setForm({...form,stock:e.target.value})} />