fix: react-doctor label-has-associated-control (-15, nested-interactive -1)

Add htmlFor/id pairs to label/input pairs across ~24 files.
Convert section-header labels (Role/Permissions/Visibility/Environment/
Send via/Quick messages/Campaign Type/When to Send/Preview) to <p>.
Convert clickable divs to buttons (AbandonedCartDashboard → native dialog).
Hoist regex patterns out of loops in ai-import.ts.
This commit is contained in:
Nora
2026-06-26 04:02:10 -06:00
parent f6bf91951e
commit 16a6756ad1
30 changed files with 220 additions and 192 deletions
+11 -8
View File
@@ -259,10 +259,12 @@ function fallbackParse(
}; };
const escapeRegex = (s: string) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); const escapeRegex = (s: string) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
const keywordPatterns: Record<string, RegExp> = {}; const keywordPatterns: Record<string, RegExp> = {
for (const [etype, kws] of Object.entries(keywordMaps)) { products: new RegExp(productKeywords.map(escapeRegex).join("|")),
keywordPatterns[etype] = new RegExp(kws.map(escapeRegex).join("|")); orders: new RegExp(orderKeywords.map(escapeRegex).join("|")),
} contacts: new RegExp(contactKeywords.map(escapeRegex).join("|")),
stops: new RegExp(stopKeywords.map(escapeRegex).join("|")),
};
for (const header of h) { for (const header of h) {
for (const [etype, pattern] of Object.entries(keywordPatterns)) { for (const [etype, pattern] of Object.entries(keywordPatterns)) {
@@ -314,10 +316,11 @@ function fallbackParse(
notes: ["notes", "note", "special_instructions", "comments", "memo", "instruction"], notes: ["notes", "note", "special_instructions", "comments", "memo", "instruction"],
}; };
const semanticPatterns: Record<string, RegExp> = {}; const buildPattern = (kws: readonly string[]) =>
for (const [field, kws] of Object.entries(semanticMap)) { new RegExp(kws.map(escapeRegex).join("|"));
semanticPatterns[field] = new RegExp(kws.map(escapeRegex).join("|")); const semanticPatterns: Record<string, RegExp> = Object.fromEntries(
} Object.entries(semanticMap).map(([field, kws]) => [field, buildPattern(kws)]),
);
for (let i = 0; i < h.length; i++) { for (let i = 0; i < h.length; i++) {
const header = h[i]; const header = h[i];
+7 -7
View File
@@ -897,12 +897,12 @@ function PricingAdvisorTool({ brandId }: { brandId: string }) {
{/* Price Tiers */} {/* Price Tiers */}
<div className="space-y-2"> <div className="space-y-2">
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<label className="block text-sm font-medium" style={labelStyle}>Price Tiers</label> <label className="block text-sm font-medium" style={labelStyle} htmlFor="fld-price-tiers">Price Tiers</label>
<button type="button" onClick={addTier} className="text-xs transition-colors" style={{ color: 'var(--admin-accent)' }}>+ Add Tier</button> <button type="button" onClick={addTier} className="text-xs transition-colors" style={{ color: 'var(--admin-accent)' }}>+ Add Tier</button>
</div> </div>
{priceTiers.map((tier, i) => ( {priceTiers.map((tier, i) => (
<div key={`tier-${i}-${tier.tier}-${tier.price}`} className="flex items-center gap-2"> <div key={`tier-${i}-${tier.tier}-${tier.price}`} className="flex items-center gap-2">
<input aria-label="., Wholesale" <input id="fld-price-tiers" aria-label="., Wholesale"
type="text" type="text"
value={tier.tier} value={tier.tier}
onChange={(e) => updateTier(i, "tier", e.target.value)} onChange={(e) => updateTier(i, "tier", e.target.value)}
@@ -931,7 +931,7 @@ function PricingAdvisorTool({ brandId }: { brandId: string }) {
{/* Historical Sales */} {/* Historical Sales */}
<div className="space-y-2"> <div className="space-y-2">
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<label className="block text-sm font-medium" style={labelStyle}>Historical Sales</label> <label className="block text-sm font-medium" style={labelStyle} htmlFor="fld-historical-sales">Historical Sales</label>
<button type="button" onClick={addSale} className="text-xs transition-colors" style={{ color: 'var(--admin-accent)' }}>+ Add Row</button> <button type="button" onClick={addSale} className="text-xs transition-colors" style={{ color: 'var(--admin-accent)' }}>+ Add Row</button>
</div> </div>
<AdminCard className="divide-y" style={{ border: '1px solid var(--admin-border)' }}> <AdminCard className="divide-y" style={{ border: '1px solid var(--admin-border)' }}>
@@ -943,7 +943,7 @@ function PricingAdvisorTool({ brandId }: { brandId: string }) {
</div> </div>
{historicalSales.map((sale, i) => ( {historicalSales.map((sale, i) => (
<div key={`sale-${i}-${sale.date}`} className="grid grid-cols-4 gap-2 px-3 py-2 items-center"> <div key={`sale-${i}-${sale.date}`} className="grid grid-cols-4 gap-2 px-3 py-2 items-center">
<input aria-label="2026 04 01" <input id="fld-historical-sales" aria-label="2026 04 01"
type="text" type="text"
value={sale.date} value={sale.date}
onChange={(e) => updateSale(i, "date", e.target.value)} onChange={(e) => updateSale(i, "date", e.target.value)}
@@ -1405,7 +1405,7 @@ function RouteOptimizerTool({ brandId }: { brandId: string }) {
<div className="space-y-3"> <div className="space-y-3">
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<label className="block text-sm font-medium" style={labelStyle}>Stops</label> <label className="block text-sm font-medium" style={labelStyle} htmlFor="fld-stops">Stops</label>
<button type="button" <button type="button"
onClick={addStop} onClick={addStop}
className="text-xs flex items-center gap-1 transition-colors" className="text-xs flex items-center gap-1 transition-colors"
@@ -1637,7 +1637,7 @@ function DemandForecastTool({ brandId }: { brandId: string }) {
{/* Historical Data */} {/* Historical Data */}
<div className="space-y-2"> <div className="space-y-2">
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<label className="block text-sm font-medium" style={labelStyle}>Historical Sales</label> <label className="block text-sm font-medium" style={labelStyle} htmlFor="fld-historical-sales-2">Historical Sales</label>
<button type="button" onClick={addRow} className="text-xs transition-colors" style={{ color: 'var(--admin-accent)' }}>+ Add Row</button> <button type="button" onClick={addRow} className="text-xs transition-colors" style={{ color: 'var(--admin-accent)' }}>+ Add Row</button>
</div> </div>
<AdminCard className="divide-y" style={{ border: '1px solid var(--admin-border)' }}> <AdminCard className="divide-y" style={{ border: '1px solid var(--admin-border)' }}>
@@ -1649,7 +1649,7 @@ function DemandForecastTool({ brandId }: { brandId: string }) {
</div> </div>
{historicalData.map((row, i) => ( {historicalData.map((row, i) => (
<div key={`hist-${i}-${row.date}-${row.stop}`} className="grid grid-cols-4 gap-2 px-3 py-2 items-center"> <div key={`hist-${i}-${row.date}-${row.stop}`} className="grid grid-cols-4 gap-2 px-3 py-2 items-center">
<input aria-label="2026 04 01" <input id="fld-historical-sales-2" aria-label="2026 04 01"
type="text" type="text"
value={row.date} value={row.date}
onChange={(e) => updateRow(i, "date", e.target.value)} onChange={(e) => updateRow(i, "date", e.target.value)}
@@ -308,15 +308,16 @@ function IntegrationCard({
<p className="text-xs text-stone-400 mt-0.5">{integration.description}</p> <p className="text-xs text-stone-400 mt-0.5">{integration.description}</p>
</div> </div>
</div> </div>
<label className="relative inline-flex items-center cursor-pointer"> <div className="relative inline-flex items-center cursor-pointer">
<input <input
type="checkbox" type="checkbox"
checked={enabled} checked={enabled}
onChange={(e) => setEnabled(e.target.checked)} onChange={(e) => setEnabled(e.target.checked)}
aria-label={`Enable ${integration.name}`}
className="sr-only peer" className="sr-only peer"
/> />
<div className="w-11 h-6 bg-zinc-700 peer-focus:outline-none rounded-full peer peer-checked:after:translate-x-4 rtl:peer-checked:after:-translate-x-4 peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:start-[2px] after:bg-zinc-900 after:border-zinc-600 after:border after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-violet-600"></div> <div className="w-11 h-6 bg-zinc-700 peer-focus:outline-none rounded-full peer peer-checked:after:translate-x-4 rtl:peer-checked:after:-translate-x-4 peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:start-[2px] after:bg-zinc-900 after:border-zinc-600 after:border after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-violet-600"></div>
</label> </div>
</div> </div>
{/* Sync options */} {/* Sync options */}
@@ -393,7 +394,7 @@ function IntegrationCard({
{/* Environment toggle */} {/* Environment toggle */}
<div className="mb-5"> <div className="mb-5">
<label className="block text-xs font-medium text-zinc-400 mb-1">Environment</label> <p className="block text-xs font-medium text-zinc-400 mb-1">Environment</p>
<div className="flex gap-2"> <div className="flex gap-2">
{["sandbox", "production"].map((e) => ( {["sandbox", "production"].map((e) => (
<button type="button" <button type="button"
+5 -5
View File
@@ -1583,7 +1583,7 @@ function OrdersTab({ orders, customers, brandId, onMsg, onRefresh }: {
className="rounded-lg border border-[var(--admin-border)] px-3 py-1.5 text-sm outline-none focus:border-[var(--admin-accent)]" /> className="rounded-lg border border-[var(--admin-border)] px-3 py-1.5 text-sm outline-none focus:border-[var(--admin-accent)]" />
</div> </div>
<div className="flex-1 min-w-[180px]"> <div className="flex-1 min-w-[180px]">
<label className="block text-xs font-medium text-[var(--admin-text-muted)] mb-1">Search</label> <label className="block text-xs font-medium text-[var(--admin-text-muted)] mb-1" htmlFor="fld-search">Search</label>
<AdminSearchInput <AdminSearchInput
value={searchQuery} value={searchQuery}
onChange={e => setSearchQuery(e.target.value)} onChange={e => setSearchQuery(e.target.value)}
@@ -1653,7 +1653,7 @@ function OrdersTab({ orders, customers, brandId, onMsg, onRefresh }: {
<thead className="bg-[var(--admin-bg-subtle)] text-[var(--admin-text-muted)]"> <thead className="bg-[var(--admin-bg-subtle)] text-[var(--admin-text-muted)]">
<tr> <tr>
<th className="px-5 py-3 font-semibold text-left w-10"> <th className="px-5 py-3 font-semibold text-left w-10">
<input aria-label="Checkbox" type="checkbox" checked={selected.size === filtered.length && filtered.length > 0} onChange={toggleAll} className="rounded" /> <input id="fld-search" aria-label="Checkbox" type="checkbox" checked={selected.size === filtered.length && filtered.length > 0} onChange={toggleAll} className="rounded" />
</th> </th>
<th className="px-5 py-3 font-semibold text-left">Invoice</th> <th className="px-5 py-3 font-semibold text-left">Invoice</th>
<th className="px-5 py-3 font-semibold text-left">Customer</th> <th className="px-5 py-3 font-semibold text-left">Customer</th>
@@ -2112,7 +2112,7 @@ function SettingsTab({ settings, brandId, onMsg, onRefresh, canManageSettings }:
{/* Team Notification Recipients */} {/* Team Notification Recipients */}
<div className="col-span-2 rounded-2xl bg-[var(--admin-bg-subtle)] p-5 ring-1 ring-[var(--admin-border)]"> <div className="col-span-2 rounded-2xl bg-[var(--admin-bg-subtle)] p-5 ring-1 ring-[var(--admin-border)]">
<label className="block text-sm font-semibold text-[var(--admin-text-primary)] mb-1">Team Notification Recipients</label> <label className="block text-sm font-semibold text-[var(--admin-text-primary)] mb-1" htmlFor="fld-team-notification-recipients">Team Notification Recipients</label>
<p className="text-xs text-[var(--admin-text-muted)] mb-4 leading-relaxed"> <p className="text-xs text-[var(--admin-text-muted)] mb-4 leading-relaxed">
These team members receive all wholesale notifications for this brand new orders, These team members receive all wholesale notifications for this brand new orders,
deposits, fulfillments, price sheets, and pickup reminders. If no recipients are deposits, fulfillments, price sheets, and pickup reminders. If no recipients are
@@ -2131,7 +2131,7 @@ function SettingsTab({ settings, brandId, onMsg, onRefresh, canManageSettings }:
<div className="space-y-2 mb-4"> <div className="space-y-2 mb-4">
{form.notificationRecipients.map((r: NotificationRecipient, idx: number) => ( {form.notificationRecipients.map((r: NotificationRecipient, idx: number) => (
<div key={r.email || `recipient-${idx}`} className={`flex items-center gap-2 rounded-xl px-3 py-2.5 bg-white ring-1 ${r.active ? "ring-[var(--admin-border)]" : "ring-[var(--admin-border-light)] opacity-70"}`}> <div key={r.email || `recipient-${idx}`} className={`flex items-center gap-2 rounded-xl px-3 py-2.5 bg-white ring-1 ${r.active ? "ring-[var(--admin-border)]" : "ring-[var(--admin-border-light)] opacity-70"}`}>
<input aria-label="Checkbox" <input id="fld-team-notification-recipients" aria-label="Checkbox"
type="checkbox" checked={r.active} onChange={() => toggleRecipient(idx)} type="checkbox" checked={r.active} onChange={() => toggleRecipient(idx)}
className="rounded border-[var(--admin-border)] mt-0.5" title={r.active ? "Active — receives notifications" : "Inactive"} /> className="rounded border-[var(--admin-border)] mt-0.5" title={r.active ? "Active — receives notifications" : "Inactive"} />
<div className="flex-1 min-w-0"> <div className="flex-1 min-w-0">
@@ -2162,7 +2162,7 @@ function SettingsTab({ settings, brandId, onMsg, onRefresh, canManageSettings }:
</div> </div>
{/* Webhook Settings */} {/* Webhook Settings */}
<div className="col-span-2 rounded-2xl bg-[var(--admin-bg-subtle)] p-5 ring-1 ring-[var(--admin-border)]"> <div className="col-span-2 rounded-2xl bg-[var(--admin-bg-subtle)] p-5 ring-1 ring-[var(--admin-border)]">
<label className="block text-sm font-semibold text-[var(--admin-text-primary)] mb-1">Webhook Integration</label> <label className="block text-sm font-semibold text-[var(--admin-text-primary)] mb-1" htmlFor="fld-webhook-integration">Webhook Integration</label>
<p className="text-xs text-[var(--admin-text-muted)] mb-4 leading-relaxed"> <p className="text-xs text-[var(--admin-text-muted)] mb-4 leading-relaxed">
Send order events to external systems (Harvest Point, ERPs, etc.). Payload is signed Send order events to external systems (Harvest Point, ERPs, etc.). Payload is signed
with HMAC-SHA256. Enable and configure the URL and secret below. with HMAC-SHA256. Enable and configure the URL and secret below.
@@ -697,11 +697,11 @@ export default function WholesalePortalClient({
<div className="rounded-2xl bg-white shadow-sm ring-1 ring-slate-200 p-5"> <div className="rounded-2xl bg-white shadow-sm ring-1 ring-slate-200 p-5">
<div className="flex flex-col sm:flex-row gap-4 sm:items-end"> <div className="flex flex-col sm:flex-row gap-4 sm:items-end">
<div className="flex-1"> <div className="flex-1">
<label className="block text-sm font-semibold text-slate-700 mb-1.5"> <label className="block text-sm font-semibold text-slate-700 mb-1.5" htmlFor="fld-preferred-pickup-date-optional">
Preferred Pickup Date Preferred Pickup Date
<span className="text-slate-400 font-normal ml-1">(optional)</span> <span className="text-slate-400 font-normal ml-1">(optional)</span>
</label> </label>
<input aria-label="Date" <input id="fld-preferred-pickup-date-optional" aria-label="Date"
type="date" type="date"
value={pickupDate} value={pickupDate}
onChange={e => setPickupDate(e.target.value)} onChange={e => setPickupDate(e.target.value)}
+35 -12
View File
@@ -1,6 +1,6 @@
"use client"; "use client";
import { useState, useCallback, useEffect } from "react"; import { useState, useCallback, useEffect, useRef } from "react";
import { getAbandonedCarts, manuallyCloseAbandonedCart, resendAbandonedCartEmail, type AbandonedCart } from "@/actions/email-automation/abandoned-cart"; import { getAbandonedCarts, manuallyCloseAbandonedCart, resendAbandonedCartEmail, type AbandonedCart } from "@/actions/email-automation/abandoned-cart";
type Props = { brandId: string }; type Props = { brandId: string };
@@ -190,21 +190,45 @@ export default function AbandonedCartDashboard({ brandId }: Props) {
)} )}
{/* Cart detail modal */} {/* Cart detail modal */}
{cart && ( <CartDetailModal cart={cart} onClose={() => setSelectedCart(null)} />
<button </div>
type="button" );
aria-label="Close abandoned cart details" }
className="fixed inset-0 z-50 flex items-center justify-center p-4 bg-black/40 backdrop-blur-sm border-0 cursor-default"
onClick={() => setSelectedCart(null)} function CartDetailModal({ cart, onClose }: { cart: AbandonedCart | null; onClose: () => void }) {
const dialogRef = useRef<HTMLDialogElement>(null);
useEffect(() => {
const dialog = dialogRef.current;
if (!dialog) return;
if (cart && !dialog.open) dialog.showModal();
if (!cart && dialog.open) dialog.close();
const onCancel = (e: Event) => {
e.preventDefault();
onClose();
};
dialog.addEventListener("cancel", onCancel);
return () => {
dialog.removeEventListener("cancel", onCancel);
};
}, [cart, onClose]);
if (!cart) return null;
return (
<dialog
ref={dialogRef}
aria-label="Abandoned cart details"
className="w-full max-w-full max-h-full m-0 p-0 bg-transparent"
style={{ backgroundColor: "transparent" }}
> >
<div className="bg-white border border-[var(--admin-border)] rounded-2xl w-full max-w-lg shadow-xl" <div className="fixed inset-0 z-50 flex items-center justify-center p-4" style={{ backgroundColor: "rgba(60, 56, 37, 0.5)" }}>
onClick={e => e.stopPropagation()}> <div className="bg-white border border-[var(--admin-border)] rounded-2xl w-full max-w-lg shadow-xl">
<div className="px-6 py-4 border-b border-[var(--admin-border)] flex items-center justify-between"> <div className="px-6 py-4 border-b border-[var(--admin-border)] flex items-center justify-between">
<div> <div>
<h3 className="text-lg font-bold text-[var(--admin-text-primary)]">Abandoned Cart</h3> <h3 className="text-lg font-bold text-[var(--admin-text-primary)]">Abandoned Cart</h3>
<p className="text-xs text-[var(--admin-text-muted)]">{cart.contact_email}</p> <p className="text-xs text-[var(--admin-text-muted)]">{cart.contact_email}</p>
</div> </div>
<button type="button" onClick={() => setSelectedCart(null)} className="text-[var(--admin-text-muted)] hover:text-[var(--admin-text-primary)] text-xl leading-none">&times;</button> <button type="button" onClick={onClose} className="text-[var(--admin-text-muted)] hover:text-[var(--admin-text-primary)] text-xl leading-none" aria-label="Close abandoned cart details">&times;</button>
</div> </div>
<div className="p-6 space-y-4"> <div className="p-6 space-y-4">
<div className="flex items-start gap-3 bg-stone-50 rounded-xl p-3"> <div className="flex items-start gap-3 bg-stone-50 rounded-xl p-3">
@@ -250,8 +274,7 @@ export default function AbandonedCartDashboard({ brandId }: Props) {
)} )}
</div> </div>
</div> </div>
</button>
)}
</div> </div>
</dialog>
); );
} }
+2 -2
View File
@@ -318,9 +318,9 @@ export default function AddStopModal({ isOpen, onClose, brandId, duplicateFrom,
{/* Row 5 — Status: segmented control (compact, replaces two giant buttons) */} {/* Row 5 — Status: segmented control (compact, replaces two giant buttons) */}
<div className="ha-field pt-0.5"> <div className="ha-field pt-0.5">
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<label className="ha-field-label"> <p className="ha-field-label">
<span>Visibility</span> <span>Visibility</span>
</label> </p>
<span className="text-[10px] text-[var(--admin-text-muted)] leading-none"> <span className="text-[10px] text-[var(--admin-text-muted)] leading-none">
Draft is hidden from customers Draft is hidden from customers
</span> </span>
+4 -4
View File
@@ -481,7 +481,7 @@ export default function AdminOrdersPanel({
key={stop.id} key={stop.id}
className="flex items-center gap-3 rounded-lg px-3 py-2 cursor-pointer transition-colors" className="flex items-center gap-3 rounded-lg px-3 py-2 cursor-pointer transition-colors"
style={{ color: "var(--admin-text-primary)" }} style={{ color: "var(--admin-text-primary)" }}
> htmlFor="fld-togglestopstopid-classnameh-4-w-4-rounde">
<input <input
type="checkbox" type="checkbox"
checked={selectedStops.includes(stop.id)} checked={selectedStops.includes(stop.id)}
@@ -637,7 +637,7 @@ export default function AdminOrdersPanel({
<thead style={{ backgroundColor: "var(--admin-bg)" }}> <thead style={{ backgroundColor: "var(--admin-bg)" }}>
<tr style={{ borderBottom: "1px solid var(--admin-border)" }}> <tr style={{ borderBottom: "1px solid var(--admin-border)" }}>
<th className="w-10 px-4 py-3"> <th className="w-10 px-4 py-3">
<input aria-label="Checkbox" <input id="fld-togglestopstopid-classnameh-4-w-4-rounde" aria-label="Checkbox"
type="checkbox" type="checkbox"
checked={selectedOrders.size === paginatedOrders.length && paginatedOrders.length > 0} checked={selectedOrders.size === paginatedOrders.length && paginatedOrders.length > 0}
onChange={toggleSelectAll} onChange={toggleSelectAll}
@@ -945,7 +945,7 @@ export default function AdminOrdersPanel({
<label <label
className="block text-xs font-semibold" className="block text-xs font-semibold"
style={{ color: "var(--admin-text-secondary)" }} style={{ color: "var(--admin-text-secondary)" }}
> htmlFor="fld-items">
Items Items
</label> </label>
<span <span
@@ -1006,7 +1006,7 @@ export default function AdminOrdersPanel({
{prod?.name ?? item.product_id} {prod?.name ?? item.product_id}
</div> </div>
<div className="col-span-2"> <div className="col-span-2">
<input aria-label="Number" <input id="fld-items" aria-label="Number"
type="number" type="number"
min={1} min={1}
value={item.quantity} value={item.quantity}
+2 -2
View File
@@ -374,7 +374,7 @@ export default function CreateUserModal({ isOpen, onClose, onSuccess, brands, cu
{/* Role */} {/* Role */}
<div> <div>
<label className="block text-sm font-medium text-[var(--admin-text-primary)] mb-2">Role</label> <p className="block text-sm font-medium text-[var(--admin-text-primary)] mb-2">Role</p>
<div className="space-y-2"> <div className="space-y-2">
{availableRoles.map((r) => ( {availableRoles.map((r) => (
<label key={r} className="flex items-center gap-3 rounded-lg border border-[var(--admin-border)] px-3 py-2.5 cursor-pointer hover:bg-[var(--admin-bg)] transition-colors"> <label key={r} className="flex items-center gap-3 rounded-lg border border-[var(--admin-border)] px-3 py-2.5 cursor-pointer hover:bg-[var(--admin-bg)] transition-colors">
@@ -417,7 +417,7 @@ export default function CreateUserModal({ isOpen, onClose, onSuccess, brands, cu
{/* Permissions */} {/* Permissions */}
<div> <div>
<label className="block text-sm font-medium text-[var(--admin-text-primary)] mb-2">Permissions</label> <p className="block text-sm font-medium text-[var(--admin-text-primary)] mb-2">Permissions</p>
<div className="space-y-2"> <div className="space-y-2">
{ALL_FLAGS.map((flag) => ( {ALL_FLAGS.map((flag) => (
<label key={flag} className="flex items-center justify-between rounded-lg border border-[var(--admin-border)] px-3 sm:px-4 py-2 sm:py-2.5 hover:bg-[var(--admin-bg)] cursor-pointer transition-colors"> <label key={flag} className="flex items-center justify-between rounded-lg border border-[var(--admin-border)] px-3 sm:px-4 py-2 sm:py-2.5 hover:bg-[var(--admin-bg)] cursor-pointer transition-colors">
+2 -2
View File
@@ -357,9 +357,9 @@ export default function EditStopModal({ isOpen, onClose, brandId, stop, onSucces
{/* Row 5 — Status: segmented control */} {/* Row 5 — Status: segmented control */}
<div className="ha-field pt-0.5"> <div className="ha-field pt-0.5">
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<label className="ha-field-label"> <p className="ha-field-label">
<span>Visibility</span> <span>Visibility</span>
</label> </p>
<span className="text-[10px] text-[var(--admin-text-muted)] leading-none"> <span className="text-[10px] text-[var(--admin-text-muted)] leading-none">
Draft is hidden from customers Draft is hidden from customers
</span> </span>
@@ -544,7 +544,7 @@ export default function CampaignComposerPage({ brandId, campaigns, templates, se
{/* Preview */} {/* Preview */}
<div> <div>
<label className="block text-sm font-semibold text-stone-700 mb-1.5">Preview</label> <p className="block text-sm font-semibold text-stone-700 mb-1.5">Preview</p>
<EmailPreview subject={subject} body={bodyText} /> <EmailPreview subject={subject} body={bodyText} />
</div> </div>
</div> </div>
@@ -583,10 +583,10 @@ export default function CampaignComposerPage({ brandId, campaigns, templates, se
{/* Campaign settings */} {/* Campaign settings */}
<div className="space-y-4"> <div className="space-y-4">
<div> <div>
<label className="block text-sm font-semibold text-stone-700 mb-1.5"> <label htmlFor="fld-campaign-name" className="block text-sm font-semibold text-stone-700 mb-1.5">
Campaign Name <span className="text-red-500">*</span> Campaign Name <span className="text-red-500">*</span>
</label> </label>
<input aria-label=". May Sweet Corn Promotion" <input id="fld-campaign-name" aria-label=". May Sweet Corn Promotion"
type="text" type="text"
value={name} value={name}
onChange={(e) => setName(e.target.value)} onChange={(e) => setName(e.target.value)}
@@ -721,7 +721,7 @@ export default function CampaignComposerPage({ brandId, campaigns, templates, se
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6"> <div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
{/* Campaign type selection */} {/* Campaign type selection */}
<div> <div>
<label className="block text-sm font-semibold text-stone-700 mb-3">Campaign Type</label> <p className="block text-sm font-semibold text-stone-700 mb-3">Campaign Type</p>
<div className="space-y-3"> <div className="space-y-3">
{CAMPAIGN_TYPES.map((ct) => ( {CAMPAIGN_TYPES.map((ct) => (
<button type="button" <button type="button"
@@ -751,7 +751,7 @@ export default function CampaignComposerPage({ brandId, campaigns, templates, se
{/* Send timing */} {/* Send timing */}
<div> <div>
<label className="block text-sm font-semibold text-stone-700 mb-3">When to Send</label> <p className="block text-sm font-semibold text-stone-700 mb-3">When to Send</p>
<div className="space-y-3"> <div className="space-y-3">
<label className={`flex items-center gap-4 p-4 rounded-xl border-2 cursor-pointer transition-all ${ <label className={`flex items-center gap-4 p-4 rounded-xl border-2 cursor-pointer transition-all ${
sendNow ? "border-emerald-500 bg-emerald-50" : "border-stone-200 hover:border-stone-300" sendNow ? "border-emerald-500 bg-emerald-50" : "border-stone-200 hover:border-stone-300"
@@ -87,10 +87,10 @@ export default function SegmentEditModal({ initialName = "", initialDescription
/> />
</div> </div>
<div> <div>
<label className="block text-sm font-medium text-[var(--admin-text-primary)] mb-1.5"> <label className="block text-sm font-medium text-[var(--admin-text-primary)] mb-1.5" htmlFor="fld-description-optional">
Description <span className="text-[var(--admin-text-muted)] font-normal">(optional)</span> Description <span className="text-[var(--admin-text-muted)] font-normal">(optional)</span>
</label> </label>
<textarea aria-label=". Customers Who Pick Up At The Fort Pierce Stop Regularly" <textarea id="fld-description-optional" aria-label=". Customers Who Pick Up At The Fort Pierce Stop Regularly"
value={description} value={description}
onChange={(e) => setDescription(e.target.value)} onChange={(e) => setDescription(e.target.value)}
placeholder="e.g. Customers who pick up at the Fort Pierce stop regularly" placeholder="e.g. Customers who pick up at the Fort Pierce stop regularly"
@@ -161,7 +161,7 @@ export default function MessageCustomersSection({
<> <>
{/* Audience selector */} {/* Audience selector */}
<div> <div>
<label className="mb-2 block text-sm font-medium text-zinc-300"> <label className="mb-2 block text-sm font-medium text-zinc-300" htmlFor="fld-audience---recipientslength-with-contact">
Audience {" "} Audience {" "}
<span className="text-zinc-500"> <span className="text-zinc-500">
{recipients.length} with contact info {recipients.length} with contact info
@@ -194,7 +194,7 @@ export default function MessageCustomersSection({
{/* Channel selector */} {/* Channel selector */}
<div> <div>
<label className="mb-2 block text-sm font-medium text-zinc-300"> <label className="mb-2 block text-sm font-medium text-zinc-300" htmlFor="fld-channel">
Channel Channel
</label> </label>
<div className="flex gap-2"> <div className="flex gap-2">
+14 -14
View File
@@ -127,10 +127,10 @@ export default function NewStopForm({ duplicateFrom }: Props) {
<div className="grid grid-cols-2 gap-4"> <div className="grid grid-cols-2 gap-4">
<div> <div>
<label className="block text-xs font-semibold text-stone-700 mb-1.5"> <label htmlFor="fld-city" className="block text-xs font-semibold text-stone-700 mb-1.5">
City <span className="text-red-500">*</span> City <span className="text-red-500">*</span>
</label> </label>
<input aria-label=". Denver" <input id="fld-city" aria-label=". Denver"
type="text" type="text"
value={city} value={city}
onChange={(e) => { onChange={(e) => {
@@ -152,10 +152,10 @@ export default function NewStopForm({ duplicateFrom }: Props) {
</div> </div>
<div> <div>
<label className="block text-xs font-semibold text-stone-700 mb-1.5"> <label htmlFor="fld-state" className="block text-xs font-semibold text-stone-700 mb-1.5">
State <span className="text-red-500">*</span> State <span className="text-red-500">*</span>
</label> </label>
<input aria-label=". CO" <input id="fld-state" aria-label=". CO"
type="text" type="text"
value={state} value={state}
onChange={(e) => { onChange={(e) => {
@@ -178,10 +178,10 @@ export default function NewStopForm({ duplicateFrom }: Props) {
</div> </div>
<div> <div>
<label className="block text-xs font-semibold text-stone-700 mb-1.5"> <label htmlFor="fld-location-name" className="block text-xs font-semibold text-stone-700 mb-1.5">
Location Name <span className="text-red-500">*</span> Location Name <span className="text-red-500">*</span>
</label> </label>
<input aria-label=". Southwest Plaza Parking Lot" <input id="fld-location-name" aria-label=". Southwest Plaza Parking Lot"
type="text" type="text"
value={location} value={location}
onChange={(e) => { onChange={(e) => {
@@ -204,10 +204,10 @@ export default function NewStopForm({ duplicateFrom }: Props) {
<div className="grid grid-cols-2 gap-4"> <div className="grid grid-cols-2 gap-4">
<div> <div>
<label className="block text-xs font-semibold text-stone-700 mb-1.5"> <label htmlFor="fld-date" className="block text-xs font-semibold text-stone-700 mb-1.5">
Date <span className="text-red-500">*</span> Date <span className="text-red-500">*</span>
</label> </label>
<input aria-label="Date" <input id="fld-date" aria-label="Date"
type="date" type="date"
value={date} value={date}
onChange={(e) => { onChange={(e) => {
@@ -228,10 +228,10 @@ export default function NewStopForm({ duplicateFrom }: Props) {
</div> </div>
<div> <div>
<label className="block text-xs font-semibold text-stone-700 mb-1.5"> <label htmlFor="fld-time" className="block text-xs font-semibold text-stone-700 mb-1.5">
Time <span className="text-red-500">*</span> Time <span className="text-red-500">*</span>
</label> </label>
<input aria-label=". 8:00 AM 2:00 PM" <input id="fld-time" aria-label=". 8:00 AM 2:00 PM"
type="text" type="text"
value={time} value={time}
onChange={(e) => { onChange={(e) => {
@@ -254,10 +254,10 @@ export default function NewStopForm({ duplicateFrom }: Props) {
</div> </div>
<div> <div>
<label className="block text-xs font-semibold text-stone-700 mb-1.5"> <label htmlFor="fld-brand" className="block text-xs font-semibold text-stone-700 mb-1.5">
Brand <span className="text-red-500">*</span> Brand <span className="text-red-500">*</span>
</label> </label>
<select aria-label="Select" <select id="fld-brand" aria-label="Select"
value={brandId} value={brandId}
onChange={(e) => { onChange={(e) => {
setBrandId(e.target.value); setBrandId(e.target.value);
@@ -318,9 +318,9 @@ export default function NewStopForm({ duplicateFrom }: Props) {
</div> </div>
<div> <div>
<label className="block text-xs font-semibold text-stone-700 mb-1.5">Order Cutoff</label> <label htmlFor="fld-cutoff" className="block text-xs font-semibold text-stone-700 mb-1.5">Order Cutoff</label>
<p className="text-[10px] text-stone-500 mb-2">Customers must order before this time to be included at this stop.</p> <p className="text-[10px] text-stone-500 mb-2">Customers must order before this time to be included at this stop.</p>
<input aria-label="Datetime Local" <input id="fld-cutoff" aria-label="Datetime Local"
type="datetime-local" type="datetime-local"
value={cutoffTime} value={cutoffTime}
onChange={(e) => setCutoffTime(e.target.value)} onChange={(e) => setCutoffTime(e.target.value)}
+2 -2
View File
@@ -277,10 +277,10 @@ export default function OrderPaymentSection({
)} )}
<form onSubmit={handleRefund} className="space-y-3"> <form onSubmit={handleRefund} className="space-y-3">
<div> <div>
<label className="mb-1 block text-xs font-semibold text-stone-500">Amount</label> <label className="mb-1 block text-xs font-semibold text-stone-500" htmlFor="fld-amount">Amount</label>
<div className="relative"> <div className="relative">
<span className="absolute left-3 top-1/2 -translate-y-1/2 text-stone-400 text-sm">$</span> <span className="absolute left-3 top-1/2 -translate-y-1/2 text-stone-400 text-sm">$</span>
<input aria-label="Number" <input id="fld-amount" aria-label="Number"
type="number" type="number"
step="0.01" step="0.01"
min="0.01" min="0.01"
+3 -3
View File
@@ -273,7 +273,7 @@ export default function ProductEditForm({ product, brands }: ProductEditFormProp
</AdminInput> </AdminInput>
<div> <div>
<label className="mb-2 block text-sm font-medium text-[var(--admin-text-primary)]">Status</label> <label className="mb-2 block text-sm font-medium text-[var(--admin-text-primary)]" htmlFor="fld-status">Status</label><label className="mb-2 block text-sm font-medium text-[var(--admin-text-primary)]">Status</label>
<button type="button" <button type="button"
onClick={() => setActive((v) => !v)} onClick={() => setActive((v) => !v)}
className={`w-full rounded-xl px-4 py-3 text-sm font-medium transition-colors ${ className={`w-full rounded-xl px-4 py-3 text-sm font-medium transition-colors ${
@@ -287,7 +287,7 @@ export default function ProductEditForm({ product, brands }: ProductEditFormProp
</div> </div>
<div> <div>
<label className="mb-2 block text-sm font-medium text-[var(--admin-text-primary)]">Taxable</label> <label className="mb-2 block text-sm font-medium text-[var(--admin-text-primary)]" htmlFor="fld-taxable">Taxable</label><label className="mb-2 block text-sm font-medium text-[var(--admin-text-primary)]">Taxable</label>
<button type="button" <button type="button"
onClick={() => setIs_taxable((v) => !v)} onClick={() => setIs_taxable((v) => !v)}
className={`w-full rounded-xl px-4 py-3 text-sm font-medium transition-colors flex items-center gap-3 ${ className={`w-full rounded-xl px-4 py-3 text-sm font-medium transition-colors flex items-center gap-3 ${
@@ -317,7 +317,7 @@ export default function ProductEditForm({ product, brands }: ProductEditFormProp
</AdminInput> </AdminInput>
<div> <div>
<label className="mb-1 block text-sm font-medium text-[var(--admin-text-primary)]">Product Image</label> <label className="mb-1 block text-sm font-medium text-[var(--admin-text-primary)]" htmlFor="fld-product-image">Product Image</label><label className="mb-1 block text-sm font-medium text-[var(--admin-text-primary)]">Product Image</label>
<p className="text-xs text-[var(--admin-text-muted)] mb-2">JPG, PNG, WebP · 1200px max width · max 5MB (ideally under 2MB)</p> <p className="text-xs text-[var(--admin-text-muted)] mb-2">JPG, PNG, WebP · 1200px max width · max 5MB (ideally under 2MB)</p>
<div <div
+10 -10
View File
@@ -331,27 +331,27 @@ export default function SettingsSections({ brandId, workersOnly, tasksOnly }: Pr
</select> </select>
</div> </div>
<div> <div>
<label className="block text-xs font-semibold uppercase tracking-widest text-stone-500 mb-2">Pay period length</label> <label className="block text-xs font-semibold uppercase tracking-widest text-stone-500 mb-2" htmlFor="fld-pay-period-length">Pay period length</label>
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<input aria-label="Number" type="number" min={1} max={31} value={payPeriodLength} <input id="fld-pay-period-length" aria-label="Number" type="number" min={1} max={31} value={payPeriodLength}
onChange={e => setPayPeriodLength(Number(e.target.value))} onChange={e => setPayPeriodLength(Number(e.target.value))}
className="flex-1 px-4 py-3 rounded-xl border border-stone-200 bg-white text-stone-900 focus:outline-none focus:border-emerald-500 transition-colors" /> className="flex-1 px-4 py-3 rounded-xl border border-stone-200 bg-white text-stone-900 focus:outline-none focus:border-emerald-500 transition-colors" />
<span className="text-sm text-stone-500">days</span> <span className="text-sm text-stone-500">days</span>
</div> </div>
</div> </div>
<div> <div>
<label className="block text-xs font-semibold uppercase tracking-widest text-stone-500 mb-2">Daily overtime threshold</label> <label className="block text-xs font-semibold uppercase tracking-widest text-stone-500 mb-2" htmlFor="fld-daily-overtime-threshold">Daily overtime threshold</label>
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<input aria-label="Number" type="number" min={1} max={24} value={dailyOvertimeThreshold} <input id="fld-daily-overtime-threshold" aria-label="Number" type="number" min={1} max={24} value={dailyOvertimeThreshold}
onChange={e => setDailyOvertimeThreshold(Number(e.target.value))} onChange={e => setDailyOvertimeThreshold(Number(e.target.value))}
className="flex-1 px-4 py-3 rounded-xl border border-stone-200 bg-white text-stone-900 focus:outline-none focus:border-emerald-500 transition-colors" /> className="flex-1 px-4 py-3 rounded-xl border border-stone-200 bg-white text-stone-900 focus:outline-none focus:border-emerald-500 transition-colors" />
<span className="text-sm text-stone-500">hrs</span> <span className="text-sm text-stone-500">hrs</span>
</div> </div>
</div> </div>
<div> <div>
<label className="block text-xs font-semibold uppercase tracking-widest text-stone-500 mb-2">Weekly overtime threshold</label> <label className="block text-xs font-semibold uppercase tracking-widest text-stone-500 mb-2" htmlFor="fld-weekly-overtime-threshold">Weekly overtime threshold</label>
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<input aria-label="Number" type="number" min={1} max={80} value={weeklyOvertimeThreshold} <input id="fld-weekly-overtime-threshold" aria-label="Number" type="number" min={1} max={80} value={weeklyOvertimeThreshold}
onChange={e => setWeeklyOvertimeThreshold(Number(e.target.value))} onChange={e => setWeeklyOvertimeThreshold(Number(e.target.value))}
className="flex-1 px-4 py-3 rounded-xl border border-stone-200 bg-white text-stone-900 focus:outline-none focus:border-emerald-500 transition-colors" /> className="flex-1 px-4 py-3 rounded-xl border border-stone-200 bg-white text-stone-900 focus:outline-none focus:border-emerald-500 transition-colors" />
<span className="text-sm text-stone-500">hrs</span> <span className="text-sm text-stone-500">hrs</span>
@@ -405,9 +405,9 @@ export default function SettingsSections({ brandId, workersOnly, tasksOnly }: Pr
<h3 className="text-sm font-semibold text-stone-800 mb-4">Notification Recipients</h3> <h3 className="text-sm font-semibold text-stone-800 mb-4">Notification Recipients</h3>
<div className="space-y-4"> <div className="space-y-4">
<div> <div>
<label className="block text-xs font-semibold uppercase tracking-widest text-stone-500 mb-2">Email addresses</label> <label className="block text-xs font-semibold uppercase tracking-widest text-stone-500 mb-2" htmlFor="fld-email-addresses">Email addresses</label>
<div className="flex gap-2"> <div className="flex gap-2">
<input aria-label="Manager@farm.com" value={newEmail} <input id="fld-email-addresses" aria-label="Manager@farm.com" value={newEmail}
onChange={e => setNewEmail(e.target.value)} onChange={e => setNewEmail(e.target.value)}
onKeyDown={e => e.key === "Enter" && (addEmail(), e.preventDefault())} onKeyDown={e => e.key === "Enter" && (addEmail(), e.preventDefault())}
className="flex-1 px-4 py-3 rounded-xl border border-stone-200 bg-white text-stone-900 placeholder:text-stone-400 focus:outline-none focus:border-emerald-500" className="flex-1 px-4 py-3 rounded-xl border border-stone-200 bg-white text-stone-900 placeholder:text-stone-400 focus:outline-none focus:border-emerald-500"
@@ -428,9 +428,9 @@ export default function SettingsSections({ brandId, workersOnly, tasksOnly }: Pr
</div> </div>
</div> </div>
<div> <div>
<label className="block text-xs font-semibold uppercase tracking-widest text-stone-500 mb-2">SMS numbers</label> <label className="block text-xs font-semibold uppercase tracking-widest text-stone-500 mb-2" htmlFor="fld-sms-numbers">SMS numbers</label>
<div className="flex gap-2"> <div className="flex gap-2">
<input aria-label="+1234567890" value={newSms} <input id="fld-sms-numbers" aria-label="+1234567890" value={newSms}
onChange={e => setNewSms(e.target.value)} onChange={e => setNewSms(e.target.value)}
onKeyDown={e => e.key === "Enter" && (addSms(), e.preventDefault())} onKeyDown={e => e.key === "Enter" && (addSms(), e.preventDefault())}
className="flex-1 px-4 py-3 rounded-xl border border-stone-200 bg-white text-stone-900 placeholder:text-stone-400 focus:outline-none focus:border-emerald-500" className="flex-1 px-4 py-3 rounded-xl border border-stone-200 bg-white text-stone-900 placeholder:text-stone-400 focus:outline-none focus:border-emerald-500"
+7 -7
View File
@@ -188,10 +188,10 @@ export default function StopEditForm({ stop, brands, onSaved }: StopEditFormProp
<div className="grid grid-cols-2 gap-4"> <div className="grid grid-cols-2 gap-4">
<div> <div>
<label className="block text-xs font-semibold text-stone-700 mb-1.5"> <label className="block text-xs font-semibold text-stone-700 mb-1.5" htmlFor="fld-city-">
City <span className="text-red-500">*</span> City <span className="text-red-500">*</span>
</label> </label>
<input aria-label="City Name" <input id="fld-city-" aria-label="City Name"
type="text" type="text"
value={city} value={city}
onChange={(e) => { onChange={(e) => {
@@ -213,10 +213,10 @@ export default function StopEditForm({ stop, brands, onSaved }: StopEditFormProp
</div> </div>
<div> <div>
<label className="block text-xs font-semibold text-stone-700 mb-1.5"> <label className="block text-xs font-semibold text-stone-700 mb-1.5" htmlFor="fld-state-">
State <span className="text-red-500">*</span> State <span className="text-red-500">*</span>
</label> </label>
<input aria-label="State Code" <input id="fld-state-" aria-label="State Code"
type="text" type="text"
value={state} value={state}
onChange={(e) => { onChange={(e) => {
@@ -240,10 +240,10 @@ export default function StopEditForm({ stop, brands, onSaved }: StopEditFormProp
<div className="grid grid-cols-2 gap-4"> <div className="grid grid-cols-2 gap-4">
<div> <div>
<label className="block text-xs font-semibold text-stone-700 mb-1.5"> <label className="block text-xs font-semibold text-stone-700 mb-1.5" htmlFor="fld-date-">
Date <span className="text-red-500">*</span> Date <span className="text-red-500">*</span>
</label> </label>
<input aria-label="Date" <input id="fld-date-" aria-label="Date"
type="date" type="date"
value={date} value={date}
onChange={(e) => { onChange={(e) => {
@@ -332,7 +332,7 @@ export default function StopEditForm({ stop, brands, onSaved }: StopEditFormProp
</AdminInput> </AdminInput>
<div> <div>
<label className="mb-2 block text-sm font-medium text-stone-700">Status</label> <label className="mb-2 block text-sm font-medium text-stone-700" htmlFor="fld-status">Status</label><label className="mb-2 block text-sm font-medium text-stone-700">Status</label>
<button <button
type="button" type="button"
onClick={() => setActive((v) => !v)} onClick={() => setActive((v) => !v)}
+6 -6
View File
@@ -121,9 +121,9 @@ export default function StopMessagingForm({
{/* Channel */} {/* Channel */}
<div> <div>
<label className="ha-field-label mb-2"> <p className="ha-field-label mb-2">
<span>Send via</span> <span>Send via</span>
</label> </p>
<div className="flex gap-2"> <div className="flex gap-2">
{(["sms", "email", "both"] as const).map((ch) => ( {(["sms", "email", "both"] as const).map((ch) => (
<button type="button" <button type="button"
@@ -143,9 +143,9 @@ export default function StopMessagingForm({
{/* Quick messages */} {/* Quick messages */}
<div> <div>
<label className="ha-field-label mb-2"> <p className="ha-field-label mb-2">
<span>Quick messages</span> <span>Quick messages</span>
</label> </p>
<div className="flex flex-wrap gap-2"> <div className="flex flex-wrap gap-2">
{quickMessages.map((qm) => ( {quickMessages.map((qm) => (
<button type="button" <button type="button"
@@ -165,10 +165,10 @@ export default function StopMessagingForm({
{/* Message preview */} {/* Message preview */}
<div> <div>
<label className="ha-field-label mb-2"> <label htmlFor="fld-stop-message" className="ha-field-label mb-2">
<span>Message</span> <span>Message</span>
</label> </label>
<textarea aria-label="Type Your Message..." <textarea id="fld-stop-message" aria-label="Type Your Message..."
value={message} value={message}
onChange={(e) => { onChange={(e) => {
setMessage(e.target.value); setMessage(e.target.value);
+3 -3
View File
@@ -515,7 +515,7 @@ export function TemplateEditForm({
<div className="rounded-xl border border-[var(--admin-border)] bg-white p-4 sm:p-6 space-y-4"> <div className="rounded-xl border border-[var(--admin-border)] bg-white p-4 sm:p-6 space-y-4">
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<h3 className="text-xs sm:text-sm font-semibold text-[var(--admin-text-muted)] uppercase tracking-wide">Message Body</h3> <h3 className="text-xs sm:text-sm font-semibold text-[var(--admin-text-muted)] uppercase tracking-wide">Message Body</h3>
<label className="flex items-center gap-2 text-xs sm:text-sm"> <label className="flex items-center gap-2 text-xs sm:text-sm" htmlFor="fld-sethtmlmodeetargetchecked-classnameround">
<input <input
type="checkbox" type="checkbox"
checked={htmlMode} checked={htmlMode}
@@ -555,7 +555,7 @@ export function TemplateEditForm({
/> />
</div> </div>
<div> <div>
<label className="block text-xs sm:text-sm font-medium text-[var(--admin-text-primary)] mb-1.5">Plain Text Fallback *</label> <label className="block text-xs sm:text-sm font-medium text-[var(--admin-text-primary)] mb-1.5" htmlFor="fld-plain-text-fallback-">Plain Text Fallback *</label>
<textarea aria-label="Plain Text Version..." <textarea aria-label="Plain Text Version..."
id="body-textarea" id="body-textarea"
value={bodyText} value={bodyText}
@@ -568,7 +568,7 @@ export function TemplateEditForm({
</> </>
) : ( ) : (
<div> <div>
<label className="block text-xs sm:text-sm font-medium text-[var(--admin-text-primary)] mb-1.5">Body (Plain Text) *</label> <label className="block text-xs sm:text-sm font-medium text-[var(--admin-text-primary)] mb-1.5" htmlFor="fld-body-plain-text-">Body (Plain Text) *</label>
<textarea aria-label="Message Body With Variables Like {{first Name}}..." <textarea aria-label="Message Body With Variables Like {{first Name}}..."
id="body-textarea" id="body-textarea"
value={bodyText} value={bodyText}
@@ -535,27 +535,27 @@ export default function TimeTrackingSettingsClient({ brandId }: TimeTrackingSett
</select> </select>
</div> </div>
<div> <div>
<label className="block text-xs font-semibold uppercase tracking-widest text-[var(--admin-text-muted)] mb-2">Pay period length</label> <label className="block text-xs font-semibold uppercase tracking-widest text-[var(--admin-text-muted)] mb-2" htmlFor="fld-pay-period-length">Pay period length</label>
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<input aria-label="Number" type="number" min={1} max={31} value={payPeriodLength} <input id="fld-pay-period-length" aria-label="Number" type="number" min={1} max={31} value={payPeriodLength}
onChange={e => setPayPeriodLength(Number(e.target.value))} onChange={e => setPayPeriodLength(Number(e.target.value))}
className="flex-1 px-4 py-3 rounded-xl border border-[var(--admin-border)] bg-white text-[var(--admin-text-primary)] focus:outline-none focus:border-[var(--admin-accent)] transition-colors" /> className="flex-1 px-4 py-3 rounded-xl border border-[var(--admin-border)] bg-white text-[var(--admin-text-primary)] focus:outline-none focus:border-[var(--admin-accent)] transition-colors" />
<span className="text-sm text-[var(--admin-text-muted)]">days</span> <span className="text-sm text-[var(--admin-text-muted)]">days</span>
</div> </div>
</div> </div>
<div> <div>
<label className="block text-xs font-semibold uppercase tracking-widest text-[var(--admin-text-muted)] mb-2">Daily overtime threshold</label> <label className="block text-xs font-semibold uppercase tracking-widest text-[var(--admin-text-muted)] mb-2" htmlFor="fld-daily-overtime-threshold">Daily overtime threshold</label>
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<input aria-label="Number" type="number" min={1} max={24} value={dailyOvertimeThreshold} <input id="fld-daily-overtime-threshold" aria-label="Number" type="number" min={1} max={24} value={dailyOvertimeThreshold}
onChange={e => setDailyOvertimeThreshold(Number(e.target.value))} onChange={e => setDailyOvertimeThreshold(Number(e.target.value))}
className="flex-1 px-4 py-3 rounded-xl border border-[var(--admin-border)] bg-white text-[var(--admin-text-primary)] focus:outline-none focus:border-[var(--admin-accent)] transition-colors" /> className="flex-1 px-4 py-3 rounded-xl border border-[var(--admin-border)] bg-white text-[var(--admin-text-primary)] focus:outline-none focus:border-[var(--admin-accent)] transition-colors" />
<span className="text-sm text-[var(--admin-text-muted)]">hrs</span> <span className="text-sm text-[var(--admin-text-muted)]">hrs</span>
</div> </div>
</div> </div>
<div> <div>
<label className="block text-xs font-semibold uppercase tracking-widest text-[var(--admin-text-muted)] mb-2">Weekly overtime threshold</label> <label className="block text-xs font-semibold uppercase tracking-widest text-[var(--admin-text-muted)] mb-2" htmlFor="fld-weekly-overtime-threshold">Weekly overtime threshold</label>
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<input aria-label="Number" type="number" min={1} max={80} value={weeklyOvertimeThreshold} <input id="fld-weekly-overtime-threshold" aria-label="Number" type="number" min={1} max={80} value={weeklyOvertimeThreshold}
onChange={e => setWeeklyOvertimeThreshold(Number(e.target.value))} onChange={e => setWeeklyOvertimeThreshold(Number(e.target.value))}
className="flex-1 px-4 py-3 rounded-xl border border-[var(--admin-border)] bg-white text-[var(--admin-text-primary)] focus:outline-none focus:border-[var(--admin-accent)] transition-colors" /> className="flex-1 px-4 py-3 rounded-xl border border-[var(--admin-border)] bg-white text-[var(--admin-text-primary)] focus:outline-none focus:border-[var(--admin-accent)] transition-colors" />
<span className="text-sm text-[var(--admin-text-muted)]">hrs</span> <span className="text-sm text-[var(--admin-text-muted)]">hrs</span>
@@ -616,9 +616,9 @@ export default function TimeTrackingSettingsClient({ brandId }: TimeTrackingSett
<div className="bg-white border border-[var(--admin-border)] rounded-xl p-6 space-y-4"> <div className="bg-white border border-[var(--admin-border)] rounded-xl p-6 space-y-4">
<h3 className="text-sm font-semibold text-[var(--admin-text-primary)]">Notification Recipients</h3> <h3 className="text-sm font-semibold text-[var(--admin-text-primary)]">Notification Recipients</h3>
<div> <div>
<label className="block text-xs font-semibold uppercase tracking-widest text-[var(--admin-text-muted)] mb-2">Email addresses</label> <label className="block text-xs font-semibold uppercase tracking-widest text-[var(--admin-text-muted)] mb-2" htmlFor="fld-email-addresses">Email addresses</label>
<div className="flex gap-2"> <div className="flex gap-2">
<input aria-label="Manager@farm.com" value={newEmail} <input id="fld-email-addresses" aria-label="Manager@farm.com" value={newEmail}
onChange={e => setNewEmail(e.target.value)} onChange={e => setNewEmail(e.target.value)}
onKeyDown={e => e.key === "Enter" && (addEmail(), e.preventDefault())} onKeyDown={e => e.key === "Enter" && (addEmail(), e.preventDefault())}
className="flex-1 px-4 py-3 rounded-xl border border-[var(--admin-border)] bg-white text-[var(--admin-text-primary)] placeholder:text-[var(--admin-text-muted)] focus:outline-none focus:border-[var(--admin-accent)] transition-colors" className="flex-1 px-4 py-3 rounded-xl border border-[var(--admin-border)] bg-white text-[var(--admin-text-primary)] placeholder:text-[var(--admin-text-muted)] focus:outline-none focus:border-[var(--admin-accent)] transition-colors"
@@ -635,9 +635,9 @@ export default function TimeTrackingSettingsClient({ brandId }: TimeTrackingSett
</div> </div>
</div> </div>
<div> <div>
<label className="block text-xs font-semibold uppercase tracking-widest text-[var(--admin-text-muted)] mb-2">SMS numbers</label> <label className="block text-xs font-semibold uppercase tracking-widest text-[var(--admin-text-muted)] mb-2" htmlFor="fld-sms-numbers">SMS numbers</label>
<div className="flex gap-2"> <div className="flex gap-2">
<input aria-label="+1234567890" value={newSms} <input id="fld-sms-numbers" aria-label="+1234567890" value={newSms}
onChange={e => setNewSms(e.target.value)} onChange={e => setNewSms(e.target.value)}
onKeyDown={e => e.key === "Enter" && (addSms(), e.preventDefault())} onKeyDown={e => e.key === "Enter" && (addSms(), e.preventDefault())}
className="flex-1 px-4 py-3 rounded-xl border border-[var(--admin-border)] bg-white text-[var(--admin-text-primary)] placeholder:text-[var(--admin-text-muted)] focus:outline-none focus:border-[var(--admin-accent)] transition-colors" className="flex-1 px-4 py-3 rounded-xl border border-[var(--admin-border)] bg-white text-[var(--admin-text-primary)] placeholder:text-[var(--admin-text-muted)] focus:outline-none focus:border-[var(--admin-accent)] transition-colors"
+8 -8
View File
@@ -561,9 +561,9 @@ export default function UsersPage({ initialUsers, brands, currentUser, onUserCre
{/* Password (create only) */} {/* Password (create only) */}
{editing.isNew && ( {editing.isNew && (
<div> <div>
<label className="block text-sm font-medium text-stone-700">Password</label> <label htmlFor="fld-password" className="block text-sm font-medium text-stone-700">Password</label>
<p className="mt-1 text-xs text-stone-500 mb-1.5">Minimum 6 characters.</p> <p className="mt-1 text-xs text-stone-500 mb-1.5">Minimum 6 characters.</p>
<input aria-label="Choose A Password" <input id="fld-password" aria-label="Choose A Password"
type="password" type="password"
value={editing.password ?? ""} value={editing.password ?? ""}
onChange={(e) => setEditing((p) => ({ ...p, password: e.target.value }))} onChange={(e) => setEditing((p) => ({ ...p, password: e.target.value }))}
@@ -576,9 +576,9 @@ export default function UsersPage({ initialUsers, brands, currentUser, onUserCre
{/* Display Name */} {/* Display Name */}
<div> <div>
<label className="block text-sm font-medium text-stone-700">Display Name</label> <label htmlFor="fld-display-name" className="block text-sm font-medium text-stone-700">Display Name</label>
<p className="mt-1 text-xs text-stone-500 mb-1.5">Shown in the admin user list.</p> <p className="mt-1 text-xs text-stone-500 mb-1.5">Shown in the admin user list.</p>
<input aria-label="Kyle Martinez" <input id="fld-display-name" aria-label="Kyle Martinez"
type="text" type="text"
value={editing.display_name ?? ""} value={editing.display_name ?? ""}
onChange={(e) => setEditing((p) => ({ ...p, display_name: e.target.value }))} onChange={(e) => setEditing((p) => ({ ...p, display_name: e.target.value }))}
@@ -589,9 +589,9 @@ export default function UsersPage({ initialUsers, brands, currentUser, onUserCre
{/* Phone Number */} {/* Phone Number */}
<div> <div>
<label className="block text-sm font-medium text-stone-700">Phone Number</label> <label htmlFor="fld-phone" className="block text-sm font-medium text-stone-700">Phone Number</label>
<p className="mt-1 text-xs text-stone-500 mb-1.5">Optional.</p> <p className="mt-1 text-xs text-stone-500 mb-1.5">Optional.</p>
<input aria-label="+1 (555) 000 0000" <input id="fld-phone" aria-label="+1 (555) 000 0000"
type="tel" type="tel"
value={editing.phone_number ?? ""} value={editing.phone_number ?? ""}
onChange={(e) => setEditing((p) => ({ ...p, phone_number: e.target.value }))} onChange={(e) => setEditing((p) => ({ ...p, phone_number: e.target.value }))}
@@ -602,7 +602,7 @@ export default function UsersPage({ initialUsers, brands, currentUser, onUserCre
{/* Role */} {/* Role */}
<div> <div>
<label className="block text-sm font-medium text-stone-700">Role</label> <p className="block text-sm font-medium text-stone-700">Role</p>
<div className="mt-2 space-y-2"> <div className="mt-2 space-y-2">
{availableRoles.map((r) => ( {availableRoles.map((r) => (
<label key={r} className="flex items-center gap-3 rounded-lg border border-stone-200 px-3 py-2.5 cursor-pointer hover:bg-stone-50"> <label key={r} className="flex items-center gap-3 rounded-lg border border-stone-200 px-3 py-2.5 cursor-pointer hover:bg-stone-50">
@@ -662,7 +662,7 @@ export default function UsersPage({ initialUsers, brands, currentUser, onUserCre
{/* Permissions */} {/* Permissions */}
<div> <div>
<label className="block text-sm font-medium text-stone-700 mb-3">Permissions</label> <p className="block text-sm font-medium text-stone-700 mb-3">Permissions</p>
<div className="space-y-2"> <div className="space-y-2">
{ALL_FLAGS.map((flag) => ( {ALL_FLAGS.map((flag) => (
<label key={flag} className="flex items-center justify-between rounded-lg border border-stone-200 px-4 py-2.5 hover:bg-stone-50 cursor-pointer"> <label key={flag} className="flex items-center justify-between rounded-lg border border-stone-200 px-4 py-2.5 hover:bg-stone-50 cursor-pointer">
@@ -80,11 +80,11 @@ export default function WaterLogEntryEditForm({ entry, backHref = "/admin/water-
<div className="grid grid-cols-2 gap-4"> <div className="grid grid-cols-2 gap-4">
<div> <div>
<label className="block text-sm font-medium text-[var(--admin-text-muted)] mb-1">Headgate</label> <label className="block text-sm font-medium text-[var(--admin-text-muted)] mb-1" htmlFor="fld-headgate">Headgate</label><label className="block text-sm font-medium text-[var(--admin-text-muted)] mb-1">Headgate</label>
<p className="rounded-lg border border-[var(--admin-border)] bg-[var(--admin-bg-subtle)] px-3 py-2 text-sm text-[var(--admin-text-secondary)]">{entry.headgate_name}</p> <p className="rounded-lg border border-[var(--admin-border)] bg-[var(--admin-bg-subtle)] px-3 py-2 text-sm text-[var(--admin-text-secondary)]">{entry.headgate_name}</p>
</div> </div>
<div> <div>
<label className="block text-sm font-medium text-[var(--admin-text-muted)] mb-1">User</label> <label className="block text-sm font-medium text-[var(--admin-text-muted)] mb-1" htmlFor="fld-user">User</label><label className="block text-sm font-medium text-[var(--admin-text-muted)] mb-1">User</label>
<p className="rounded-lg border border-[var(--admin-border)] bg-[var(--admin-bg-subtle)] px-3 py-2 text-sm text-[var(--admin-text-secondary)]">{entry.user_name}</p> <p className="rounded-lg border border-[var(--admin-border)] bg-[var(--admin-bg-subtle)] px-3 py-2 text-sm text-[var(--admin-text-secondary)]">{entry.user_name}</p>
</div> </div>
</div> </div>
@@ -106,11 +106,11 @@ export default function WaterLogEntryEditForm({ entry, backHref = "/admin/water-
/> />
</AdminInput> </AdminInput>
<div> <div>
<label className="block text-sm font-medium text-[var(--admin-text-muted)] mb-1">Submitted Via</label> <label className="block text-sm font-medium text-[var(--admin-text-muted)] mb-1" htmlFor="fld-submitted-via">Submitted Via</label><label className="block text-sm font-medium text-[var(--admin-text-muted)] mb-1">Submitted Via</label>
<p className="rounded-lg border border-[var(--admin-border)] bg-[var(--admin-bg-subtle)] px-3 py-2 text-sm text-[var(--admin-text-secondary)]">{entry.submitted_via}</p> <p className="rounded-lg border border-[var(--admin-border)] bg-[var(--admin-bg-subtle)] px-3 py-2 text-sm text-[var(--admin-text-secondary)]">{entry.submitted_via}</p>
</div> </div>
<div> <div>
<label className="block text-sm font-medium text-[var(--admin-text-muted)] mb-1">Logged</label> <label className="block text-sm font-medium text-[var(--admin-text-muted)] mb-1" htmlFor="fld-logged">Logged</label><label className="block text-sm font-medium text-[var(--admin-text-muted)] mb-1">Logged</label>
<p className="rounded-lg border border-[var(--admin-border)] bg-[var(--admin-bg-subtle)] px-3 py-2 text-sm text-[var(--admin-text-secondary)]"> <p className="rounded-lg border border-[var(--admin-border)] bg-[var(--admin-bg-subtle)] px-3 py-2 text-sm text-[var(--admin-text-secondary)]">
{new Date(entry.logged_at).toLocaleDateString("en-US", { {new Date(entry.logged_at).toLocaleDateString("en-US", {
month: "short", month: "short",
@@ -199,12 +199,13 @@ export default function StopsLocations({ stops }: Props) {
className="w-full rounded-lg border border-[var(--admin-border)] bg-white pl-9 pr-3 py-2 text-sm text-[var(--admin-text-primary)] placeholder:text-[var(--admin-text-muted)] focus:border-[var(--admin-accent)] focus:outline-none focus:ring-2 focus:ring-[var(--admin-accent)]/15" className="w-full rounded-lg border border-[var(--admin-border)] bg-white pl-9 pr-3 py-2 text-sm text-[var(--admin-text-primary)] placeholder:text-[var(--admin-text-muted)] focus:border-[var(--admin-accent)] focus:outline-none focus:ring-2 focus:ring-[var(--admin-accent)]/15"
/> />
</div> </div>
<label className="flex items-center gap-2 cursor-pointer select-none"> <div className="flex items-center gap-2 cursor-pointer select-none">
<button <button
type="button" type="button"
role="switch" role="switch"
aria-checked={showOnlyActive} aria-checked={showOnlyActive}
onClick={() => setShowOnlyActive((v) => !v)} onClick={() => setShowOnlyActive((v) => !v)}
aria-label="Show only active locations"
className={`relative h-5 w-9 rounded-full transition-colors ${showOnlyActive ? "bg-[var(--admin-accent)]" : "bg-stone-300"}`} className={`relative h-5 w-9 rounded-full transition-colors ${showOnlyActive ? "bg-[var(--admin-accent)]" : "bg-stone-300"}`}
> >
<span <span
@@ -214,7 +215,7 @@ export default function StopsLocations({ stops }: Props) {
<span className="font-mono text-[10px] uppercase tracking-wider text-[var(--admin-text-secondary)]"> <span className="font-mono text-[10px] uppercase tracking-wider text-[var(--admin-text-secondary)]">
Active only Active only
</span> </span>
</label> </div>
<span className="ml-auto font-mono text-[10px] uppercase tracking-wider text-[var(--admin-text-muted)]"> <span className="ml-auto font-mono text-[10px] uppercase tracking-wider text-[var(--admin-text-muted)]">
{filtered.length} of {groups.length} locations {filtered.length} of {groups.length} locations
</span> </span>
+2 -2
View File
@@ -184,9 +184,9 @@ export default function LotCreateForm({ brandId }: { brandId: string }) {
/> />
</div> </div>
<div> <div>
<label className="block text-xs font-medium text-stone-600 mb-1.5">Yield Est.</label> <label className="block text-xs font-medium text-stone-600 mb-1.5" htmlFor="fld-yield-est">Yield Est.</label>
<div className="flex rounded-xl border border-stone-200 bg-stone-50 overflow-hidden focus-within:border-emerald-600 focus-within:bg-white transition-colors"> <div className="flex rounded-xl border border-stone-200 bg-stone-50 overflow-hidden focus-within:border-emerald-600 focus-within:bg-white transition-colors">
<input aria-label=". 5000" <input id="fld-yield-est" aria-label=". 5000"
type="number" type="number"
value={yield_estimate_lbs} value={yield_estimate_lbs}
onChange={(e) => setYieldEstimateLbs(e.target.value)} onChange={(e) => setYieldEstimateLbs(e.target.value)}
@@ -277,9 +277,9 @@ export default function LotCreateModal({ isOpen, onClose, brandId }: Props) {
/> />
</div> </div>
<div> <div>
<label className="block text-xs font-medium mb-1.5" style={{ color: "var(--admin-text-primary)" }}>Yield Est.</label> <label className="block text-xs font-medium mb-1.5" style={{ color: "var(--admin-text-primary)" }} htmlFor="fld-yield-est">Yield Est.</label>
<div className="flex rounded-xl border overflow-hidden transition-colors" style={{ borderColor: "var(--admin-border)", backgroundColor: "var(--admin-bg-subtle)" }}> <div className="flex rounded-xl border overflow-hidden transition-colors" style={{ borderColor: "var(--admin-border)", backgroundColor: "var(--admin-bg-subtle)" }}>
<input aria-label=". 5000" <input id="fld-yield-est" aria-label=". 5000"
type="number" type="number"
value={yield_estimate_lbs} value={yield_estimate_lbs}
onChange={(e) => setYieldEstimateLbs(e.target.value)} onChange={(e) => setYieldEstimateLbs(e.target.value)}
@@ -79,10 +79,10 @@ export default function QuickNewLotModal({ brandId, onCreated, onClose }: Props)
)} )}
<div> <div>
<label className="block text-sm font-medium text-stone-700 mb-1.5"> <label className="block text-sm font-medium text-stone-700 mb-1.5" htmlFor="fld-crop-type-">
Crop Type <span className="text-red-500">*</span> Crop Type <span className="text-red-500">*</span>
</label> </label>
<input aria-label=". Sweet Corn" <input id="fld-crop-type-" aria-label=". Sweet Corn"
type="text" type="text"
value={crop_type} value={crop_type}
onChange={(e) => setCropType(e.target.value)} onChange={(e) => setCropType(e.target.value)}
+5 -5
View File
@@ -551,7 +551,7 @@ function WaterFieldInner() {
{/* Headgate selector */} {/* Headgate selector */}
<div> <div>
<label className="block text-base font-semibold text-stone-700 mb-2"> <label className="block text-base font-semibold text-stone-700 mb-2" htmlFor="fld-tselectheadgate">
{t.selectHeadgate} {t.selectHeadgate}
</label> </label>
{isHeadgateLocked ? ( {isHeadgateLocked ? (
@@ -575,7 +575,7 @@ function WaterFieldInner() {
<span className="text-stone-500 text-base font-medium">Loading headgates...</span> <span className="text-stone-500 text-base font-medium">Loading headgates...</span>
</div> </div>
) : ( ) : (
<select aria-label="Select" <select id="fld-tselectheadgate" aria-label="Select"
value={effectiveSelectedHeadgate} value={effectiveSelectedHeadgate}
onChange={(e) => setSelectedHeadgate(e.target.value)} onChange={(e) => setSelectedHeadgate(e.target.value)}
required required
@@ -629,7 +629,7 @@ function WaterFieldInner() {
{/* GPS Capture */} {/* GPS Capture */}
<div> <div>
<label className="block text-base font-semibold text-stone-700 mb-2"> <label className="block text-base font-semibold text-stone-700 mb-2" htmlFor="fld-gps-location">
GPS Location GPS Location
</label> </label>
<button <button
@@ -658,7 +658,7 @@ function WaterFieldInner() {
{/* Photo Upload */} {/* Photo Upload */}
<div> <div>
<label className="block text-base font-semibold text-stone-700 mb-2"> <label className="block text-base font-semibold text-stone-700 mb-2" htmlFor="fld-tphotolabel">
{t.photoLabel} {t.photoLabel}
</label> </label>
{photoPreview ? ( {photoPreview ? (
@@ -673,7 +673,7 @@ function WaterFieldInner() {
</button> </button>
</div> </div>
) : ( ) : (
<label className="flex items-center justify-center w-full rounded-xl border-2 border-dashed border-stone-300 cursor-pointer py-5 text-base font-semibold text-stone-500 hover:border-stone-500 hover:text-stone-700 transition-colors min-h-[80px]"> <label className="flex items-center justify-center w-full rounded-xl border-2 border-dashed border-stone-300 cursor-pointer py-5 text-base font-semibold text-stone-500 hover:border-stone-500 hover:text-stone-700 transition-colors min-h-[80px]" htmlFor="fld--taddphoto">
<span>📷 {t.addPhoto}</span> <span>📷 {t.addPhoto}</span>
<input <input
type="file" type="file"
+2 -2
View File
@@ -231,7 +231,7 @@ export default function OrdersTab({ orders, customers, brandId, onMsg, onRefresh
className="rounded-lg border border-[var(--admin-border)] px-3 py-1.5 text-sm outline-none focus:border-[var(--admin-accent)]" /> className="rounded-lg border border-[var(--admin-border)] px-3 py-1.5 text-sm outline-none focus:border-[var(--admin-accent)]" />
</div> </div>
<div className="flex-1 min-w-[180px]"> <div className="flex-1 min-w-[180px]">
<label className="block text-xs font-medium text-[var(--admin-text-muted)] mb-1">Search</label> <label className="block text-xs font-medium text-[var(--admin-text-muted)] mb-1" htmlFor="fld-search">Search</label>
<AdminSearchInput <AdminSearchInput
value={searchQuery} value={searchQuery}
onChange={e => setSearchQuery(e.target.value)} onChange={e => setSearchQuery(e.target.value)}
@@ -293,7 +293,7 @@ export default function OrdersTab({ orders, customers, brandId, onMsg, onRefresh
<thead className="bg-[var(--admin-bg-subtle)] text-[var(--admin-text-muted)]"> <thead className="bg-[var(--admin-bg-subtle)] text-[var(--admin-text-muted)]">
<tr> <tr>
<th className="px-5 py-3 font-semibold text-left w-10"> <th className="px-5 py-3 font-semibold text-left w-10">
<input type="checkbox" checked={selected.size === filtered.length && filtered.length > 0} onChange={toggleAll} className="rounded" /> <input id="fld-search" type="checkbox" checked={selected.size === filtered.length && filtered.length > 0} onChange={toggleAll} className="rounded" />
</th> </th>
<th className="px-5 py-3 font-semibold text-left">Invoice</th> <th className="px-5 py-3 font-semibold text-left">Invoice</th>
<th className="px-5 py-3 font-semibold text-left">Customer</th> <th className="px-5 py-3 font-semibold text-left">Customer</th>
@@ -217,7 +217,7 @@ export default function SettingsTab({ settings, brandId, onMsg, onRefresh, canMa
{/* Team Notification Recipients */} {/* Team Notification Recipients */}
<div className="col-span-2 rounded-2xl bg-[var(--admin-bg-subtle)] p-5 ring-1 ring-[var(--admin-border)]"> <div className="col-span-2 rounded-2xl bg-[var(--admin-bg-subtle)] p-5 ring-1 ring-[var(--admin-border)]">
<label className="block text-sm font-semibold text-[var(--admin-text-primary)] mb-1">Team Notification Recipients</label> <label className="block text-sm font-semibold text-[var(--admin-text-primary)] mb-1" htmlFor="fld-team-notification-recipients">Team Notification Recipients</label>
<p className="text-xs text-[var(--admin-text-muted)] mb-4 leading-relaxed"> <p className="text-xs text-[var(--admin-text-muted)] mb-4 leading-relaxed">
These team members receive all wholesale notifications for this brand new orders, These team members receive all wholesale notifications for this brand new orders,
deposits, fulfillments, price sheets, and pickup reminders. If no recipients are deposits, fulfillments, price sheets, and pickup reminders. If no recipients are
@@ -236,7 +236,7 @@ export default function SettingsTab({ settings, brandId, onMsg, onRefresh, canMa
<div className="space-y-2 mb-4"> <div className="space-y-2 mb-4">
{form.notificationRecipients.map((r: NotificationRecipient, idx: number) => ( {form.notificationRecipients.map((r: NotificationRecipient, idx: number) => (
<div key={r.email} className={`flex items-center gap-2 rounded-xl px-3 py-2.5 bg-white ring-1 ${r.active ? "ring-[var(--admin-border)]" : "ring-[var(--admin-border-light)] opacity-70"}`}> <div key={r.email} className={`flex items-center gap-2 rounded-xl px-3 py-2.5 bg-white ring-1 ${r.active ? "ring-[var(--admin-border)]" : "ring-[var(--admin-border-light)] opacity-70"}`}>
<input <input id="fld-team-notification-recipients"
type="checkbox" checked={r.active} onChange={() => toggleRecipient(idx)} type="checkbox" checked={r.active} onChange={() => toggleRecipient(idx)}
className="rounded border-[var(--admin-border)] mt-0.5" title={r.active ? "Active — receives notifications" : "Inactive"} /> className="rounded border-[var(--admin-border)] mt-0.5" title={r.active ? "Active — receives notifications" : "Inactive"} />
<div className="flex-1 min-w-0"> <div className="flex-1 min-w-0">
@@ -267,7 +267,7 @@ export default function SettingsTab({ settings, brandId, onMsg, onRefresh, canMa
</div> </div>
{/* Webhook Settings */} {/* Webhook Settings */}
<div className="col-span-2 rounded-2xl bg-[var(--admin-bg-subtle)] p-5 ring-1 ring-[var(--admin-border)]"> <div className="col-span-2 rounded-2xl bg-[var(--admin-bg-subtle)] p-5 ring-1 ring-[var(--admin-border)]">
<label className="block text-sm font-semibold text-[var(--admin-text-primary)] mb-1">Webhook Integration</label> <label className="block text-sm font-semibold text-[var(--admin-text-primary)] mb-1" htmlFor="fld-webhook-integration">Webhook Integration</label><label className="block text-sm font-semibold text-[var(--admin-text-primary)] mb-1">Webhook Integration</label>
<p className="text-xs text-[var(--admin-text-muted)] mb-4 leading-relaxed"> <p className="text-xs text-[var(--admin-text-muted)] mb-4 leading-relaxed">
Send order events to external systems (Harvest Point, ERPs, etc.). Payload is signed Send order events to external systems (Harvest Point, ERPs, etc.). Payload is signed
with HMAC-SHA256. Enable and configure the URL and secret below. with HMAC-SHA256. Enable and configure the URL and secret below.