// 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 PORTAL54_URL = '/dashboard'; // ── Auth token + cart session (Sanctum Bearer / X-Cart-Session) ── const getToken = () => localStorage.getItem('auth_token'); const setToken = (token) => token ? localStorage.setItem('auth_token', token) : localStorage.removeItem('auth_token'); const getCartSession = () => { let id = localStorage.getItem('cart_session'); if (!id) { id = (crypto.randomUUID ? crypto.randomUUID() : `${Date.now()}-${Math.random().toString(36).slice(2)}`); localStorage.setItem('cart_session', id); } return id; }; const apiRequest = async (path, options = {}) => { const headers = { Accept: 'application/json', 'X-Cart-Session': getCartSession(), ...(options.headers || {}) }; const token = getToken(); if (token) headers['Authorization'] = `Bearer ${token}`; const res = await fetch(path, { ...options, headers }); const json = await res.json().catch(() => ({})); if (!res.ok || json?.success === false) { if (res.status === 401) setToken(null); const firstError = json?.errors ? Object.values(json.errors).flat()[0] : null; throw new Error(firstError || json?.error || json?.message || 'Request failed'); } return json; }; 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, logout, 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 ( ); }; // ── Calibration Trace Canvas ──────────────────────────────────── const CalibrationTrace = () => { const canvasRef = useRef(null); useEffect(() => { const canvas = canvasRef.current; if (!canvas) return; const ctx = canvas.getContext('2d'); const reduce = window.matchMedia('(prefers-reduced-motion: reduce)').matches; let raf, t = 0; function resize() { const rect = canvas.getBoundingClientRect(); canvas.width = rect.width * devicePixelRatio; canvas.height = rect.height * devicePixelRatio; ctx.setTransform(1,0,0,1,0,0); ctx.scale(devicePixelRatio, devicePixelRatio); } function draw() { const w = canvas.clientWidth, h = canvas.clientHeight; ctx.clearRect(0, 0, w, h); ctx.strokeStyle = 'rgba(255,255,255,0.06)'; ctx.lineWidth = 1; for (let i = 1; i < 5; i++) { ctx.beginPath(); ctx.moveTo(0, h*i/5); ctx.lineTo(w, h*i/5); ctx.stroke(); } ctx.beginPath(); ctx.strokeStyle = '#01C7FC'; ctx.lineWidth = 1.6; for (let x = 0; x <= w; x += 2) { const y = h/2 + Math.sin(x*0.05 + t)*18*Math.sin(x*0.008 + t*0.4) + Math.sin(x*0.13 + t*1.7)*6; x === 0 ? ctx.moveTo(x, y) : ctx.lineTo(x, y); } ctx.stroke(); if (!reduce) { t += 0.03; raf = requestAnimationFrame(draw); } } resize(); draw(); window.addEventListener('resize', resize); return () => { cancelAnimationFrame(raf); window.removeEventListener('resize', resize); }; }, []); return ; }; // ── Home Page ───────────────────────────────────────────────── const HomePage = () => { const { setPage, addToCart, products, t } = useApp(); const shopProducts = products; const pillars = [ { tag: 'PROTOCOL_01', title: 'Repair & Calibration', desc: 'Audiometers, tympanometers, and OAE screeners serviced and calibrated to manufacturer and ANSI spec.', items: ['Bench & on-site diagnostics', 'OEM-approved parts', 'Documented calibration report'], icon: , }, { tag: 'PROTOCOL_02', title: 'Sound Booth Lifecycle', desc: 'Install, dismantle, relocate, and rebuild sound-attenuating test suites without downtime surprises.', items: ['Site survey & acoustic spec', 'Full teardown / rebuild crews', 'New & certified pre-owned units'], icon: , }, { tag: 'PROTOCOL_03', title: 'State Certification', desc: 'Testing environments and equipment certified to meet State sound-quality and performance standards.', items: ['Ambient noise verification', 'Compliance documentation', 'Renewal scheduling & reminders'], icon: , }, ]; const capabilities = [ ['Diagnostic Audiometers', 'Repair · Calibration', '24–48 hrs'], ['Tympanometers', 'Repair · Calibration', '24–48 hrs'], ['OAE / Screening Systems', 'Repair · Calibration', '48–72 hrs'], ['Sound Booths', 'Install · Relocate · Rebuild', 'Scheduled'], ['Test Environments', 'State Certification', 'Scheduled'], ]; return (
AUDICAL // FIELD SERVICE NETWORK
001020304050
EST. COVERAGE 14 STATES
{/* Hero */}
Calibrated Field Service

Audiometric equipment,
serviced to spec, every time.

We repair, calibrate, and certify the instruments hearing clinics and hospitals depend on — audiometers, tympanometers, and OAE systems — plus full sound booth install, relocation, and rebuild.

LIVE CALIBRATION TRACE 250Hz – 8kHz
THRESHOLD DEVIATION: 0.3dBSTATUS: PASS
{/* Stats */}
4,200+
Instruments serviced annually
14
States with certified technicians
48hr
Average turnaround, standard repair
100%
State-compliant calibration records
{/* Capabilities */}
Core Capabilities

Three disciplines. One accountable vendor.

From diagnostic equipment on the bench to a fully built sound-attenuated suite — one team, one service record.

{pillars.map((p, i) => (
{p.tag}
{p.icon}

{p.title}

{p.desc}

    {p.items.map((it, j) =>
  • {it}
  • )}
))}
{/* Equipment for sale */}
Equipment Catalog

New and certified pre-owned, ready to ship.

The same instruments we repair and calibrate every day — sold, installed, and backed by the team that services them.

{shopProducts.slice(0, 4).map(p => (
{p.name}
{p.category}
{p.name}
${p.price.toLocaleString()} SERIAL UNIT
))}
{/* Capability matrix */}
Capability Matrix

What we service, by equipment class

{capabilities.map(([eq, svc, time], i) => ( ))}
EquipmentServiceTurnaround
{eq}{svc}{time}
Note

Every service ticket closes with a written calibration or compliance record — the same document your next State inspection or accreditation review will ask for.

We do not service hearing aids. Our scope is the instrumentation and environments clinics use to test and evaluate hearing.

{/* Certification */}
STATE CERTIFIED AUDICAL SERVICES
Compliance

Certified to the standard your inspector actually checks.

We test and document ambient noise levels, equipment calibration, and booth performance against your State's audiology equipment requirements — filed and ready before renewal season.

{['CA','TX','FL','NY','PA','OH','IL','+7 more'].map(s => {s})}
{/* CTA */}

Get your equipment back in spec.

Call (555) 019-2244
); }; // ── Product Card ────────────────────────────────────────────── const ProductCard = ({ product }) => { const { addToCart, viewProduct, t } = useApp(); return (
viewProduct(product.id)} role="button" tabIndex={0} onKeyDown={e => { if (e.key === 'Enter') viewProduct(product.id); }}> {product.name}
{product.category}
{product.name}

{(product.description || '').slice(0, 80)}…

${product.price.toLocaleString()}
{product.manufacturer ? `${product.manufacturer} · ` : ''}Individually serialized unit
); }; // ── Products Page ───────────────────────────────────────────── const ProductsPage = () => { const { t, products, categories } = useApp(); const [query, setQuery] = useState(''); const [category, setCategory] = useState('All'); const [page, setPage] = useState(1); const PER_PAGE = 6; const filtered = useMemo(() => products.filter(p => (category === 'All' || p.category === category) && p.name.toLowerCase().includes(query.toLowerCase()) ), [products, 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 => ( ))}
)}
); }; // ── Product Detail Page ───────────────────────────────────────── const ProductDetailPage = () => { const { selectedProductId, setPage, addToCart, t } = useApp(); const [product, setProduct] = useState(null); const [activeImage, setActiveImage] = useState(0); const [loading, setLoading] = useState(true); const [notFound, setNotFound] = useState(false); useEffect(() => { setLoading(true); setNotFound(false); setProduct(null); setActiveImage(0); if (!selectedProductId) return; apiRequest(`/api/products/${selectedProductId}`) .then(json => setProduct(json.data.product)) .catch(() => setNotFound(true)) .finally(() => setLoading(false)); }, [selectedProductId]); if (loading) { return (
Loading…
); } if (notFound || !product) { return (


Product not found.

); } const images = product.images && product.images.length > 0 ? product.images : [{ file_path: product.image, alt_text: product.name, is_primary: true }]; return (
{images[activeImage]?.alt_text {images.length > 1 && (
{images.map((img, i) => ( {img.alt_text setActiveImage(i)} /> ))}
)}
{product.category}

{product.name}

{product.model_number && (
Model #{product.model_number}
)}
${product.price.toLocaleString()}

{product.description || 'No description available.'}

{product.manufacturer ? `${product.manufacturer} · ` : ''}Individually serialized unit
); }; // ── Cart Page ───────────────────────────────────────────────── const CartPage = () => { const { cart, removeFromCart, t, setPage } = useApp(); const total = cart.reduce((s, i) => s + i.price * i.qty, 0); 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()}
QTY 1 · SERIAL UNIT
${(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, refreshCart, t, user, setPage, addNotification } = useApp(); const [step, setStep] = useState(1); const [loading, setLoading] = useState(false); const [order, setOrder] = useState(null); const [payError, setPayError] = useState(null); const [form, setForm] = useState({ name: user?.name||'', email: user?.email||'', address:'', city:'', zip:'', phone:'' }); const stripeRef = useRef(null); const elementsRef = useRef(null); const paymentElRef = useRef(null); 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 beginPayment = async () => { setLoading(true); setPayError(null); try { const json = await apiRequest('/api/checkout', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ shipping: form }), }); const { client_secret, publishable_key, order: createdOrder } = json.data; if (!publishable_key) throw new Error('Payments are not configured yet.'); setOrder(createdOrder); setStep(2); stripeRef.current = Stripe(publishable_key); elementsRef.current = stripeRef.current.elements({ clientSecret: client_secret }); setTimeout(() => { const paymentElement = elementsRef.current.create('payment'); paymentElement.mount(paymentElRef.current); }, 0); } catch (err) { addToast(err.message || 'Unable to start checkout', 'error'); if (/no longer available|empty/i.test(err.message || '')) refreshCart(); } finally { setLoading(false); } }; const pollOrderStatus = async (orderNumber) => { for (let attempt = 0; attempt < 15; attempt++) { try { const json = await apiRequest(`/api/orders/${orderNumber}`); const status = json.data.order.status; if (status === 'paid') return 'paid'; if (status === 'payment_failed' || status === 'requires_refund') return status; } catch (err) { /* transient; keep polling */ } await new Promise(r => setTimeout(r, 2000)); } return 'processing'; }; const placeOrder = async () => { if (!stripeRef.current || !elementsRef.current) return; setLoading(true); setPayError(null); const { error } = await stripeRef.current.confirmPayment({ elements: elementsRef.current, redirect: 'if_required', confirmParams: { return_url: window.location.origin + '/' }, }); if (error) { setPayError(error.message); setLoading(false); return; } const status = await pollOrderStatus(order.order_number); setLoading(false); if (status === 'paid' || status === 'processing') { refreshCart(); addNotification(`Order ${order.order_number} placed for $${total.toLocaleString()}`); addToast(status === 'paid' ? 'Payment confirmed!' : 'Payment received — order is processing.', 'success'); setStep(3); } else { setPayError('Payment could not be completed. You have not been charged, or a refund will be issued.'); } }; if (!user) return (

Login Required

Sign in with your email to complete checkout.

); 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}

{order && (
Order {order.order_number}
)}
{payError && (
{payError}
)}
{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, refreshCart, 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); try { await apiRequest('/api/auth/otp', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ email }), }); setStage('otp'); setCountdown(60); addToast(t.otp_sent, 'success'); } catch (err) { addToast(err.message || 'Unable to send OTP', 'error'); } finally { setLoading(false); } }; const resendOtp = async () => { if (!email || loading || countdown > 0) return; setLoading(true); try { await apiRequest('/api/auth/otp', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ email }), }); setOtp(['','','','','','']); setCountdown(60); addToast('New OTP sent!', 'info'); } catch (err) { addToast(err.message || 'Unable to resend OTP', 'error'); } finally { setLoading(false); } }; 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); try { const json = await apiRequest('/api/auth/verify', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ email, otp: code }), }); const verifiedUser = json.data.user; setToken(json.data.token); setUser(verifiedUser); addToast(`Welcome, ${verifiedUser.name}!`, 'success'); if (verifiedUser.role === 'admin') { window.location.assign(json.data.sso_url || PORTAL54_URL); return; } refreshCart(); setPage('home'); } catch (err) { addToast(err.message || 'Unable to verify OTP', 'error'); } finally { setLoading(false); } }; 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 />
) : (
{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 ) : ( )}
)}
); }; // ── Customer Orders ─────────────────────────────────────────── const OrdersPage = () => { const { t, setPage, user } = useApp(); const [orders, setOrders] = useState(null); useEffect(() => { if (!user) return; apiRequest('/api/orders') .then(json => setOrders(json.data.orders || [])) .catch(() => setOrders([])); }, [user]); if (!user) return (

Login Required

); const badge = (status) => status === 'paid' ? 'success' : (status === 'payment_failed' || status === 'requires_refund' || status === 'expired') ? 'danger' : 'primary'; return (

{t.orders}

{orders === null ? (
Loading…
) : orders.length === 0 ? (
No orders yet.
) : (
{orders.map(o => ( ))}
{t.order_id}{t.status}Date{t.total}
{o.order_number} {o.status} {(o.date || '').slice(0, 10)} ${o.grand_total.toLocaleString()}
)}
); }; // ── 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})} />