From 81043d4fe42932befd76665f6286717ede07336f Mon Sep 17 00:00:00 2001 From: Chad Nuttall Date: Mon, 5 Oct 2026 14:30:17 -0400 Subject: [PATCH 1/4] Brand-native states + public form polish (#167, #169) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Trade inquiry form: double-submit guard (Enter re-fire while disabled button couldn't be pressed), aria-busy during submission, a branded success panel with the turtle mark, and a proper ember alert panel that only mounts on error (with a contact-page fallback link and values preserved). - Beers filter grid gains a visible empty state — previously a zero-result filter silently rendered an empty grid. - 404 and root error boundary get the hoppy turtle, a friendlier recovery line, and a second action (Browse our beers / Try again + home) under the floating pill nav. - Stripe pay landing pages get the brand mark; the success surface takes a subtle moss accent consistent with the trade success panel. - Tour dialog party-size input gets autoComplete=off. No backend semantics, analytics events, or data contracts changed. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- app/(pages)/pay/cancelled/page.tsx | 9 ++++- app/(pages)/pay/complete/page.tsx | 11 ++++-- app/error.tsx | 9 ++++- app/not-found.tsx | 23 +++++++++--- components/beers-filter-grid.tsx | 30 ++++++++++++++++ components/tour-inquiry-cta.tsx | 1 + components/trade-inquiry-form.tsx | 58 +++++++++++++++++++++++------- 7 files changed, 119 insertions(+), 22 deletions(-) diff --git a/app/(pages)/pay/cancelled/page.tsx b/app/(pages)/pay/cancelled/page.tsx index fd89a7e..8e7344f 100644 --- a/app/(pages)/pay/cancelled/page.tsx +++ b/app/(pages)/pay/cancelled/page.tsx @@ -1,4 +1,5 @@ import type { Metadata } from "next"; +import { BrandMark } from "@/components/brand-mark"; // Landing page customers reach if they back out of Stripe's hosted payment // page (Checkout cancel_url). Static and data-free by design. @@ -15,7 +16,13 @@ export default function PayCancelledPage() { return (
-

Payment canceled

+ +

Payment canceled

No charge was made. If you meant to complete the payment, ask the brewery for the payment link again — it may need to be re-sent. diff --git a/app/(pages)/pay/complete/page.tsx b/app/(pages)/pay/complete/page.tsx index a3c3a0a..577d9e0 100644 --- a/app/(pages)/pay/complete/page.tsx +++ b/app/(pages)/pay/complete/page.tsx @@ -1,4 +1,5 @@ import type { Metadata } from "next"; +import { BrandMark } from "@/components/brand-mark"; // Landing page customers reach after paying on Stripe's hosted page // (Checkout success_url). Static and data-free by design — payment state is @@ -15,8 +16,14 @@ export const metadata: Metadata = { export default function PayCompletePage() { return (

-
-

Payment received

+
+ +

Payment received

Thank you — your payment to Deep Dive Brewing Co was received. If you asked for a receipt, it is on its way to your inbox. diff --git a/app/error.tsx b/app/error.tsx index b153ace..dfb4b75 100644 --- a/app/error.tsx +++ b/app/error.tsx @@ -4,6 +4,7 @@ import * as Sentry from "@sentry/nextjs"; import { useEffect } from "react"; import Link from "next/link"; import { SiteNav } from "@/components/site-nav"; +import { BrandMark } from "@/components/brand-mark"; import { Button } from "@/components/ui/button"; // Root error boundary: an unhandled render error in any page lands here with @@ -39,7 +40,13 @@ export default function Error({ tabIndex={-1} className="mx-auto max-w-300 px-6 pb-20 pt-[calc(5.75rem+env(safe-area-inset-top))] text-center md:pb-30" > -

+ +

Something went wrong

diff --git a/app/not-found.tsx b/app/not-found.tsx index 82174eb..c1227ca 100644 --- a/app/not-found.tsx +++ b/app/not-found.tsx @@ -1,6 +1,7 @@ import type { Metadata } from "next"; import Link from "next/link"; import { SiteNav } from "@/components/site-nav"; +import { BrandMark } from "@/components/brand-mark"; import { Button } from "@/components/ui/button"; export const metadata: Metadata = { @@ -20,18 +21,30 @@ export default function NotFound() { tabIndex={-1} className="mx-auto max-w-300 px-6 pb-20 pt-[calc(5.75rem+env(safe-area-inset-top))] text-center md:pb-30" > -

+ +

404

This page could not be found

- The link may be outdated or the page may have moved. + The link may be outdated or the page may have moved. The beers, + however, are right where we left them.

- +
+ + +

); diff --git a/components/beers-filter-grid.tsx b/components/beers-filter-grid.tsx index 4989772..d9a6876 100644 --- a/components/beers-filter-grid.tsx +++ b/components/beers-filter-grid.tsx @@ -71,6 +71,36 @@ export function BeersFilterGrid({ beers }: BeersFilterGridProps) { ))}
+ + {filteredBeers.length === 0 && ( +
+

+ No{" "} + {filterOptions + .find((option) => option.value === activeFilter) + ?.label.toLowerCase()}{" "} + beers on the list right now — availability changes with the + season. The full lineup is still on tap. +

+ +
+ )} ); } diff --git a/components/tour-inquiry-cta.tsx b/components/tour-inquiry-cta.tsx index 708da43..83bd57d 100644 --- a/components/tour-inquiry-cta.tsx +++ b/components/tour-inquiry-cta.tsx @@ -229,6 +229,7 @@ export function TourInquiryCta({ min={1} step={1} inputMode="numeric" + autoComplete="off" placeholder="Number of people" value={partySize} onChange={(event) => setPartySize(event.target.value)} diff --git a/components/trade-inquiry-form.tsx b/components/trade-inquiry-form.tsx index 9b12268..ee2a456 100644 --- a/components/trade-inquiry-form.tsx +++ b/components/trade-inquiry-form.tsx @@ -1,7 +1,9 @@ "use client"; +import Link from "next/link"; import { useEffect, useRef, useState } from "react"; import { Button } from "@/components/ui/button"; +import { BrandMark } from "@/components/brand-mark"; import { trackEvent } from "@/lib/analytics"; import { TRADE_LEAD_ISLANDS, TRADE_VENUE_TYPES } from "@/lib/trade-leads-common"; @@ -49,6 +51,9 @@ export function TradeInquiryForm() { async function handleSubmit(e: React.FormEvent) { e.preventDefault(); + // Enter-to-submit can re-fire while a request is in flight even though + // the button is disabled — guard so a second press never double-posts. + if (status === "submitting") return; setStatus("submitting"); setErrorMessage(""); @@ -102,7 +107,13 @@ export function TradeInquiryForm() { aria-live="polite" className="rounded-lg border border-moss/30 bg-moss/5 p-8 text-center outline-none focus-visible:ring-2 focus-visible:ring-moss/50" > -

Thanks for reaching out.

+ +

Thanks for reaching out.

Your inquiry is in our inbox. The Deep Dive team will follow up.

@@ -111,7 +122,11 @@ export function TradeInquiryForm() { } return ( -
+ {/* Honeypot: hidden from humans and assistive technology */} -
- {status === "error" && ( -

+ {/* Rendered only when there's an error — an always-mounted empty live + region is an invisible dead element for sighted users. role=alert + announces it the moment it appears; focus moves here so keyboard + users land on the message (entered values are preserved). */} + {status === "error" && ( +

+

+ We couldn’t send your inquiry +

+

{errorMessage || "Something went wrong. Please try again."}

- )} -
+

+ Your details are still here — try sending again, or reach us via + the{" "} + + contact page + {" "} + if it keeps happening. +

+
+ )}