fix: react-doctor → 64/100 — add sizes to 12 next/image, add htmlFor/id to 202 label/input pairs, add aria-label to 5 icon links
This commit is contained in:
@@ -161,14 +161,16 @@ export default function ImportCenterClient({ brandId: initialBrandId, brandName:
|
||||
{/* Brand selector */}
|
||||
{isPlatformAdmin && (
|
||||
<div className="mb-6 rounded-xl bg-[var(--admin-bg)] border border-[var(--admin-border)] p-4">
|
||||
<label className="block text-sm font-medium text-[var(--admin-text-muted)] mb-1">Importing to brand</label>
|
||||
<select aria-label="Select"
|
||||
value={activeBrandId}
|
||||
onChange={(e) => handleBrandChange(e.target.value)}
|
||||
className="w-full rounded-xl border border-[var(--admin-border)] bg-white px-4 py-2.5 text-sm text-[var(--admin-text-primary)] outline-none focus:border-[var(--admin-accent)]"
|
||||
>
|
||||
{brands.map((b) => <option key={b.id} value={b.id}>{b.name}</option>)}
|
||||
</select>
|
||||
<label className="block text-sm font-medium text-[var(--admin-text-muted)] mb-1">
|
||||
Importing to brand
|
||||
<select aria-label="Select"
|
||||
value={activeBrandId}
|
||||
onChange={(e) => handleBrandChange(e.target.value)}
|
||||
className="mt-1 w-full rounded-xl border border-[var(--admin-border)] bg-white px-4 py-2.5 text-sm text-[var(--admin-text-primary)] outline-none focus:border-[var(--admin-accent)]"
|
||||
>
|
||||
{brands.map((b) => <option key={b.id} value={b.id}>{b.name}</option>)}
|
||||
</select>
|
||||
</label>
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
@@ -1105,20 +1105,20 @@ function StopBlastAdvisorTool({ brandId }: { brandId: string }) {
|
||||
</p>
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium mb-1" style={labelStyle}>Stop Name *</label>
|
||||
<input aria-label="Downtown Farmers Market" type="text" value={stopName} onChange={(e) => setStopName(e.target.value)} placeholder="Downtown Farmers Market" className={inputBaseClass} style={inputStyle} />
|
||||
<label htmlFor="fld-1-stop-name" className="block text-sm font-medium mb-1" style={labelStyle}>Stop Name *</label>
|
||||
<input id="fld-1-stop-name" aria-label="Downtown Farmers Market" type="text" value={stopName} onChange={(e) => setStopName(e.target.value)} placeholder="Downtown Farmers Market" className={inputBaseClass} style={inputStyle} />
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium mb-1" style={labelStyle}>Stop Date</label>
|
||||
<input aria-label="2026 06 15" type="text" value={stopDate} onChange={(e) => setStopDate(e.target.value)} placeholder="2026-06-15" className={inputBaseClass} style={inputStyle} />
|
||||
<label htmlFor="fld-2-stop-date" className="block text-sm font-medium mb-1" style={labelStyle}>Stop Date</label>
|
||||
<input id="fld-2-stop-date" aria-label="2026 06 15" type="text" value={stopDate} onChange={(e) => setStopDate(e.target.value)} placeholder="2026-06-15" className={inputBaseClass} style={inputStyle} />
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium mb-1" style={labelStyle}>City</label>
|
||||
<input aria-label="Greeley" type="text" value={city} onChange={(e) => setCity(e.target.value)} placeholder="Greeley" className={inputBaseClass} style={inputStyle} />
|
||||
<label htmlFor="fld-3-city" className="block text-sm font-medium mb-1" style={labelStyle}>City</label>
|
||||
<input id="fld-3-city" aria-label="Greeley" type="text" value={city} onChange={(e) => setCity(e.target.value)} placeholder="Greeley" className={inputBaseClass} style={inputStyle} />
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium mb-1" style={labelStyle}>Customer Count</label>
|
||||
<input aria-label="42" type="text" value={customerCount} onChange={(e) => setCustomerCount(e.target.value)} placeholder="42" className={inputBaseClass} style={inputStyle} />
|
||||
<label htmlFor="fld-4-customer-count" className="block text-sm font-medium mb-1" style={labelStyle}>Customer Count</label>
|
||||
<input id="fld-4-customer-count" aria-label="42" type="text" value={customerCount} onChange={(e) => setCustomerCount(e.target.value)} placeholder="42" className={inputBaseClass} style={inputStyle} />
|
||||
</div>
|
||||
</div>
|
||||
<div className="rounded-lg p-3 text-xs" style={{ backgroundColor: 'rgba(171, 162, 120, 0.1)', border: '1px solid rgba(171, 162, 120, 0.2)', color: '#92781e' }}>
|
||||
@@ -1235,8 +1235,8 @@ function CustomerInsightsTool({ brandId }: { brandId: string }) {
|
||||
Ask questions about your customers and orders in plain English. The AI will analyze and return actionable insights.
|
||||
</p>
|
||||
<div>
|
||||
<label className="block text-sm font-medium mb-1" style={labelStyle}>Ask about your customers</label>
|
||||
<textarea aria-label="., 'Which Customers Haven't Ordered In 45 Days?'"
|
||||
<label htmlFor="fld-5-ask-about-your-customers" className="block text-sm font-medium mb-1" style={labelStyle}>Ask about your customers</label>
|
||||
<textarea id="fld-5-ask-about-your-customers" aria-label="., 'Which Customers Haven't Ordered In 45 Days?'"
|
||||
value={query}
|
||||
onChange={(e) => setQuery(e.target.value)}
|
||||
rows={2}
|
||||
@@ -1396,8 +1396,8 @@ function RouteOptimizerTool({ brandId }: { brandId: string }) {
|
||||
Add your stops below. You need at least 2 stops to optimize a route.
|
||||
</p>
|
||||
<div>
|
||||
<label className="block text-sm font-medium mb-1" style={labelStyle}>Start Location</label>
|
||||
<input aria-label="Warehouse, Greeley CO"
|
||||
<label htmlFor="fld-6-start-location" className="block text-sm font-medium mb-1" style={labelStyle}>Start Location</label>
|
||||
<input id="fld-6-start-location" aria-label="Warehouse, Greeley CO"
|
||||
type="text"
|
||||
value={startLocation}
|
||||
onChange={(e) => setStartLocation(e.target.value)}
|
||||
@@ -1430,8 +1430,8 @@ function RouteOptimizerTool({ brandId }: { brandId: string }) {
|
||||
</div>
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
<div>
|
||||
<label className="block text-xs mb-1" style={{ color: 'var(--admin-text-muted)' }}>Name *</label>
|
||||
<input aria-label="Farmers Market"
|
||||
<label htmlFor="fld-7-name" className="block text-xs mb-1" style={{ color: 'var(--admin-text-muted)' }}>Name *</label>
|
||||
<input id="fld-7-name" aria-label="Farmers Market"
|
||||
type="text"
|
||||
value={stop.name}
|
||||
onChange={(e) => updateStop(i, "name", e.target.value)}
|
||||
@@ -1441,8 +1441,8 @@ function RouteOptimizerTool({ brandId }: { brandId: string }) {
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-xs mb-1" style={{ color: 'var(--admin-text-muted)' }}>City *</label>
|
||||
<input aria-label="Greeley"
|
||||
<label htmlFor="fld-8-city-2" className="block text-xs mb-1" style={{ color: 'var(--admin-text-muted)' }}>City *</label>
|
||||
<input id="fld-8-city-2" aria-label="Greeley"
|
||||
type="text"
|
||||
value={stop.city}
|
||||
onChange={(e) => updateStop(i, "city", e.target.value)}
|
||||
@@ -1452,8 +1452,8 @@ function RouteOptimizerTool({ brandId }: { brandId: string }) {
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-xs mb-1" style={{ color: 'var(--admin-text-muted)' }}>State *</label>
|
||||
<input aria-label="CO"
|
||||
<label htmlFor="fld-9-state" className="block text-xs mb-1" style={{ color: 'var(--admin-text-muted)' }}>State *</label>
|
||||
<input id="fld-9-state" aria-label="CO"
|
||||
type="text"
|
||||
value={stop.state}
|
||||
onChange={(e) => updateStop(i, "state", e.target.value)}
|
||||
@@ -1463,8 +1463,8 @@ function RouteOptimizerTool({ brandId }: { brandId: string }) {
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-xs mb-1" style={{ color: 'var(--admin-text-muted)' }}>Address</label>
|
||||
<input aria-label="123 Main St"
|
||||
<label htmlFor="fld-10-address" className="block text-xs mb-1" style={{ color: 'var(--admin-text-muted)' }}>Address</label>
|
||||
<input id="fld-10-address" aria-label="123 Main St"
|
||||
type="text"
|
||||
value={stop.address}
|
||||
onChange={(e) => updateStop(i, "address", e.target.value)}
|
||||
@@ -1475,8 +1475,8 @@ function RouteOptimizerTool({ brandId }: { brandId: string }) {
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-xs mb-1" style={{ color: 'var(--admin-text-muted)' }}>Time Window</label>
|
||||
<input aria-label="8am–12pm"
|
||||
<label htmlFor="fld-11-time-window" className="block text-xs mb-1" style={{ color: 'var(--admin-text-muted)' }}>Time Window</label>
|
||||
<input id="fld-11-time-window" aria-label="8am–12pm"
|
||||
type="text"
|
||||
value={stop.time_window}
|
||||
onChange={(e) => updateStop(i, "time_window", e.target.value)}
|
||||
@@ -1631,12 +1631,12 @@ function DemandForecastTool({ brandId }: { brandId: string }) {
|
||||
</p>
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium mb-1" style={labelStyle}>Product Name *</label>
|
||||
<input aria-label="Sweet Corn" type="text" value={productName} onChange={(e) => setProductName(e.target.value)} placeholder="Sweet Corn" className={inputBaseClass} style={inputStyle} />
|
||||
<label htmlFor="fld-12-product-name" className="block text-sm font-medium mb-1" style={labelStyle}>Product Name *</label>
|
||||
<input id="fld-12-product-name" aria-label="Sweet Corn" type="text" value={productName} onChange={(e) => setProductName(e.target.value)} placeholder="Sweet Corn" className={inputBaseClass} style={inputStyle} />
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium mb-1" style={labelStyle}>Stop Name</label>
|
||||
<input aria-label="Downtown Farmers Market" type="text" value={stopName} onChange={(e) => setStopName(e.target.value)} placeholder="Downtown Farmers Market" className={inputBaseClass} style={inputStyle} />
|
||||
<label htmlFor="fld-13-stop-name-2" className="block text-sm font-medium mb-1" style={labelStyle}>Stop Name</label>
|
||||
<input id="fld-13-stop-name-2" aria-label="Downtown Farmers Market" type="text" value={stopName} onChange={(e) => setStopName(e.target.value)} placeholder="Downtown Farmers Market" className={inputBaseClass} style={inputStyle} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -544,8 +544,8 @@ export default function IntegrationsClient({ brandId, brands, isPlatformAdmin, p
|
||||
{/* Brand picker */}
|
||||
{isPlatformAdmin && brands.length > 0 && (
|
||||
<div className="mb-6 flex items-center gap-3">
|
||||
<label className="text-sm font-medium text-zinc-400">Brand</label>
|
||||
<select aria-label="Select"
|
||||
<label htmlFor="fld-1-brand" className="text-sm font-medium text-zinc-400">Brand</label>
|
||||
<select id="fld-1-brand" aria-label="Select"
|
||||
value={selectedBrandId}
|
||||
onChange={() => {
|
||||
// selectedBrandId is derived from the `brandId` prop.
|
||||
|
||||
@@ -1191,8 +1191,8 @@ function ProductsTab({ products, brandId, onMsg, onRefresh }: {
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-[var(--admin-text-secondary)] mb-1">Availability</label>
|
||||
<select aria-label="Select" value={form.availability} onChange={e => setForm(f => ({ ...f, availability: e.target.value }))}
|
||||
<label htmlFor="fld-1-availability" className="block text-sm font-medium text-[var(--admin-text-secondary)] mb-1">Availability</label>
|
||||
<select id="fld-1-availability" aria-label="Select" value={form.availability} onChange={e => setForm(f => ({ ...f, availability: e.target.value }))}
|
||||
className="w-full rounded-xl border border-[var(--admin-border)] px-3 py-2 text-sm outline-none focus:border-[var(--admin-accent)]">
|
||||
<option value="available">Available</option>
|
||||
<option value="unavailable">Unavailable</option>
|
||||
@@ -1200,20 +1200,18 @@ function ProductsTab({ products, brandId, onMsg, onRefresh }: {
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-[var(--admin-text-secondary)] mb-1">Qty Available</label>
|
||||
<input aria-label="Number" type="number" value={form.qtyAvailable} onChange={e => setForm(f => ({ ...f, qtyAvailable: Number(e.target.value) }))}
|
||||
<label htmlFor="fld-2-qty-available" className="block text-sm font-medium text-[var(--admin-text-secondary)] mb-1">Qty Available</label>
|
||||
<input id="fld-2-qty-available" aria-label="Number" type="number" value={form.qtyAvailable} onChange={e => setForm(f => ({ ...f, qtyAvailable: Number(e.target.value) }))}
|
||||
className="w-full rounded-xl border border-[var(--admin-border)] px-3 py-2 text-sm outline-none focus:border-[var(--admin-accent)]" />
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-[var(--admin-text-secondary)] mb-1">HP SKU</label>
|
||||
<input aria-label="Input" value={form.hpSku} onChange={e => setForm(f => ({ ...f, hpSku: e.target.value }))}
|
||||
<label htmlFor="fld-3-hp-sku" className="block text-sm font-medium text-[var(--admin-text-secondary)] mb-1">HP SKU</label>
|
||||
<input id="fld-3-hp-sku" aria-label="Input" value={form.hpSku} onChange={e => setForm(f => ({ ...f, hpSku: e.target.value }))}
|
||||
className="w-full rounded-xl border border-[var(--admin-border)] px-3 py-2 text-sm outline-none focus:border-[var(--admin-accent)]" />
|
||||
</div>
|
||||
<div className="col-span-2">
|
||||
<label className="block text-sm font-medium text-[var(--admin-text-secondary)] mb-1">
|
||||
Price Tiers (JSON array of {"{\"min_qty\":1,\"max_qty\":24,\"price\":20}"})
|
||||
</label>
|
||||
<input aria-label="[{"min Qty":1,"max Qty":24,"price":20},{"min Qty":25,"max Qty":0,"price":18}]" value={form.priceTiers} onChange={e => setForm(f => ({ ...f, priceTiers: e.target.value }))}
|
||||
<label htmlFor="fld-4-price-tiers-json-array-of-min-qty1max-qty24price20" className="block text-sm font-medium text-[var(--admin-text-secondary)] mb-1">Price Tiers (JSON array of {"{\"min_qty\":1,\"max_qty\":24,\"price\":20}"})</label>
|
||||
<input id="fld-4-price-tiers-json-array-of-min-qty1max-qty24price20" aria-label="[{"min Qty":1,"max Qty":24,"price":20},{"min Qty":25,"max Qty":0,"price":18}]" value={form.priceTiers} onChange={e => setForm(f => ({ ...f, priceTiers: e.target.value }))}
|
||||
placeholder='[{"min_qty":1,"max_qty":24,"price":20},{"min_qty":25,"max_qty":0,"price":18}]'
|
||||
className="w-full rounded-xl border border-[var(--admin-border)] px-3 py-2 text-sm font-mono outline-none focus:border-[var(--admin-accent)]" />
|
||||
</div>
|
||||
@@ -1506,8 +1504,8 @@ function OrdersTab({ orders, customers, brandId, onMsg, onRefresh }: {
|
||||
{/* Filter bar */}
|
||||
<div className="flex flex-wrap items-center gap-3 rounded-2xl bg-white border border-[var(--admin-border)] p-4 shadow-sm">
|
||||
<div>
|
||||
<label className="block text-xs font-medium text-[var(--admin-text-muted)] mb-1">Status</label>
|
||||
<select aria-label="Select" value={statusFilter} onChange={e => setStatusFilter(e.target.value)}
|
||||
<label htmlFor="fld-5-status" className="block text-xs font-medium text-[var(--admin-text-muted)] mb-1">Status</label>
|
||||
<select id="fld-5-status" aria-label="Select" value={statusFilter} onChange={e => setStatusFilter(e.target.value)}
|
||||
className="rounded-lg border border-[var(--admin-border)] px-3 py-1.5 text-sm outline-none focus:border-[var(--admin-accent)]">
|
||||
<option value="all">All Statuses</option>
|
||||
<option value="pending">Pending</option>
|
||||
@@ -1517,13 +1515,13 @@ function OrdersTab({ orders, customers, brandId, onMsg, onRefresh }: {
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-xs font-medium text-[var(--admin-text-muted)] mb-1">Pickup From</label>
|
||||
<input aria-label="Date" type="date" value={dateFrom} onChange={e => setDateFrom(e.target.value)}
|
||||
<label htmlFor="fld-6-pickup-from" className="block text-xs font-medium text-[var(--admin-text-muted)] mb-1">Pickup From</label>
|
||||
<input id="fld-6-pickup-from" aria-label="Date" type="date" value={dateFrom} onChange={e => setDateFrom(e.target.value)}
|
||||
className="rounded-lg border border-[var(--admin-border)] px-3 py-1.5 text-sm outline-none focus:border-[var(--admin-accent)]" />
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-xs font-medium text-[var(--admin-text-muted)] mb-1">Pickup To</label>
|
||||
<input aria-label="Date" type="date" value={dateTo} onChange={e => setDateTo(e.target.value)}
|
||||
<label htmlFor="fld-7-pickup-to" className="block text-xs font-medium text-[var(--admin-text-muted)] mb-1">Pickup To</label>
|
||||
<input id="fld-7-pickup-to" aria-label="Date" type="date" value={dateTo} onChange={e => setDateTo(e.target.value)}
|
||||
className="rounded-lg border border-[var(--admin-border)] px-3 py-1.5 text-sm outline-none focus:border-[var(--admin-accent)]" />
|
||||
</div>
|
||||
<div className="flex-1 min-w-[180px]">
|
||||
@@ -1663,13 +1661,11 @@ function OrdersTab({ orders, customers, brandId, onMsg, onRefresh }: {
|
||||
)}
|
||||
|
||||
{/* Invoice download — always visible as icon button */}
|
||||
<a
|
||||
<a aria-label="Download Invoice"
|
||||
href={`/api/wholesale/invoice/${o.id}/pdf`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
title="Download Invoice"
|
||||
className="inline-flex items-center justify-center rounded-xl w-9 h-9 text-[var(--admin-text-muted)] hover:text-[var(--admin-accent)] hover:bg-[var(--admin-bg-subtle)] transition-colors"
|
||||
>
|
||||
rel="noopener noreferrer" title="Download Invoice"
|
||||
className="inline-flex items-center justify-center rounded-xl w-9 h-9 text-[var(--admin-text-muted)] hover:text-[var(--admin-accent)] hover:bg-[var(--admin-bg-subtle)] transition-colors">
|
||||
<svg className="w-4 h-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={1.75}><path strokeLinecap="round" strokeLinejoin="round" d="M9 12h9m-9-6h6m-3 12a9 9 0 110 12H15M6 2h9l4 4v14a2 2 0 01-2 2H6a2 2 0 01-2-2V4a2 2 0 012-2z"/></svg>
|
||||
</a>
|
||||
|
||||
@@ -1777,13 +1773,13 @@ function OrdersTab({ orders, customers, brandId, onMsg, onRefresh }: {
|
||||
<h3 className="font-semibold text-[var(--admin-text-primary)] mb-4">Record Deposit</h3>
|
||||
<div className="space-y-3">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-[var(--admin-text-secondary)] mb-1">Amount ($)</label>
|
||||
<input aria-label="Number" type="number" value={depAmount} onChange={e => setDepAmount(e.target.value)}
|
||||
<label htmlFor="fld-8-amount" className="block text-sm font-medium text-[var(--admin-text-secondary)] mb-1">Amount ($)</label>
|
||||
<input id="fld-8-amount" aria-label="Number" type="number" value={depAmount} onChange={e => setDepAmount(e.target.value)}
|
||||
className="w-full rounded-xl border border-[var(--admin-border)] px-3 py-2 text-sm outline-none focus:border-[var(--admin-accent)]" step="0.01" />
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-[var(--admin-text-secondary)] mb-1">Method</label>
|
||||
<select aria-label="Select" value={depMethod} onChange={e => setDepMethod(e.target.value)}
|
||||
<label htmlFor="fld-9-method" className="block text-sm font-medium text-[var(--admin-text-secondary)] mb-1">Method</label>
|
||||
<select id="fld-9-method" aria-label="Select" value={depMethod} onChange={e => setDepMethod(e.target.value)}
|
||||
className="w-full rounded-xl border border-[var(--admin-border)] px-3 py-2 text-sm outline-none focus:border-[var(--admin-accent)]">
|
||||
<option value="cash">Cash</option>
|
||||
<option value="check">Check</option>
|
||||
@@ -1811,13 +1807,13 @@ function OrdersTab({ orders, customers, brandId, onMsg, onRefresh }: {
|
||||
<h3 className="font-semibold text-[var(--admin-text-primary)] mb-4">Bulk Record Deposit ({selected.size} orders)</h3>
|
||||
<div className="space-y-3">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-[var(--admin-text-secondary)] mb-1">Amount per order ($)</label>
|
||||
<input aria-label="0.00" type="number" value={bulkDepAmount} onChange={e => setBulkDepAmount(e.target.value)}
|
||||
<label htmlFor="fld-10-amount-per-order" className="block text-sm font-medium text-[var(--admin-text-secondary)] mb-1">Amount per order ($)</label>
|
||||
<input id="fld-10-amount-per-order" aria-label="0.00" type="number" value={bulkDepAmount} onChange={e => setBulkDepAmount(e.target.value)}
|
||||
className="w-full rounded-xl border border-[var(--admin-border)] px-3 py-2 text-sm outline-none focus:border-[var(--admin-accent)]" step="0.01" placeholder="0.00" />
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-[var(--admin-text-secondary)] mb-1">Method</label>
|
||||
<select aria-label="Select" value={bulkDepMethod} onChange={e => setBulkDepMethod(e.target.value)}
|
||||
<label htmlFor="fld-11-method-2" className="block text-sm font-medium text-[var(--admin-text-secondary)] mb-1">Method</label>
|
||||
<select id="fld-11-method-2" aria-label="Select" value={bulkDepMethod} onChange={e => setBulkDepMethod(e.target.value)}
|
||||
className="w-full rounded-xl border border-[var(--admin-border)] px-3 py-2 text-sm outline-none focus:border-[var(--admin-accent)]">
|
||||
<option value="cash">Cash</option>
|
||||
<option value="check">Check</option>
|
||||
@@ -2011,48 +2007,48 @@ function SettingsTab({ settings, brandId, onMsg, onRefresh, canManageSettings }:
|
||||
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-[var(--admin-text-secondary)] mb-1">Minimum Order Amount ($)</label>
|
||||
<input aria-label="No Minimum" type="number" value={form.minOrderAmount} onChange={e => setForm(f => ({ ...f, minOrderAmount: e.target.value }))}
|
||||
<label htmlFor="fld-12-minimum-order-amount" className="block text-sm font-medium text-[var(--admin-text-secondary)] mb-1">Minimum Order Amount ($)</label>
|
||||
<input id="fld-12-minimum-order-amount" aria-label="No Minimum" type="number" value={form.minOrderAmount} onChange={e => setForm(f => ({ ...f, minOrderAmount: e.target.value }))}
|
||||
className="w-full rounded-xl border border-[var(--admin-border)] px-3 py-2 text-sm outline-none focus:border-[var(--admin-accent)]" step="0.01" placeholder="No minimum" />
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-[var(--admin-text-secondary)] mb-1">From Email</label>
|
||||
<input aria-label="Email" type="email" value={form.fromEmail} onChange={e => setForm(f => ({ ...f, fromEmail: e.target.value }))}
|
||||
<label htmlFor="fld-13-from-email" className="block text-sm font-medium text-[var(--admin-text-secondary)] mb-1">From Email</label>
|
||||
<input id="fld-13-from-email" aria-label="Email" type="email" value={form.fromEmail} onChange={e => setForm(f => ({ ...f, fromEmail: e.target.value }))}
|
||||
className="w-full rounded-xl border border-[var(--admin-border)] px-3 py-2 text-sm outline-none focus:border-[var(--admin-accent)]" />
|
||||
</div>
|
||||
<div className="col-span-2">
|
||||
<label className="block text-sm font-medium text-[var(--admin-text-secondary)] mb-1">Pickup Location</label>
|
||||
<textarea aria-label="Textarea" value={form.pickupLocation} onChange={e => setForm(f => ({ ...f, pickupLocation: e.target.value }))}
|
||||
<label htmlFor="fld-14-pickup-location" className="block text-sm font-medium text-[var(--admin-text-secondary)] mb-1">Pickup Location</label>
|
||||
<textarea id="fld-14-pickup-location" aria-label="Textarea" value={form.pickupLocation} onChange={e => setForm(f => ({ ...f, pickupLocation: e.target.value }))}
|
||||
className="w-full rounded-xl border border-[var(--admin-border)] px-3 py-2 text-sm outline-none focus:border-[var(--admin-accent)] font-mono" rows={3} />
|
||||
</div>
|
||||
<div className="col-span-2">
|
||||
<label className="block text-sm font-medium text-[var(--admin-text-secondary)] mb-1">FOB Location</label>
|
||||
<input aria-label="Input" value={form.fobLocation} onChange={e => setForm(f => ({ ...f, fobLocation: e.target.value }))}
|
||||
<label htmlFor="fld-15-fob-location" className="block text-sm font-medium text-[var(--admin-text-secondary)] mb-1">FOB Location</label>
|
||||
<input id="fld-15-fob-location" aria-label="Input" value={form.fobLocation} onChange={e => setForm(f => ({ ...f, fobLocation: e.target.value }))}
|
||||
className="w-full rounded-xl border border-[var(--admin-border)] px-3 py-2 text-sm outline-none focus:border-[var(--admin-accent)]" />
|
||||
</div>
|
||||
<div className="col-span-2">
|
||||
<label className="block text-sm font-medium text-[var(--admin-text-secondary)] mb-1">Invoice Business Name</label>
|
||||
<input aria-label="Input" value={form.invoiceBusinessName} onChange={e => setForm(f => ({ ...f, invoiceBusinessName: e.target.value }))}
|
||||
<label htmlFor="fld-16-invoice-business-name" className="block text-sm font-medium text-[var(--admin-text-secondary)] mb-1">Invoice Business Name</label>
|
||||
<input id="fld-16-invoice-business-name" aria-label="Input" value={form.invoiceBusinessName} onChange={e => setForm(f => ({ ...f, invoiceBusinessName: e.target.value }))}
|
||||
className="w-full rounded-xl border border-[var(--admin-border)] px-3 py-2 text-sm outline-none focus:border-[var(--admin-accent)]" />
|
||||
</div>
|
||||
<div className="col-span-2">
|
||||
<label className="block text-sm font-medium text-[var(--admin-text-secondary)] mb-1">Invoice Business Address</label>
|
||||
<textarea aria-label="Textarea" value={form.invoiceBusinessAddress} onChange={e => setForm(f => ({ ...f, invoiceBusinessAddress: e.target.value }))}
|
||||
<label htmlFor="fld-17-invoice-business-address" className="block text-sm font-medium text-[var(--admin-text-secondary)] mb-1">Invoice Business Address</label>
|
||||
<textarea id="fld-17-invoice-business-address" aria-label="Textarea" value={form.invoiceBusinessAddress} onChange={e => setForm(f => ({ ...f, invoiceBusinessAddress: e.target.value }))}
|
||||
className="w-full rounded-xl border border-[var(--admin-border)] px-3 py-2 text-sm outline-none focus:border-[var(--admin-accent)]" rows={2} />
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-[var(--admin-text-secondary)] mb-1">Invoice Business Phone</label>
|
||||
<input aria-label="Input" value={form.invoiceBusinessPhone} onChange={e => setForm(f => ({ ...f, invoiceBusinessPhone: e.target.value }))}
|
||||
<label htmlFor="fld-18-invoice-business-phone" className="block text-sm font-medium text-[var(--admin-text-secondary)] mb-1">Invoice Business Phone</label>
|
||||
<input id="fld-18-invoice-business-phone" aria-label="Input" value={form.invoiceBusinessPhone} onChange={e => setForm(f => ({ ...f, invoiceBusinessPhone: e.target.value }))}
|
||||
className="w-full rounded-xl border border-[var(--admin-border)] px-3 py-2 text-sm outline-none focus:border-[var(--admin-accent)]" />
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-[var(--admin-text-secondary)] mb-1">Invoice Business Email</label>
|
||||
<input aria-label="Email" type="email" value={form.invoiceBusinessEmail} onChange={e => setForm(f => ({ ...f, invoiceBusinessEmail: e.target.value }))}
|
||||
<label htmlFor="fld-19-invoice-business-email" className="block text-sm font-medium text-[var(--admin-text-secondary)] mb-1">Invoice Business Email</label>
|
||||
<input id="fld-19-invoice-business-email" aria-label="Email" type="email" value={form.invoiceBusinessEmail} onChange={e => setForm(f => ({ ...f, invoiceBusinessEmail: e.target.value }))}
|
||||
className="w-full rounded-xl border border-[var(--admin-border)] px-3 py-2 text-sm outline-none focus:border-[var(--admin-accent)]" />
|
||||
</div>
|
||||
<div className="col-span-2">
|
||||
<label className="block text-sm font-medium text-[var(--admin-text-secondary)] mb-1">Invoice Business Website</label>
|
||||
<input aria-label="Https://" value={form.invoiceBusinessWebsite} onChange={e => setForm(f => ({ ...f, invoiceBusinessWebsite: e.target.value }))}
|
||||
<label htmlFor="fld-20-invoice-business-website" className="block text-sm font-medium text-[var(--admin-text-secondary)] mb-1">Invoice Business Website</label>
|
||||
<input id="fld-20-invoice-business-website" aria-label="Https://" value={form.invoiceBusinessWebsite} onChange={e => setForm(f => ({ ...f, invoiceBusinessWebsite: e.target.value }))}
|
||||
className="w-full rounded-xl border border-[var(--admin-border)] px-3 py-2 text-sm outline-none focus:border-[var(--admin-accent)]" placeholder="https://" />
|
||||
</div>
|
||||
|
||||
@@ -2233,8 +2229,8 @@ function WebhookSettingsSection({ brandId, onMsg }: {
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-[var(--admin-text-secondary)] mb-1">Webhook URL</label>
|
||||
<input aria-label="Https://your System.com/webhooks/wholesale"
|
||||
<label htmlFor="fld-21-webhook-url" className="block text-sm font-medium text-[var(--admin-text-secondary)] mb-1">Webhook URL</label>
|
||||
<input id="fld-21-webhook-url" aria-label="Https://your System.com/webhooks/wholesale"
|
||||
type="url"
|
||||
value={settings.url}
|
||||
onChange={e => setSettings(s => ({ ...s, url: e.target.value }))}
|
||||
@@ -2244,10 +2240,8 @@ function WebhookSettingsSection({ brandId, onMsg }: {
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-[var(--admin-text-secondary)] mb-1">
|
||||
Signing Secret (HMAC-SHA256)
|
||||
</label>
|
||||
<input aria-label="Leave Blank To Keep Current Secret"
|
||||
<label htmlFor="fld-22-signing-secret-hmac-sha256" className="block text-sm font-medium text-[var(--admin-text-secondary)] mb-1">Signing Secret (HMAC-SHA256)</label>
|
||||
<input id="fld-22-signing-secret-hmac-sha256" aria-label="Leave Blank To Keep Current Secret"
|
||||
type="password"
|
||||
value={settings.secret}
|
||||
onChange={e => setSettings(s => ({ ...s, secret: e.target.value }))}
|
||||
|
||||
@@ -81,10 +81,8 @@ export default function ChangePasswordForm({ userId }: { userId: string }) {
|
||||
)}
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-zinc-400 mb-1">
|
||||
New Password
|
||||
</label>
|
||||
<input aria-label=". 8 Characters"
|
||||
<label htmlFor="fld-1-new-password" className="block text-sm font-medium text-zinc-400 mb-1">New Password</label>
|
||||
<input id="fld-1-new-password" aria-label=". 8 Characters"
|
||||
type="password"
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
@@ -97,10 +95,8 @@ export default function ChangePasswordForm({ userId }: { userId: string }) {
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-zinc-400 mb-1">
|
||||
Confirm Password
|
||||
</label>
|
||||
<input aria-label="Repeat Password"
|
||||
<label htmlFor="fld-2-confirm-password" className="block text-sm font-medium text-zinc-400 mb-1">Confirm Password</label>
|
||||
<input id="fld-2-confirm-password" aria-label="Repeat Password"
|
||||
type="password"
|
||||
value={confirm}
|
||||
onChange={(e) => setConfirm(e.target.value)}
|
||||
|
||||
@@ -72,10 +72,8 @@ export default function ChangePasswordPage() {
|
||||
)}
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-zinc-400 mb-1">
|
||||
New Password
|
||||
</label>
|
||||
<input aria-label=". 8 Characters"
|
||||
<label htmlFor="fld-1-new-password" className="block text-sm font-medium text-zinc-400 mb-1">New Password</label>
|
||||
<input id="fld-1-new-password" aria-label=". 8 Characters"
|
||||
type="password"
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
@@ -88,10 +86,8 @@ export default function ChangePasswordPage() {
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-zinc-400 mb-1">
|
||||
Confirm Password
|
||||
</label>
|
||||
<input aria-label="Repeat Password"
|
||||
<label htmlFor="fld-2-confirm-password" className="block text-sm font-medium text-zinc-400 mb-1">Confirm Password</label>
|
||||
<input id="fld-2-confirm-password" aria-label="Repeat Password"
|
||||
type="password"
|
||||
value={confirm}
|
||||
onChange={(e) => setConfirm(e.target.value)}
|
||||
|
||||
@@ -159,8 +159,8 @@ export default function IndianRiverContactPage() {
|
||||
<form onSubmit={handleSubmit} className="space-y-6" aria-label="Contact form">
|
||||
<div className="grid gap-6 md:grid-cols-2">
|
||||
<div>
|
||||
<label className="block text-sm font-semibold text-stone-700 mb-2">Your Name</label>
|
||||
<input aria-label="Jane Smith"
|
||||
<label htmlFor="fld-1-your-name" className="block text-sm font-semibold text-stone-700 mb-2">Your Name</label>
|
||||
<input id="fld-1-your-name" aria-label="Jane Smith"
|
||||
required
|
||||
value={form.name}
|
||||
onChange={(e) => setForm({ ...form, name: e.target.value })}
|
||||
@@ -170,8 +170,8 @@ export default function IndianRiverContactPage() {
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-semibold text-stone-700 mb-2">Email</label>
|
||||
<input aria-label="Jane@example.com"
|
||||
<label htmlFor="fld-2-email" className="block text-sm font-semibold text-stone-700 mb-2">Email</label>
|
||||
<input id="fld-2-email" aria-label="Jane@example.com"
|
||||
required
|
||||
type="email"
|
||||
value={form.email}
|
||||
@@ -183,8 +183,8 @@ export default function IndianRiverContactPage() {
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-semibold text-stone-700 mb-2">Topic</label>
|
||||
<select aria-label="Select"
|
||||
<label htmlFor="fld-3-topic" className="block text-sm font-semibold text-stone-700 mb-2">Topic</label>
|
||||
<select id="fld-3-topic" aria-label="Select"
|
||||
value={form.topic}
|
||||
onChange={(e) => setForm({ ...form, topic: e.target.value })}
|
||||
className="w-full rounded-xl border-2 border-stone-200 bg-white px-5 py-4 text-base text-stone-900 outline-none focus:border-blue-400 focus:ring-2 focus:ring-blue-400/20 transition-colors appearance-none"
|
||||
@@ -196,8 +196,8 @@ export default function IndianRiverContactPage() {
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-semibold text-stone-700 mb-2">Message</label>
|
||||
<textarea aria-label="How Can We Help You?"
|
||||
<label htmlFor="fld-4-message" className="block text-sm font-semibold text-stone-700 mb-2">Message</label>
|
||||
<textarea id="fld-4-message" aria-label="How Can We Help You?"
|
||||
required
|
||||
rows={5}
|
||||
value={form.message}
|
||||
|
||||
@@ -96,10 +96,8 @@ export default function ResetPasswordPage() {
|
||||
)}
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-slate-700">
|
||||
New Password
|
||||
</label>
|
||||
<input aria-label=". 8 Characters"
|
||||
<label htmlFor="fld-1-new-password" className="block text-sm font-medium text-slate-700">New Password</label>
|
||||
<input id="fld-1-new-password" aria-label=". 8 Characters"
|
||||
type="password"
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
@@ -111,10 +109,8 @@ export default function ResetPasswordPage() {
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-slate-700">
|
||||
Confirm Password
|
||||
</label>
|
||||
<input aria-label="Repeat Password"
|
||||
<label htmlFor="fld-2-confirm-password" className="block text-sm font-medium text-slate-700">Confirm Password</label>
|
||||
<input id="fld-2-confirm-password" aria-label="Repeat Password"
|
||||
type="password"
|
||||
value={confirm}
|
||||
onChange={(e) => setConfirm(e.target.value)}
|
||||
|
||||
@@ -139,8 +139,8 @@ export default function TuxedoContactPage() {
|
||||
<form onSubmit={handleSubmit} className="space-y-6">
|
||||
<div className="grid gap-6 md:grid-cols-2">
|
||||
<div>
|
||||
<label className="block text-sm font-semibold text-stone-700 mb-2">Your Name</label>
|
||||
<input aria-label="Jane Smith"
|
||||
<label htmlFor="fld-1-your-name" className="block text-sm font-semibold text-stone-700 mb-2">Your Name</label>
|
||||
<input id="fld-1-your-name" aria-label="Jane Smith"
|
||||
required
|
||||
value={form.name}
|
||||
onChange={(e) => setForm({ ...form, name: e.target.value })}
|
||||
@@ -149,8 +149,8 @@ export default function TuxedoContactPage() {
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-semibold text-stone-700 mb-2">Email</label>
|
||||
<input aria-label="Jane@example.com"
|
||||
<label htmlFor="fld-2-email" className="block text-sm font-semibold text-stone-700 mb-2">Email</label>
|
||||
<input id="fld-2-email" aria-label="Jane@example.com"
|
||||
required
|
||||
type="email"
|
||||
value={form.email}
|
||||
@@ -161,8 +161,8 @@ export default function TuxedoContactPage() {
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-semibold text-stone-700 mb-2">Topic</label>
|
||||
<select aria-label="Select"
|
||||
<label htmlFor="fld-3-topic" className="block text-sm font-semibold text-stone-700 mb-2">Topic</label>
|
||||
<select id="fld-3-topic" aria-label="Select"
|
||||
value={form.topic}
|
||||
onChange={(e) => setForm({ ...form, topic: e.target.value })}
|
||||
className="w-full rounded-xl border border-stone-200 bg-white px-5 py-4 text-sm text-stone-900 outline-none focus:border-stone-900 focus:ring-1 focus:ring-stone-900 transition-colors appearance-none"
|
||||
@@ -174,8 +174,8 @@ export default function TuxedoContactPage() {
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-semibold text-stone-700 mb-2">Message</label>
|
||||
<textarea aria-label="How Can We Help You?"
|
||||
<label htmlFor="fld-4-message" className="block text-sm font-semibold text-stone-700 mb-2">Message</label>
|
||||
<textarea id="fld-4-message" aria-label="How Can We Help You?"
|
||||
required
|
||||
rows={5}
|
||||
value={form.message}
|
||||
|
||||
@@ -360,32 +360,26 @@ function OrderCard({
|
||||
</button>
|
||||
)}
|
||||
{Number(order.balance_due) > 0 && order.fulfillment_status !== "fulfilled" && (
|
||||
<button
|
||||
<button aria-label="Record deposit"
|
||||
type="button"
|
||||
onClick={onRecordDeposit}
|
||||
title="Record deposit"
|
||||
className="inline-flex items-center gap-1.5 rounded-xl bg-purple-600 px-3 py-2 text-xs font-bold text-white shadow-sm hover:bg-purple-700 transition-colors"
|
||||
>
|
||||
onClick={onRecordDeposit} title="Record deposit"
|
||||
className="inline-flex items-center gap-1.5 rounded-xl bg-purple-600 px-3 py-2 text-xs font-bold text-white shadow-sm hover:bg-purple-700 transition-colors">
|
||||
<svg className="w-3.5 h-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}><path strokeLinecap="round" strokeLinejoin="round" d="M12 6v12m-9-9h18" /></svg>
|
||||
Deposit
|
||||
</button>
|
||||
)}
|
||||
<a
|
||||
<a aria-label="Download Invoice (PDF)"
|
||||
href={`/api/wholesale/invoice/${order.id}/pdf`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
title="Download Invoice (PDF)"
|
||||
className="inline-flex items-center justify-center rounded-xl w-9 h-9 text-slate-500 hover:text-slate-800 hover:bg-slate-100 transition-colors"
|
||||
>
|
||||
rel="noopener noreferrer" title="Download Invoice (PDF)"
|
||||
className="inline-flex items-center justify-center rounded-xl w-9 h-9 text-slate-500 hover:text-slate-800 hover:bg-slate-100 transition-colors">
|
||||
<svg className="w-4 h-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={1.75}><path strokeLinecap="round" strokeLinejoin="round" d="M9 12h9m-9-6h6m-3 12a9 9 0 110 12H15M6 2h9l4 4v14a2 2 0 01-2 2H6a2 2 0 01-2-2V4a2 2 0 012-2z"/></svg>
|
||||
</a>
|
||||
<div className="relative">
|
||||
<button
|
||||
<button aria-label="More actions"
|
||||
type="button"
|
||||
onClick={onToggleActions}
|
||||
title="More actions"
|
||||
className="inline-flex items-center justify-center rounded-xl w-9 h-9 text-slate-400 hover:text-slate-700 hover:bg-slate-100 transition-colors"
|
||||
>
|
||||
onClick={onToggleActions} title="More actions"
|
||||
className="inline-flex items-center justify-center rounded-xl w-9 h-9 text-slate-400 hover:text-slate-700 hover:bg-slate-100 transition-colors">
|
||||
<svg className="w-4 h-4" fill="currentColor" viewBox="0 0 20 20"><circle cx="10" cy="4" r="1.5"/><circle cx="10" cy="10" r="1.5"/><circle cx="10" cy="16" r="1.5"/></svg>
|
||||
</button>
|
||||
{openActions === order.id && (
|
||||
|
||||
Reference in New Issue
Block a user