From cb0ac7f41dbf6c295a3bde60e5a7ed96a74bc40b Mon Sep 17 00:00:00 2001 From: Paul Wackerow <54227730+wackerow@users.noreply.github.com> Date: Thu, 24 Jul 2025 13:54:24 -0700 Subject: [PATCH 01/64] feat: initialize enterprise contact form --- .../_components/ContactForm/index.tsx | 37 ++++++++++++++++++ .../_components/ContactForm/lazy.tsx | 5 +++ .../_components/ContactForm/loading.tsx | 10 +++++ app/[locale]/enterprise/page.tsx | 17 ++++----- src/components/ui/textarea.tsx | 38 +++++++++++++++++++ src/intl/en/common.json | 2 + src/intl/en/page-enterprise.json | 5 ++- 7 files changed, 102 insertions(+), 12 deletions(-) create mode 100644 app/[locale]/enterprise/_components/ContactForm/index.tsx create mode 100644 app/[locale]/enterprise/_components/ContactForm/lazy.tsx create mode 100644 app/[locale]/enterprise/_components/ContactForm/loading.tsx create mode 100644 src/components/ui/textarea.tsx diff --git a/app/[locale]/enterprise/_components/ContactForm/index.tsx b/app/[locale]/enterprise/_components/ContactForm/index.tsx new file mode 100644 index 00000000000..421f7b5e7e8 --- /dev/null +++ b/app/[locale]/enterprise/_components/ContactForm/index.tsx @@ -0,0 +1,37 @@ +"use client" + +import { Button } from "@/components/ui/buttons/Button" +import Input from "@/components/ui/input" +import { Textarea } from "@/components/ui/textarea" + +type EnterpriseContactFormProps = { + emailPlaceholder: string + bodyPlaceholder: string + buttonLabel: string +} + +const EnterpriseContactForm = ({ + emailPlaceholder, + bodyPlaceholder, + buttonLabel, +}: EnterpriseContactFormProps) => ( +
+ + + + ) +} From 60a8253bef1346a9e1a6208394a57aa76129ff7c Mon Sep 17 00:00:00 2001 From: Dmitry <98899785+mdqst@users.noreply.github.com> Date: Mon, 28 Jul 2025 14:51:44 +0300 Subject: [PATCH 05/64] docs: fix broken documentation link to Chainstack --- public/content/developers/docs/apis/backend/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/content/developers/docs/apis/backend/index.md b/public/content/developers/docs/apis/backend/index.md index 100b43333fa..47219134ea6 100644 --- a/public/content/developers/docs/apis/backend/index.md +++ b/public/content/developers/docs/apis/backend/index.md @@ -147,7 +147,7 @@ These libraries abstract away much of the complexity of interacting directly wit **Chainstack -** **_Elastic and dedicated Ethereum nodes as a service._** - [chainstack.com](https://chainstack.com) -- [Documentation](https://docs.chainbase.com/docs) +- [Documentation](https://docs.chainstack.com/) - [Ethereum API reference](https://docs.chainstack.com/reference/ethereum-getting-started) **Coinbase Cloud Node -** **_Blockchain Infrastructure API._** From a53694d0a49d38f65e41fbc896fa327fbff1e09d Mon Sep 17 00:00:00 2001 From: Paul Wackerow <54227730+wackerow@users.noreply.github.com> Date: Mon, 28 Jul 2025 11:39:52 -0700 Subject: [PATCH 06/64] revert: min char requirement - enterprise form body --- .../enterprise/_components/ContactForm/index.tsx | 5 ----- app/[locale]/enterprise/constants.ts | 2 -- app/[locale]/enterprise/page.tsx | 5 +---- app/api/enterprise-contact/route.ts | 12 ------------ 4 files changed, 1 insertion(+), 23 deletions(-) diff --git a/app/[locale]/enterprise/_components/ContactForm/index.tsx b/app/[locale]/enterprise/_components/ContactForm/index.tsx index 51eb2d69f72..ce2a652c485 100644 --- a/app/[locale]/enterprise/_components/ContactForm/index.tsx +++ b/app/[locale]/enterprise/_components/ContactForm/index.tsx @@ -8,15 +8,12 @@ import Input from "@/components/ui/input" import { Spinner } from "@/components/ui/spinner" import { Textarea } from "@/components/ui/textarea" -import { CONTACT_FORM_CHAR_MIN } from "../../constants" - type EnterpriseContactFormProps = { strings: { error: { domain: string emailInvalid: string general: string - minLength: React.ReactNode // constant injected into span required: string } placeholder: { @@ -127,8 +124,6 @@ const EnterpriseContactForm = ({ strings }: EnterpriseContactFormProps) => { if (!sanitized) return strings.error.required - if (sanitized.length < CONTACT_FORM_CHAR_MIN) return strings.error.minLength - return undefined } diff --git a/app/[locale]/enterprise/constants.ts b/app/[locale]/enterprise/constants.ts index 303a5fedbe3..528ba6ce7a7 100644 --- a/app/[locale]/enterprise/constants.ts +++ b/app/[locale]/enterprise/constants.ts @@ -1,5 +1,3 @@ // TODO: Confirm export const ENTERPRISE_MAILTO = "mailto:enterprise@ethereum.org?subject=Enterprise%20inquiry" - -export const CONTACT_FORM_CHAR_MIN = 40 diff --git a/app/[locale]/enterprise/page.tsx b/app/[locale]/enterprise/page.tsx index 8a56d58c548..5f10b7edcfe 100644 --- a/app/[locale]/enterprise/page.tsx +++ b/app/[locale]/enterprise/page.tsx @@ -51,7 +51,7 @@ import { BASE_TIME_UNIT } from "@/lib/constants" import CasesColumn from "./_components/CasesColumn" import EnterpriseContactForm from "./_components/ContactForm/lazy" import FeatureCard from "./_components/FeatureCard" -import { CONTACT_FORM_CHAR_MIN, ENTERPRISE_MAILTO } from "./constants" +import { ENTERPRISE_MAILTO } from "./constants" import type { Case, EcosystemPlayer, Feature } from "./types" import { parseActivity } from "./utils" @@ -503,9 +503,6 @@ const Page = async ({ params }: { params: { locale: Lang } }) => { "page-enterprise-team-form-error-email-invalid" ), general: t("page-enterprise-team-form-error-general"), - minLength: t.rich("page-enterprise-team-form-error-short", { - span: () => CONTACT_FORM_CHAR_MIN, - }), required: t("page-enterprise-team-form-error-required"), }, placeholder: { diff --git a/app/api/enterprise-contact/route.ts b/app/api/enterprise-contact/route.ts index 7f28ef560ef..71fa92e5fff 100644 --- a/app/api/enterprise-contact/route.ts +++ b/app/api/enterprise-contact/route.ts @@ -1,7 +1,5 @@ import { NextRequest, NextResponse } from "next/server" -import { CONTACT_FORM_CHAR_MIN } from "../../[locale]/enterprise/constants" - const ENTERPRISE_EMAIL = "enterprise@ethereum.org" const RATE_LIMIT_WINDOW_MS = 60 * 1000 // 1 minute const MAX_REQUESTS_PER_WINDOW = 3 @@ -90,16 +88,6 @@ export async function POST(request: NextRequest) { ) } - // Validate message length - if (sanitizedMessage.length < CONTACT_FORM_CHAR_MIN) { - return NextResponse.json( - { - error: `Message must be at least ${CONTACT_FORM_CHAR_MIN} characters`, - }, - { status: 400 } - ) - } - // Create email content const emailSubject = "Enterprise Inquiry from ethereum.org" const emailBody = ` From 034534f79f512a0586d196ab5c18f3bf4861430e Mon Sep 17 00:00:00 2001 From: Paul Wackerow <54227730+wackerow@users.noreply.github.com> Date: Mon, 28 Jul 2025 11:58:14 -0700 Subject: [PATCH 07/64] feat: update ui/input ui/textarea with error state styling --- src/components/ui/input.tsx | 12 +++++++++--- src/components/ui/textarea.tsx | 12 +++++++++--- 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/src/components/ui/input.tsx b/src/components/ui/input.tsx index 49112e58781..286e2ec430b 100644 --- a/src/components/ui/input.tsx +++ b/src/components/ui/input.tsx @@ -4,16 +4,22 @@ import { cva, type VariantProps } from "class-variance-authority" import { cn } from "@/lib/utils/cn" const inputVariants = cva( - "rounded border border-body placeholder:text-disabled hover:not-disabled:border-primary-hover focus-visible:outline focus-visible:outline-primary-hover focus-visible:outline-[3px] focus-visible:-outline-offset-2 disabled:cursor-not-allowed disabled:border-disabled bg-background", + "rounded border placeholder:text-disabled focus-visible:outline focus-visible:outline-[3px] focus-visible:-outline-offset-2 disabled:cursor-not-allowed disabled:border-disabled bg-background", { variants: { size: { md: "p-2", sm: "p-1 text-sm", }, + hasError: { + true: "border-error hover:not-disabled:border-error focus-visible:outline-error", + false: + "border-body hover:not-disabled:border-primary-hover focus-visible:outline-primary-hover", + }, }, defaultVariants: { size: "md", + hasError: false, }, } ) @@ -23,11 +29,11 @@ export interface InputProps VariantProps {} const Input = React.forwardRef( - ({ className, type, size, ...props }, ref) => { + ({ className, type, size, hasError, ...props }, ref) => { return ( diff --git a/src/components/ui/textarea.tsx b/src/components/ui/textarea.tsx index 9c0680003c9..9f116c78acb 100644 --- a/src/components/ui/textarea.tsx +++ b/src/components/ui/textarea.tsx @@ -4,16 +4,22 @@ import { cva, type VariantProps } from "class-variance-authority" import { cn } from "@/lib/utils/cn" const textareaVariants = cva( - "focus-visible:outline focus-visible:outline-primary-hover focus-visible:outline-[3px] focus-visible:-outline-offset-2 flex min-h-[200px] w-full rounded border border-body bg-background px-3 py-2 text-base ring-offset-background placeholder:text-disabled disabled:cursor-not-allowed disabled:opacity-50 hover:not-disabled:border-primary-hover", + "focus-visible:outline focus-visible:outline-[3px] focus-visible:-outline-offset-2 flex min-h-[200px] w-full rounded border bg-background px-3 py-2 text-base ring-offset-background placeholder:text-disabled disabled:cursor-not-allowed disabled:opacity-50", { variants: { size: { md: "p-2", sm: "p-1 text-sm", }, + hasError: { + true: "border-error hover:not-disabled:border-error focus-visible:outline-error", + false: + "border-body hover:not-disabled:border-primary-hover focus-visible:outline-primary-hover", + }, }, defaultVariants: { size: "md", + hasError: false, }, } ) @@ -23,10 +29,10 @@ export interface TextareaProps VariantProps {} const Textarea = React.forwardRef( - ({ className, size, ...props }, ref) => { + ({ className, size, hasError, ...props }, ref) => { return ( - - ) -} From c651e86b5a89feb1fafb1a3fef0619fbc0ec8160 Mon Sep 17 00:00:00 2001 From: Paul Wackerow <54227730+wackerow@users.noreply.github.com> Date: Tue, 5 Aug 2025 17:17:20 -0700 Subject: [PATCH 50/64] feat: advance ab test to new production test testKey: StablecoinApps --- app/[locale]/stablecoins/page.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/[locale]/stablecoins/page.tsx b/app/[locale]/stablecoins/page.tsx index dba1c00c0ab..6d5a6731a0d 100644 --- a/app/[locale]/stablecoins/page.tsx +++ b/app/[locale]/stablecoins/page.tsx @@ -598,7 +598,7 @@ async function Page({ params }: { params: Promise<{ locale: Lang }> }) { alt={t("page-stablecoins-stablecoins-dapp-callout-image-alt")} > From d0805ce42e0ceeafacbc5453be16b11ffc02dbf4 Mon Sep 17 00:00:00 2001 From: Paul Wackerow <54227730+wackerow@users.noreply.github.com> Date: Tue, 5 Aug 2025 16:32:08 -0700 Subject: [PATCH 51/64] feat: add max char error states --- .../_components/ContactForm/index.tsx | 49 +++++++++++++++---- app/[locale]/enterprise/constants.ts | 2 + app/[locale]/enterprise/page.tsx | 13 +++++ src/intl/en/page-enterprise.json | 2 + 4 files changed, 57 insertions(+), 9 deletions(-) create mode 100644 app/[locale]/enterprise/constants.ts diff --git a/app/[locale]/enterprise/_components/ContactForm/index.tsx b/app/[locale]/enterprise/_components/ContactForm/index.tsx index b3aecc433a4..ce07646c976 100644 --- a/app/[locale]/enterprise/_components/ContactForm/index.tsx +++ b/app/[locale]/enterprise/_components/ContactForm/index.tsx @@ -8,12 +8,18 @@ import Input from "@/components/ui/input" import { Spinner } from "@/components/ui/spinner" import { Textarea } from "@/components/ui/textarea" +import { cn } from "@/lib/utils/cn" + +import { MAX_EMAIL_LENGTH, MAX_MESSAGE_LENGTH } from "../../constants" + type EnterpriseContactFormProps = { strings: { error: { domain: React.ReactNode // Link injected emailInvalid: string + emailTooLong: string // Length injected via {length} general: string + messageTooLong: string // Length injected via {length} required: string } placeholder: { @@ -83,6 +89,13 @@ const sanitizeInput = (input: string): string => .trim() const EnterpriseContactForm = ({ strings }: EnterpriseContactFormProps) => { + const getCharacterCountClasses = (currentLength: number, maxLength: number) => + cn( + currentLength >= Math.floor(maxLength * 0.9) && "block", // Show char count when within 10% remaining to limit + currentLength > maxLength - 64 && "text-warning-border", // Warning color within 64 chars (border version for proper contrast ratio), + currentLength > maxLength && "text-error" // Error color over limit + ) + const [formData, setFormData] = useState({ email: "", message: "", @@ -126,6 +139,8 @@ const EnterpriseContactForm = ({ strings }: EnterpriseContactFormProps) => { if (!sanitized) return strings.error.required + if (sanitized.length > MAX_EMAIL_LENGTH) return strings.error.emailTooLong + const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/ if (!emailRegex.test(sanitized)) return strings.error.emailInvalid @@ -142,6 +157,9 @@ const EnterpriseContactForm = ({ strings }: EnterpriseContactFormProps) => { if (!sanitized) return strings.error.required + if (sanitized.length > MAX_MESSAGE_LENGTH) + return strings.error.messageTooLong + return undefined } @@ -220,15 +238,28 @@ const EnterpriseContactForm = ({ strings }: EnterpriseContactFormProps) => {
-