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
+22 -30
View File
@@ -1,6 +1,6 @@
import { supabase } from "@/lib/supabase";
import { getAdminUser } from "@/lib/admin-permissions";
import { getBrandPlanInfo, getEnabledAddons } from "@/actions/billing/stripe-portal";
import { getBillingOverview } from "@/actions/billing/billing-overview";
import AdminAccessDenied from "@/components/admin/AdminAccessDenied";
import BillingClientPage from "./BillingClientPage";
@@ -49,24 +49,25 @@ export default async function BillingPage({ params }: Props) {
if (!resolvedBrandId) return <AdminAccessDenied />;
const [planResult, addons] = await Promise.all([
getBrandPlanInfo(resolvedBrandId),
getEnabledAddons(resolvedBrandId),
]);
// Single source of truth for everything the billing client needs.
const overviewRes = await getBillingOverview(resolvedBrandId);
if (!overviewRes.success || !overviewRes.data) {
return (
<main className="min-h-screen bg-[var(--admin-bg)] px-6 py-12">
<div className="mx-auto max-w-6xl">
<div className="rounded-2xl bg-white shadow-md ring-1 ring-[var(--admin-border)] p-8 text-center">
<h1 className="text-2xl font-bold text-[var(--admin-text-primary)]">Billing Unavailable</h1>
<p className="mt-2 text-[var(--admin-text-muted)]">{overviewRes.error ?? "Could not load billing information."}</p>
<a href="/admin" className="mt-4 inline-block rounded-xl bg-[var(--admin-accent)] hover:bg-[var(--admin-accent-hover)] px-6 py-3 text-sm font-medium text-white transition-colors">
Back to Admin
</a>
</div>
</div>
</main>
);
}
const planData = (planResult.success && planResult.data && typeof planResult.data === "object")
? planResult.data as Record<string, any>
: {} as Record<string, any>;
const planTier = planData.plan_tier ?? "starter";
const { data: brand } = await supabase
.from("brands")
.select("name, stripe_customer_id, stripe_subscription_id, stripe_subscription_status, stripe_current_period_end")
.eq("id", resolvedBrandId)
.single();
const hasStripeCustomer = !!brand?.stripe_customer_id;
const overview = overviewRes.data;
return (
<main className="min-h-screen bg-[var(--admin-bg)]">
@@ -94,21 +95,12 @@ export default async function BillingPage({ params }: Props) {
<div className="mb-8">
<h1 className="text-3xl font-bold text-[var(--admin-text-primary)]">Billing &amp; Subscription</h1>
<p className="mt-1 text-[var(--admin-text-muted)]">
Manage your Route Commerce subscription for {brand?.name ?? "your brand"}.
Manage your Route Commerce subscription for {overview.brandName ?? "your brand"}.
</p>
</div>
<BillingClientPage
brandId={resolvedBrandId}
planTier={planTier}
brandName={brand?.name ?? null}
hasStripeCustomer={hasStripeCustomer}
enabledAddons={addons}
isPlatformAdmin={isPlatformAdmin}
subscriptionStatus={brand?.stripe_subscription_status ?? null}
currentPeriodEnd={brand?.stripe_current_period_end ?? null}
/>
<BillingClientPage overview={overview} />
</div>
</main>
);
}
}