Skip to main content

PromoCodeValidator

Implemented

Promo-code entry molecule with an embedded async validation state machine (idle → validating → valid / invalid). Renders input + Apply + a live-region feedback line; the result is a {valid, message} text line — no discount/expiry/terms display. Used in checkout.

Categorymolecules
Sourcepatterns/molecules/promocodevalidator.html
AI HintBuyer-side promo entry (checkout). The state machine ends at valid/invalid — no applied-code pill; consumers hand-roll the applied pill (see CheckoutForm) pending a step-6 API decision. Pass applyLabel (e.g. t('form.promo.apply')) for a localized Apply button; default 'Apply'. Success feedback uses the AA-passing text-ep-semantic-success-fg token.

Promo-code entry field with an embedded async validation state machine (idle → validating → valid / invalid). Barrel-exported molecule; the only consumer is the storefront checkout.

PromoCodeValidator

Molecule — Form & Validation

Raw input + Button atom + live-region feedback. The consumer owns the validation call (onValidate) and any applied-code display.

Try WELCOME10 (valid) — any other code is invalid.

Controlled value: (empty)

This component supports light and dark mode.

Props

Prop
Type
Default
Description
value
string
—

Controlled value. Omit to run uncontrolled (internal state).

onChange*
(value: string) => void
—

Called on every keystroke with the (auto-formatted) value.

onValidate*
(code: string) => Promise<{ valid: boolean; message?: string; discount?: string }>
—

Called on Apply / Enter. Drives the validating → valid / invalid lifecycle.

loading
boolean
false

Forces the busy state (parent-driven async work).

placeholder
string
Enter promo code

Placeholder text shown when the field is empty.

disabled
boolean
false

Disables both the input and the Apply button.

autoFormat
boolean
true

Uppercases input and filters to A-Z / 0-9.

size
'sm' | 'md' | 'lg'
md

Size preset applied to the input and the Apply button.

label
string
—

Visible label, associated via htmlFor.

hint
string
—

Helper text shown below the field in the idle state.

error
string
—

Error message; takes precedence over validation feedback.

successMessage
string
—

Success message shown when validation succeeds.

className
string
—

Additional CSS classes on the root element.

id
string
—

Input id override; derived from useId otherwise.

Usage

Barrel import. Both onChange and onValidate are required — the component does not default them. The real-world wiring (storefront checkout) calls a server endpoint from onValidate and reports a valid / message pair; the molecule owns the Apply button, Enter-to-submit and the status lifecycle.

import { useState } from 'react'
import { PromoCodeValidator } from '@epasslive/design-system'

export function PromoSection() {
  const [code, setCode] = useState('')

  return (
    <PromoCodeValidator
      label="Promo code"
      value={code}
      onChange={setCode}
      onValidate={async (candidate) => {
        const res = await fetch('/api/promo-codes/apply', {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({ code: candidate }),
        })
        const data = await res.json().catch(() => ({}))
        return res.ok
          ? { valid: true, message: 'Promo code applied.' }
          : { valid: false, message: data.message ?? 'This code is not valid.' }
      }}
    />
  )
}

Props

Preview

PromoCodeValidator

Prop
Type
Required
Default
Description
value
string
Optional
—

Controlled value. Omit to run uncontrolled (internal state).

onChange
(value: string) => void
Required
—

Called on every keystroke with the (auto-formatted) value.

onValidate
(code: string) => Promise<{ valid: boolean; message?: string; discount?: string }>
Required
—

Called on Apply / Enter. Drives the validating → valid / invalid lifecycle.

loading
boolean
Optional
false

Forces the busy state (parent-driven async work).

placeholder
string
Optional
'Enter promo code'

Placeholder text shown when the field is empty.

disabled
boolean
Optional
false

Disables both the input and the Apply button.

autoFormat
boolean
Optional
true

Uppercases input and filters to A-Z / 0-9.

size
'sm' | 'md' | 'lg'
Optional
'md'

Size preset applied to the input and the Apply button.

label
string
Optional
—

Visible label, associated via htmlFor.

hint
string
Optional
—

Helper text shown below the field in the idle state.

error
string
Optional
—

Error message; takes precedence over validation feedback.

successMessage
string
Optional
—

Success message shown when validation succeeds.

applyLabel
string
Optional
'Apply'

Label for the Apply button — pass a localized string (e.g. t('form.promo.apply')) to avoid hardcoded English.

className
string
Optional
—

Additional CSS classes on the root element.

id
string
Optional
generated useId

Input id override; derived from useId otherwise.

Quick use:<PromoCodeValidator />

Accessibility

Type

1 rule

NASA Rule 5

promo code must be non-empty string

ARIA

1 rule

WCAG 4.1.3

Validation result must be announced to screen reader

Usage

Import

tsxSnippet
import { PromoCodeValidator } from "@epasslive/design-system"

Example 1

tsxSnippet
<PromoCodeValidator label={t("form.promo.label")} placeholder={t("form.promo.placeholder")} value={code} onChange={setCode} onValidate={applyPromo} successMessage={t("form.promo.applied")} />

Anti-patterns

— Do not hardcode data in molecules

— Avoid complex business logic in molecules

Design System v4.1.0 | ePassLive