fix(buyer/billing/comms/a11y): Codex review pass round 2

Tuxedo buyer path (subagent 2):
- src/app/tuxedo/page.tsx: remove duplicate CinematicShowcase render
- src/components/storefront/CinematicShowcase.tsx: wire up useCart, Add to Cart button, brand-aware fulfillment
- src/app/tuxedo/stops/TuxedoStopsList.tsx: improved empty state with calendar icon + CTAs
- src/app/cart/CartClient.tsx: guard against empty cart checkout; 'Cart is Empty' state

Billing reconciliation (subagent 3):
- src/actions/billing/billing-overview.ts: NEW — single source of truth
- src/app/admin/settings/billing/page.tsx: use getBillingOverview
- src/app/admin/settings/billing/BillingClientPage.tsx: rewritten to consume BillingOverview (status pill, addons state, removable flags, derived invoice amounts, usage footer)
- src/app/admin/page.tsx: use getBillingOverview (aligns dashboard with billing)
- src/components/admin/DashboardClient.tsx: 'Active Products' now reads from getBillingOverview
- supabase/migrations/203_plan_usage_active_products.sql: get_brand_plan_info counts products as active=true AND deleted_at IS NULL

Harvest Reach dedup + audience preview (manual, subagent 4 didn't complete):
- src/components/admin/CommunicationsPage.tsx: add initialTab prop
- src/app/admin/communications/compose/page.tsx: now renders with initialTab='compose' (single compose experience, no duplicate edit panel)
- src/components/admin/HarvestReach/CampaignComposerPage.tsx: always-visible audience preview panel (count + sample emails), loads via previewCampaignAudience action

Layout/content consistency + a11y sweep (subagent 5):
- src/components/layout/SiteHeader.tsx: Admin link only shows for authenticated admin users
- src/components/Providers.tsx: suppress public SiteHeader/Footer for /admin, /cart, /checkout, /wholesale, /water (fixes duplicate headers)
- src/app/contact/ContactClientPage.tsx: Phone/Email now use tel:/mailto: links; dynamic year
- src/app/blog/page.tsx, changelog, privacy-policy, roadmap, security, terms-and-conditions, waitlist: dynamic year
- src/app/admin/wholesale/WholesaleClient.tsx: proper htmlFor/id, type=email/tel, autoComplete
- src/app/admin/settings/ai/AIClient.tsx: proper htmlFor/id, required + aria-required
- src/app/admin/settings/integrations/IntegrationsClient.tsx: only mask secret fields; add required + aria-required + CredentialField.required type
- src/app/admin/water-log/headgates/HeadgatesManager.tsx: htmlFor/id, aria-required
- src/components/admin/CreateUserModal.tsx: htmlFor/id, required, aria-required, aria-describedby, autoComplete
- src/app/admin/me/AdminMeClient.tsx, products/import, sales/import, water-log/settings, login, brands, tuxedo: a11y polish (ids/required/aria)
This commit is contained in:
2026-06-03 16:39:19 +00:00
parent 03ae372509
commit 0245aa29cc
34 changed files with 1122 additions and 295 deletions
@@ -222,8 +222,9 @@ export default function HeadgatesManager({ initialHeadgates, brandId }: Props) {
<div className="rounded-xl border border-[var(--admin-border)] bg-white p-5">
<form onSubmit={handleAdd} className="flex gap-3 items-end">
<div className="flex-1">
<label className="block text-xs font-semibold text-[var(--admin-text-muted)] mb-1">Headgate Name</label>
<label htmlFor="headgate-add-name" className="block text-xs font-semibold text-[var(--admin-text-muted)] mb-1">Headgate Name</label>
<input
id="headgate-add-name"
autoFocus
type="text"
value={newName}
@@ -231,11 +232,13 @@ export default function HeadgatesManager({ initialHeadgates, brandId }: Props) {
placeholder="e.g. North Field Gate 1"
className="w-full rounded-xl border border-[var(--admin-border)] px-4 py-3 text-sm outline-none focus:border-[var(--admin-accent)]"
required
aria-required="true"
/>
</div>
<div>
<label className="block text-xs font-semibold text-[var(--admin-text-muted)] mb-1">Unit</label>
<label htmlFor="headgate-add-unit" className="block text-xs font-semibold text-[var(--admin-text-muted)] mb-1">Unit</label>
<select
id="headgate-add-unit"
value={newUnit}
onChange={(e) => setNewUnit(e.target.value)}
className="rounded-xl border border-[var(--admin-border)] px-3 py-3 text-sm outline-none focus:border-[var(--admin-accent)]"
@@ -371,19 +374,22 @@ export default function HeadgatesManager({ initialHeadgates, brandId }: Props) {
</div>
<form onSubmit={handleEditSave} className="p-5 space-y-4">
<div>
<label className="block text-xs font-semibold text-[var(--admin-text-muted)] mb-1">Name</label>
<label htmlFor="headgate-edit-name" className="block text-xs font-semibold text-[var(--admin-text-muted)] mb-1">Name</label>
<input
id="headgate-edit-name"
type="text"
value={editName}
onChange={(e) => setEditName(e.target.value)}
className="w-full rounded-xl border border-[var(--admin-border)] px-4 py-3 text-sm outline-none focus:border-[var(--admin-accent)]"
required
aria-required="true"
/>
</div>
<div className="grid grid-cols-2 gap-3">
<div>
<label className="block text-xs font-semibold text-[var(--admin-text-muted)] mb-1">Unit</label>
<label htmlFor="headgate-edit-unit" className="block text-xs font-semibold text-[var(--admin-text-muted)] mb-1">Unit</label>
<select
id="headgate-edit-unit"
value={editUnit}
onChange={(e) => setEditUnit(e.target.value)}
className="w-full rounded-xl border border-[var(--admin-border)] px-3 py-3 text-sm outline-none focus:border-[var(--admin-accent)]"
@@ -395,15 +401,16 @@ export default function HeadgatesManager({ initialHeadgates, brandId }: Props) {
</div>
<div className="flex items-center">
<label className="flex items-center gap-2 text-sm text-[var(--admin-text-secondary)] cursor-pointer">
<input type="checkbox" checked={editActive} onChange={(e) => setEditActive(e.target.checked)} className="rounded" />
<input id="headgate-edit-active" type="checkbox" checked={editActive} onChange={(e) => setEditActive(e.target.checked)} className="rounded" />
Active
</label>
</div>
</div>
<div className="grid grid-cols-2 gap-3">
<div>
<label className="block text-xs font-semibold text-[var(--admin-text-muted)] mb-1">High Alert Threshold</label>
<label htmlFor="headgate-edit-high" className="block text-xs font-semibold text-[var(--admin-text-muted)] mb-1">High Alert Threshold</label>
<input
id="headgate-edit-high"
type="number"
step="any"
value={editHigh}
@@ -413,8 +420,9 @@ export default function HeadgatesManager({ initialHeadgates, brandId }: Props) {
/>
</div>
<div>
<label className="block text-xs font-semibold text-[var(--admin-text-muted)] mb-1">Low Alert Threshold</label>
<label htmlFor="headgate-edit-low" className="block text-xs font-semibold text-[var(--admin-text-muted)] mb-1">Low Alert Threshold</label>
<input
id="headgate-edit-low"
type="number"
step="any"
value={editLow}
+7 -3
View File
@@ -133,8 +133,9 @@ export default function WaterLogSettingsPage() {
<p className="text-xs text-zinc-500">Leave blank to keep existing PIN. Set a new PIN to replace it.</p>
<div className="grid grid-cols-2 gap-3">
<div>
<label className="block text-xs font-medium text-zinc-400 mb-1">New PIN</label>
<label htmlFor="water-new-pin" className="block text-xs font-medium text-zinc-400 mb-1">New PIN</label>
<input
id="water-new-pin"
type="password"
inputMode="numeric"
pattern="[0-9]*"
@@ -147,8 +148,9 @@ export default function WaterLogSettingsPage() {
/>
</div>
<div>
<label className="block text-xs font-medium text-zinc-400 mb-1">Confirm PIN</label>
<label htmlFor="water-confirm-pin" className="block text-xs font-medium text-zinc-400 mb-1">Confirm PIN</label>
<input
id="water-confirm-pin"
type="password"
inputMode="numeric"
pattern="[0-9]*"
@@ -225,13 +227,15 @@ export default function WaterLogSettingsPage() {
{alertsEnabled && (
<div>
<label className="block text-xs font-medium text-zinc-400 mb-1">Alert Phone Number</label>
<label htmlFor="water-alert-phone" className="block text-xs font-medium text-zinc-400 mb-1">Alert Phone Number</label>
<input
id="water-alert-phone"
type="tel"
value={alertPhone}
onChange={(e) => setAlertPhone(e.target.value)}
placeholder="+1234567890"
className="w-full rounded-xl border border-zinc-600 px-4 py-3 text-base outline-none focus:border-stone-900"
autoComplete="tel"
/>
<p className="text-xs text-zinc-500 mt-1">U.S. format recommended. Must include country code (e.g. +1 for USA).</p>
</div>