fix: full Apple HIG mobile + SEO audit - all issues resolved

MOBILE RESPONSIVENESS (Apple HIG):
- HeroSection: typography scaled 7xl→4xl sm:5xl md:6xl lg:7xl, responsive px/py, rounded-2xl, active:scale-95
- Cart quantity buttons: h-8→h-11 w-8→w-11 (44px touch target), rounded-xl, active:scale-95
- StorefrontFooter newsletter: responsive w-full sm:w-52 lg:w-64, aria-label, larger touch targets
- StorefrontHeader mobile nav: padding px-6→px-4 py-6→py-5
- AdminTable: overflow-x-auto + min-w-[600px] for horizontal scroll
- AdminOrdersPanel: same table overflow fix
- AdminLayout: mobile px-4 sm:px-6 py-6 sm:py-10
- AdminFilterTabs: responsive text/text sizes
- AdminSidebar hamburger: h-10→h-11 w-10→w-11 (44px touch target)
- DashboardClient: grid gap-3 sm:gap-4, responsive stat text
- OrderEditForm: grid-cols-1 sm:grid-cols-2 (was 2, breaks on mobile)
- BillingClient: min-h-[44px] on select/button
- ProductsClient: h-32 sm:h-40 responsive image height
- StopCard: line-clamp-2 instead of line-clamp-1 on location
- CommunicationsPage: tabs wrapped in overflow-x-auto
- Checkout page: grid breakpoint md not lg
- Tuxedo page: SectionHeader mobile-first, feature grid grid-cols-1 sm:2, label visible
- Tuxedo stats: text-3xl sm:text-4xl

SEO METADATA:
- Root layout: viewport export, full OG/Twitter, metadataBase, keywords, robots
- Tuxedo layout: complete OG + Twitter + canonical + keywords
- Indian River layout: complete OG + Twitter + canonical + keywords
- Tuxedo/IRD FAQ pages: new layout.tsx with full metadata + FAQPage JSON-LD schema
- Tuxedo/IRD Contact pages: new layout.tsx with full metadata
- Pricing page: expanded metadata with OG/Twitter
- Contact page: refactored to layout+ClientPage structure
- Sitemap: updated with dynamic stop URLs, async function

SCHEMA + STRUCTURED DATA:
- FAQPage JSON-LD on FAQ pages
- BreadcrumbList JSON-LD on storefront layouts
- BreadcrumbNav component created (Apple HIG compliant)

BUG FIXES:
- Indian River: replaced raw <img> with Next.js Image component
- Indian River: verified single H1 (others are h2)
- Stop card: location line-clamp-2 for better readability

TYPE CHECK: all pass
This commit is contained in:
2026-06-02 04:32:58 +00:00
parent c73da417af
commit 778b3fe311
32 changed files with 861 additions and 284 deletions
+44
View File
@@ -0,0 +1,44 @@
import type { Metadata } from "next";
import ContactClientPage from "./ContactClientPage";
const BASE_URL = process.env.NEXT_PUBLIC_SITE_URL ?? "https://routecommerce.com";
export const metadata: Metadata = {
title: "Contact Us",
description: "Get in touch with Tuxedo Corn. Questions about corn orders, pickup stops, or wholesale accounts — we're happy to help.",
keywords: ["Tuxedo Corn contact", "Olathe Sweet contact", "corn wholesale inquiry", "order questions"],
openGraph: {
title: "Contact Us — Tuxedo Corn",
description: "Get in touch with Tuxedo Corn. Questions about corn orders, pickup stops, or wholesale accounts.",
url: `${BASE_URL}/tuxedo/contact`,
siteName: "Tuxedo Corn",
locale: "en_US",
type: "website",
images: [
{
url: "/og-tuxedo.jpg",
width: 1200,
height: 630,
alt: "Contact Tuxedo Corn",
},
],
},
twitter: {
card: "summary_large_image",
title: "Contact Us — Tuxedo Corn",
description: "Get in touch with Tuxedo Corn. Questions about corn orders, pickup stops, or wholesale accounts.",
site: "@TuxedoCorn",
images: ["/og-tuxedo.jpg"],
},
alternates: {
canonical: `${BASE_URL}/tuxedo/contact`,
},
robots: {
index: true,
follow: true,
},
};
export default function TuxedoContactLayout({ children }: { children: React.ReactNode }) {
return <ContactClientPage />;
}
+99
View File
@@ -0,0 +1,99 @@
import type { Metadata } from "next";
import FAQClientPage from "./FAQClientPage";
const BASE_URL = process.env.NEXT_PUBLIC_SITE_URL ?? "https://routecommerce.com";
export const metadata: Metadata = {
title: "FAQ — Frequently Asked Questions",
description: "Find answers to common questions about ordering corn, pickup stops, shipping, and wholesale accounts for Tuxedo Corn.",
keywords: ["Tuxedo Corn FAQ", "Olathe Sweet corn questions", "corn pickup FAQ", "wholesale corn accounts", "corn shipping"],
openGraph: {
title: "FAQ — Tuxedo Corn Frequently Asked Questions",
description: "Find answers to common questions about ordering corn, pickup stops, shipping, and wholesale accounts.",
url: `${BASE_URL}/tuxedo/faq`,
siteName: "Tuxedo Corn",
locale: "en_US",
type: "website",
images: [
{
url: "/og-tuxedo.jpg",
width: 1200,
height: 630,
alt: "Tuxedo Corn FAQ",
},
],
},
twitter: {
card: "summary_large_image",
title: "FAQ — Tuxedo Corn",
description: "Find answers to common questions about ordering corn, pickup stops, shipping, and wholesale accounts.",
site: "@TuxedoCorn",
images: ["/og-tuxedo.jpg"],
},
alternates: {
canonical: `${BASE_URL}/tuxedo/faq`,
},
robots: {
index: true,
follow: true,
},
};
const faqJsonLd = {
"@context": "https://schema.org",
"@type": "FAQPage",
"mainEntity": [
{
"@type": "Question",
"name": "How do I place a preorder for corn?",
"acceptedAnswer": {
"@type": "Answer",
"text": "Find a stop near you on our homepage and click 'Shop This Stop' to add corn to your cart and select your pickup location. Preordering helps us bring the right amount of corn to each stop."
}
},
{
"@type": "Question",
"name": "Can I order without selecting a pickup stop?",
"acceptedAnswer": {
"@type": "Answer",
"text": "Yes. Choose 'Shipping' at checkout and we will mail cooler boxes directly to your home. Shipping is available for orders of 4 or more dozen and is fulfilled after the season ends."
}
},
{
"@type": "Question",
"name": "Do you ship corn?",
"acceptedAnswer": {
"@type": "Answer",
"text": "Yes. We ship Olathe Sweet Sweet Corn nationwide. Orders ship as cooler boxes after our field season ends in late summer. A minimum of 4 dozen is required for shipping."
}
},
{
"@type": "Question",
"name": "How do I set up a wholesale account?",
"acceptedAnswer": {
"@type": "Answer",
"text": "Visit /wholesale/register to apply. We review applications and respond within 1-2 business days. Wholesale accounts are available to retailers, restaurants, and farm stands."
}
},
{
"@type": "Question",
"name": "What makes Olathe Sweet different?",
"acceptedAnswer": {
"@type": "Answer",
"text": "Olathe Sweet Sweet Corn is grown in the Uncompahgre Valley of Colorado, where high-altitude days and cool nights produce exceptionally sweet, tender corn. We have been growing this variety for over 40 years."
}
}
]
};
export default function TuxedoFAQLayout({ children }: { children: React.ReactNode }) {
return (
<>
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(faqJsonLd) }}
/>
<FAQClientPage />
</>
);
}
+23
View File
@@ -2,6 +2,26 @@ import type { Metadata } from "next";
const BASE_URL = process.env.NEXT_PUBLIC_SITE_URL ?? "https://routecommerce.com";
// BreadcrumbList schema for SEO
const tuxedoBreadcrumbSchema = {
"@context": "https://schema.org",
"@type": "BreadcrumbList",
"itemListElement": [
{
"@type": "ListItem",
"position": 1,
"name": "Home",
"item": BASE_URL,
},
{
"@type": "ListItem",
"position": 2,
"name": "Tuxedo Corn",
"item": `${BASE_URL}/tuxedo`,
},
],
};
export const metadata: Metadata = {
title: {
default: "Tuxedo Corn | Fresh Produce Wholesale",
@@ -39,6 +59,9 @@ export const metadata: Metadata = {
index: true,
follow: true,
},
other: {
"application/ld+json": JSON.stringify(tuxedoBreadcrumbSchema),
},
};
export default function TuxedoLayout({ children }: { children: React.ReactNode }) {
+2 -2
View File
@@ -548,7 +548,7 @@ export default function TuxedoPage() {
<div className="mb-14 flex items-end justify-between">
<div>
<p className="text-[11px] font-semibold uppercase tracking-widest text-emerald-600 mb-4">Delivery Stops</p>
<h2 className="text-5xl md:text-6xl font-black tracking-tight text-stone-950 leading-[1.05]">
<h2 className="text-3xl sm:text-4xl md:text-5xl lg:text-6xl font-black tracking-tight text-stone-950 leading-[1.05]">
Upcoming<br className="hidden md:block" /> Stops
</h2>
<div className="mt-6 h-px w-12 bg-emerald-600" />
@@ -656,7 +656,7 @@ export default function TuxedoPage() {
{ stat: "100%", label: "Hand-Picked" },
].map(({ stat, label }) => (
<div key={label} className="text-center">
<p className="text-4xl font-black text-white">{stat}</p>
<p className="text-3xl sm:text-4xl font-black text-white">{stat}</p>
<p className="mt-1 text-xs text-stone-500 uppercase tracking-widest">{label}</p>
</div>
))}