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:
+33
-19
@@ -2,8 +2,7 @@
|
||||
|
||||
import { useState, useEffect, useRef } from "react";
|
||||
import Link from "next/link";
|
||||
import { motion, useInView, AnimatePresence } from "framer-motion";
|
||||
import { useCart } from "@/context/CartContext";
|
||||
import { motion, useInView } from "framer-motion";
|
||||
import TuxedoVideoHero from "@/components/storefront/TuxedoVideoHero";
|
||||
import CinematicShowcase from "@/components/storefront/CinematicShowcase";
|
||||
import LayoutContainer from "@/components/layout/LayoutContainer";
|
||||
@@ -618,8 +617,6 @@ export default function TuxedoPage() {
|
||||
return () => ctx.revert();
|
||||
}, []);
|
||||
|
||||
const featuredProducts = products.slice(0, 3);
|
||||
|
||||
return (
|
||||
<div ref={pageScopeRef} className="min-h-screen bg-stone-50">
|
||||
<BrandStylesProvider
|
||||
@@ -665,19 +662,6 @@ export default function TuxedoPage() {
|
||||
onSecondaryClick={scrollToStory}
|
||||
/>
|
||||
|
||||
{/* ─── CINEMATIC PRODUCT SHOWCASE ────────────────────────────────── */}
|
||||
<CinematicShowcase
|
||||
products={featuredProducts.map((p) => ({
|
||||
id: p.id,
|
||||
name: p.name,
|
||||
description: p.description ?? "",
|
||||
price: `$${p.price}`,
|
||||
type: p.type,
|
||||
imageUrl: p.image_url,
|
||||
}))}
|
||||
brandSlug="tuxedo"
|
||||
/>
|
||||
|
||||
<WhyTuxedoCorn />
|
||||
|
||||
<section id="stops" className="relative bg-gradient-to-b from-stone-100 to-stone-50 scroll-mt-20">
|
||||
@@ -724,8 +708,33 @@ export default function TuxedoPage() {
|
||||
</div>
|
||||
<FadeOnScroll from="up" delay={0.3}>
|
||||
{stops.length === 0 ? (
|
||||
<div className="rounded-3xl bg-white p-14 text-center ring-1 ring-stone-200/60">
|
||||
<p className="text-stone-500">No upcoming stops scheduled. Check back soon!</p>
|
||||
<div className="rounded-3xl bg-white p-12 sm:p-16 text-center ring-1 ring-stone-200/60">
|
||||
<div className="mx-auto mb-5 flex h-14 w-14 items-center justify-center rounded-2xl bg-stone-100">
|
||||
<svg className="h-7 w-7 text-stone-400" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={1.5} aria-hidden="true">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z" />
|
||||
</svg>
|
||||
</div>
|
||||
<p className="text-lg font-semibold text-stone-800">No stops on the calendar just yet</p>
|
||||
<p className="mx-auto mt-2 max-w-md text-stone-500">
|
||||
The harvest is right around the corner — new pickup stops go live weekly. You can still preorder below and pick a stop once they're announced.
|
||||
</p>
|
||||
<div className="mt-6 flex flex-col sm:flex-row items-center justify-center gap-3">
|
||||
<Link
|
||||
href="/tuxedo/stops"
|
||||
className="inline-flex items-center gap-2 rounded-2xl bg-stone-950 px-5 py-2.5 text-sm font-semibold text-white hover:bg-stone-800 active:bg-stone-900 transition-colors"
|
||||
>
|
||||
View All Stops
|
||||
</Link>
|
||||
{showSchedulePdf && (
|
||||
<a
|
||||
href="/api/tuxedo/schedule-pdf"
|
||||
download
|
||||
className="inline-flex items-center gap-2 rounded-2xl bg-white px-5 py-2.5 text-sm font-semibold text-stone-700 ring-1 ring-stone-200 hover:bg-stone-50 transition-colors"
|
||||
>
|
||||
Download Schedule
|
||||
</a>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<PaginatedStops stops={stops} brandSlug="tuxedo" brandAccent="green" />
|
||||
@@ -774,8 +783,13 @@ export default function TuxedoPage() {
|
||||
price: `$${p.price}`,
|
||||
type: p.type,
|
||||
imageUrl: p.image_url,
|
||||
brand_id: p.brand_id,
|
||||
brand_slug: "tuxedo",
|
||||
is_taxable: p.is_taxable,
|
||||
pickup_type: p.pickup_type,
|
||||
}))}
|
||||
brandSlug="tuxedo"
|
||||
brandName={brand?.name ?? "Tuxedo Corn"}
|
||||
/>
|
||||
|
||||
{/* Mobile-friendly fallback grid for smaller screens */}
|
||||
|
||||
@@ -55,14 +55,31 @@ export default function TuxedoStopsList({ stops, brandName, brandSlug }: Props)
|
||||
</div>
|
||||
|
||||
{upcomingStops.length === 0 ? (
|
||||
<div className="max-w-4xl mx-auto text-center py-16">
|
||||
<div className="max-w-2xl mx-auto text-center py-16">
|
||||
<div className="w-20 h-20 rounded-2xl bg-stone-100 flex items-center justify-center mx-auto mb-6">
|
||||
<svg className="w-10 h-10 text-stone-400" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={1.5}>
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z" />
|
||||
</svg>
|
||||
</div>
|
||||
<h2 className="text-2xl font-bold text-stone-800 mb-2">No Upcoming Stops</h2>
|
||||
<p className="text-stone-500">Check back soon for new pickup locations!</p>
|
||||
<h2 className="text-2xl font-bold text-stone-800 mb-2">No Upcoming Stops Yet</h2>
|
||||
<p className="text-stone-500 mb-8 max-w-md mx-auto">
|
||||
The corn is still ripening in the field. New pickup stops are added every week — check back soon, or browse the full season schedule.
|
||||
</p>
|
||||
<div className="flex flex-col sm:flex-row items-center justify-center gap-3">
|
||||
<Link
|
||||
href="/tuxedo"
|
||||
className="inline-flex items-center gap-2 rounded-2xl bg-stone-950 px-6 py-3 text-sm font-semibold text-white hover:bg-stone-800 active:bg-stone-900 transition-colors"
|
||||
>
|
||||
Back to Tuxedo Corn
|
||||
</Link>
|
||||
<a
|
||||
href="/api/tuxedo/schedule-pdf"
|
||||
download
|
||||
className="inline-flex items-center gap-2 rounded-2xl bg-white px-6 py-3 text-sm font-semibold text-stone-700 ring-1 ring-stone-200 hover:bg-stone-50 transition-colors"
|
||||
>
|
||||
Download Full Schedule
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="max-w-4xl mx-auto space-y-3">
|
||||
|
||||
Reference in New Issue
Block a user