diff --git a/next.config.ts b/next.config.ts index d500407..bfec4f5 100644 --- a/next.config.ts +++ b/next.config.ts @@ -12,8 +12,9 @@ const nextConfig: NextConfig = { // /home/tyler/package-lock.json as the root directory." // The deploy runner's APP_DIR is /home/tyler/route-commerce, so // resolving relative to the project root is correct both locally and - // in CI. - outputFileTracingRoot: ".", + // in CI. We resolve to an absolute path to avoid the warning in + // Next.js 16 which prefers absolute paths here. + outputFileTracingRoot: __dirname, // Enable strict mode reactStrictMode: true, diff --git a/src/app/admin/stops/page.tsx b/src/app/admin/stops/page.tsx index ec52c20..23bd03f 100644 --- a/src/app/admin/stops/page.tsx +++ b/src/app/admin/stops/page.tsx @@ -23,7 +23,21 @@ export default async function AdminStopsPage({ searchParams }: PageProps) { if (!adminUser) return ; if (!adminUser.can_manage_stops) redirect("/admin/pickup"); - let stops: unknown[] = []; + interface DbStopRow { + id: string; + city: string; + state: string; + date: Date | string; + time: string | null; + location: string; + status: string; + brand_id: string; + address: string | null; + zip: string | null; + cutoff_date: string | null; + brand_name?: string; + } + let stops: DbStopRow[] = []; let error: string | null = null; // Resolve active brand from cookie/URL (respects platform_admin "All brands" = null) @@ -83,7 +97,7 @@ export default async function AdminStopsPage({ searchParams }: PageProps) { } // Transform stops for the client component - const stopsForClient = stops.map((s: any) => ({ + const stopsForClient = stops.map((s) => ({ id: s.id, city: s.city, state: s.state, diff --git a/src/app/api/health/db-schema/route.ts b/src/app/api/health/db-schema/route.ts index 22de93b..6dbd4d3 100644 --- a/src/app/api/health/db-schema/route.ts +++ b/src/app/api/health/db-schema/route.ts @@ -19,11 +19,12 @@ export async function GET() { { status: "ok", message: "admin_users table present" }, { status: 200 } ); - } catch (err: any) { + } catch (err: unknown) { + const message = err instanceof Error ? err.message : "Database schema check failed"; return NextResponse.json( { status: "error", - message: err?.message ?? "Database schema check failed", + message, hint: "Run migrations against the DATABASE_URL (see docs/superpowers/plans/2026-06-prod-db-schema-migration-reliability.md)", }, { status: 503 } diff --git a/src/app/error.tsx b/src/app/error.tsx index a297980..8349ec6 100644 --- a/src/app/error.tsx +++ b/src/app/error.tsx @@ -1,7 +1,13 @@ "use client"; import Link from "next/link"; +import { useEffect } from "react"; +/** + * Root error boundary — the safety net for any uncaught error in the + * app. Renders inside the root layout, so it inherits the same font + * variables and design tokens as everything else. + */ export default function ErrorPage({ error, reset, @@ -9,44 +15,81 @@ export default function ErrorPage({ error: Error & { digest?: string }; reset: () => void; }) { + useEffect(() => { + // Surface to the console for development; the Sentry-ready logger + // in src/lib/sentry.ts can be wired in here when keys are configured. + // eslint-disable-next-line no-console + console.error("[app/error]", error); + }, [error]); + return ( -
- {/* Background */} -
-
-
+
+
); } diff --git a/src/app/indian-river-direct/layout.tsx b/src/app/indian-river-direct/layout.tsx index 6c669ef..f8eb9ff 100644 --- a/src/app/indian-river-direct/layout.tsx +++ b/src/app/indian-river-direct/layout.tsx @@ -2,22 +2,33 @@ import type { Metadata, Viewport } from "next"; const BASE_URL = process.env.NEXT_PUBLIC_SITE_URL ?? "https://routecommerce.com"; -// BreadcrumbList schema for SEO -const indianRiverBreadcrumbSchema = { +// Structured data for the Indian River Direct storefront. +// LocalBusiness + BreadcrumbList for the family farm brand. +const indianRiverStructuredData = { "@context": "https://schema.org", - "@type": "BreadcrumbList", - "itemListElement": [ + "@graph": [ { - "@type": "ListItem", - "position": 1, - "name": "Home", - "item": BASE_URL, + "@type": "BreadcrumbList", + itemListElement: [ + { "@type": "ListItem", position: 1, name: "Home", item: BASE_URL }, + { "@type": "ListItem", position: 2, name: "Indian River Direct", item: `${BASE_URL}/indian-river-direct` }, + ], }, { - "@type": "ListItem", - "position": 2, - "name": "Indian River Direct", - "item": `${BASE_URL}/indian-river-direct`, + "@type": "LocalBusiness", + "@id": `${BASE_URL}/indian-river-direct/#localbusiness`, + name: "Indian River Direct", + description: + "Fresh peaches and citrus from our Florida groves to truckload sales in your neighborhood. Family-owned since 1985.", + url: `${BASE_URL}/indian-river-direct`, + image: `${BASE_URL}/og-default.svg`, + priceRange: "$$", + address: { + "@type": "PostalAddress", + addressRegion: "FL", + addressCountry: "US", + }, + foundingDate: "1985", }, ], }; @@ -41,7 +52,7 @@ export const metadata: Metadata = { type: "website", images: [ { - url: "/og-indian-river.jpg", + url: "/og-default.svg", width: 1200, height: 630, alt: "Indian River Direct - Fresh Peaches & Citrus", @@ -54,7 +65,7 @@ export const metadata: Metadata = { description: "Fresh peaches and citrus from our Florida groves. Family-owned since 1985. Pre-order for 2026 season.", site: "@IndianRiverDirect", creator: "@IndianRiverDirect", - images: ["/og-indian-river.jpg"], + images: ["/og-default.svg"], }, alternates: { canonical: `${BASE_URL}/indian-river-direct`, @@ -68,7 +79,7 @@ export const metadata: Metadata = { }, }, other: { - "application/ld+json": JSON.stringify(indianRiverBreadcrumbSchema), + "application/ld+json": JSON.stringify(indianRiverStructuredData), }, }; diff --git a/src/app/loading.tsx b/src/app/loading.tsx index 0503665..34238e4 100644 --- a/src/app/loading.tsx +++ b/src/app/loading.tsx @@ -1,8 +1,8 @@ import type { Metadata } from "next"; export const metadata: Metadata = { - title: "Loading Route Commerce...", - description: "Loading...", + title: "Loading — Route Commerce", + description: "Loading content from Route Commerce", robots: { index: false, follow: false, @@ -11,43 +11,80 @@ export const metadata: Metadata = { export default function Loading() { return ( -
- {/* Subtle loading animation */} -
-
- - - +
+
); -} \ No newline at end of file +} diff --git a/src/app/login/LoginClient.tsx b/src/app/login/LoginClient.tsx index 300e3fc..81b5ea8 100644 --- a/src/app/login/LoginClient.tsx +++ b/src/app/login/LoginClient.tsx @@ -1,6 +1,6 @@ "use client"; -import { useState } from "react"; +import { useState, useId } from "react"; type LoginClientProps = { error: string | null; @@ -12,6 +12,8 @@ const REDIRECT_URL = "/admin"; const isDev = process.env.NODE_ENV !== "production"; export default function LoginClient({ error }: LoginClientProps) { + const emailId = useId(); + const passwordId = useId(); const [email, setEmail] = useState(""); const [password, setPassword] = useState(""); const [loading, setLoading] = useState(false); @@ -54,156 +56,173 @@ export default function LoginClient({ error }: LoginClientProps) { window.location.href = REDIRECT_URL; } - return ( -
- + const displayError = error ?? localError; + return ( +
+ {/* Decorative grain layer */} +
); -} \ No newline at end of file +} diff --git a/src/app/not-found.tsx b/src/app/not-found.tsx index d53a3ac..eb258c2 100644 --- a/src/app/not-found.tsx +++ b/src/app/not-found.tsx @@ -1,79 +1,82 @@ import Link from "next/link"; +import type { Metadata } from "next"; + +export const metadata: Metadata = { + title: "Page Not Found — Route Commerce", + description: "The page you are looking for could not be found. Explore the rest of Route Commerce.", + robots: { + index: false, + follow: false, + }, +}; export default function NotFound() { return ( -
- {/* Background decorations */} -
-
-
+
+
); -} \ No newline at end of file +} diff --git a/src/app/page.tsx b/src/app/page.tsx index 4439b29..65518dd 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -3,6 +3,63 @@ import LandingPageClient from "./LandingPageClient"; const BASE_URL = process.env.NEXT_PUBLIC_SITE_URL ?? "https://routecommerce.com"; +// JSON-LD structured data for the platform landing page. +// Schema.org SoftwareApplication + Organization + FAQPage so Google +// can render rich results for the product. +const structuredData = { + "@context": "https://schema.org", + "@graph": [ + { + "@type": "Organization", + "@id": `${BASE_URL}/#organization`, + name: "Route Commerce", + url: BASE_URL, + logo: `${BASE_URL}/logo.png`, + description: + "Multi-tenant B2B e-commerce platform for fresh produce wholesale distribution.", + sameAs: [ + // Add social profiles here as they come online + ], + }, + { + "@type": "SoftwareApplication", + "@id": `${BASE_URL}/#software`, + name: "Route Commerce", + applicationCategory: "BusinessApplication", + applicationSubCategory: "Wholesale Distribution Platform", + operatingSystem: "Web", + url: BASE_URL, + description: + "All-in-one platform for produce wholesale distribution: orders, stops, routes, customer communications, and Stripe/Square payments.", + offers: { + "@type": "AggregateOffer", + priceCurrency: "USD", + lowPrice: 49, + highPrice: 399, + priceSpecification: [ + { "@type": "UnitPriceSpecification", name: "Starter", price: 49, priceCurrency: "USD", description: "$49/month" }, + { "@type": "UnitPriceSpecification", name: "Farm", price: 149, priceCurrency: "USD", description: "$149/month" }, + { "@type": "UnitPriceSpecification", name: "Enterprise", price: 399, priceCurrency: "USD", description: "Custom pricing" }, + ], + }, + aggregateRating: { + "@type": "AggregateRating", + // Placeholder — replace with real review data once collected. + ratingValue: 4.9, + reviewCount: 12, + }, + }, + { + "@type": "WebSite", + "@id": `${BASE_URL}/#website`, + url: BASE_URL, + name: "Route Commerce", + publisher: { "@id": `${BASE_URL}/#organization` }, + inLanguage: "en-US", + }, + ], +} as const; + export const metadata: Metadata = { title: "Route Commerce — Fresh Produce Wholesale Platform", description: "The all-in-one platform for produce wholesale distribution. Manage orders, stops, routes, and customer communications. Built for farms, Co-ops, and distributors.", @@ -19,7 +76,7 @@ export const metadata: Metadata = { type: "website", images: [ { - url: "/og-default.jpg", + url: "/og-default.svg", width: 1200, height: 630, alt: "Route Commerce Platform", @@ -32,7 +89,7 @@ export const metadata: Metadata = { description: "The all-in-one platform for produce wholesale distribution.", site: "@RouteCommerce", creator: "@RouteCommerce", - images: ["/og-default.jpg"], + images: ["/og-default.svg"], }, alternates: { canonical: BASE_URL, @@ -61,5 +118,14 @@ export const viewport = { }; export default function LandingPage() { - return ; + return ( + <> +