Discount Builder
A discount builder where the offer is one editable sentence, with a live checkout preview. Handles code checks, drafts and schedules.
Discount Builder is the page a merchant uses to create or edit a discount. A new discount opens with a short type dialog: amount off products, amount off order, buy X get Y or free shipping. After that the offer is a sentence the merchant edits in place: “Customers get [15%] off [Ring Vase and Glass Carafe]”. Each underlined part is a real control with a label, so screen readers hear “Percentage off, 15” rather than a sentence of buttons.
Everything optional sits in a Rules card as collapsed rows: Minimum purchase, Customers, Usage limits, Combines with and Schedule. Each row shows its current value, so a merchant can read the whole setup without opening anything, and each panel explains its setting with an example. Schedules start now by default and offer presets for 7 days, 30 days and the end of the month, in the store’s time zone.
The rail is a preview, not a list of settings. It shows the offer as one sentence and a sample checkout built from your products, with the discount applied and what the customer saves. A Qualifies / Doesn’t toggle swaps to a cart just under the minimum and shows the same message a storefront would, such as “Add $10.00 more to use SUMMER15”. Risky settings, like 100% off with no limit, show up as warnings under it.
Data goes in, async callbacks come out. onSave gets a cleaned Discount and the intent (draft or live); return field errors to show them on the sentence or the rule rows, or throw to show the message in the save bar. Errors appear when a field is left, never while typing. New discounts save from the header (Save draft and Create discount). Existing ones use the shared IslandBar, which appears only when there are unsaved changes.
HextaUI Pro'yu alın
Tek bir ödeme, gelecekte eklenecekler dahil tüm blokların kilidini açar. Fiyatları görün.
Pro registry'yi components.json dosyasına ekleyin
components.json Token'ınızı ekleyin
Hesap sayfanızda bir token oluşturun ve
.env.localdosyasınaHEXTAUI_PRO_TOKENolarak ekleyin.Bloğu ekleyin
pnpm dlx shadcn@latest add @hextaui-pro/discount-builder
Shopify Admin API
Map the Discount to discountCodeBasicCreate or discountAutomaticBxgyCreate and return Shopify’s userErrors as field errors. Drafts go to your own table, since Shopify has no draft discounts. The type comes from the route, so the type dialog is off.
Stripe coupons and promotion codes
A live discount becomes a Stripe coupon plus a promotion code with its redemption limit, first-order rule and expiry. Stripe’s duplicate-code error comes back as a code field error.
Medusa promotions
Medusa v2 promotions take the same shape and support draft status directly. onCheckCode lists promotions by code to check that a new one is free.
Yapı
Dıştan içe doğru birleştirdiğiniz parçalar.
| Parça | Açıklama |
|---|---|
DiscountBuilder | The whole page: pinned header with the title, status badge and actions, the offer and rules cards, the preview rail (a collapsible strip under the header on phones) and the save bar. |
TypeChooser | The “Create a discount” dialog with four types and number-key shortcuts. A bottom sheet on phones. |
DiscountPreview | The summary sentence, status line, sample checkout with a qualifies toggle, warnings and, for saved discounts, performance. |
DiscountStatusBadge | Draft, Scheduled, Active, Ends in 2 days, Expired or Limit reached. |
applyDiscount, sampleCart | Pure helpers that apply a discount to a cart and build a cart that qualifies, or just misses. |
describeDiscount, discountWarnings, qualifyMessage | Plain-English clauses, risk warnings and the storefront message for a cart that doesn’t qualify. |
validateDiscount, suggestCode, generateCode, cleanCode, nextCode | Specific field errors (drafts skip missing values), code suggestions such as SAVE15 and readable random codes. |
schedulePresets, getDiscountStatus, fromZonedDateTime, toZonedDateTime | End-date presets in the store’s time zone, status at a moment in time and time zone conversion. |
DiscountBuilder
Also takes div props. Fills its parent’s height; only the content scrolls.
| Prop | Tür | Varsayılan |
|---|---|---|
discountThe saved discount, or createDiscount() for a new one. New discounts start now as drafts. The form edits a draft of it and adopts new values when clean. | Discount | – |
onSaveCalled with a cleaned discount. A live save resolves “now” to the current time. Return field errors to show them in place, or throw to show the message in the save bar. | (discount: Discount, { intent: "draft" | "live" }) => Promise<DiscountErrors | void> | – |
onCheckCodeChecks that a code is free, 400 ms after typing stops. A taken code offers the next free variant, such as WELCOME11. | (code: string, { signal }) => Promise<boolean | { available: boolean; message?: string }> | – |
productsProducts for the pickers and the sample checkout, in the shared catalog shape. | Product[] | – |
collectionsCollections with an optional count, image and productIds. productIds lets the preview build carts for collection discounts. | DiscountCollection[] | [] |
segmentsCustomer segments with an optional customer count. | DiscountSegment[] | [] |
customersCustomers with a name and email. | DiscountCustomer[] | [] |
statsShown as Performance for saved discounts only. | { uses: number; sales: Money; orders?: number; lastUsedAt?: string } | – |
existingDiscountsOther live discounts, used to word the Combines with examples (“e.g. FREESHIP can apply on top”). | { code?: string; title?: string; type: DiscountType }[] | – |
showTypePickerShow the type dialog for new discounts. Turn it off when the type comes from the route. | boolean | true |
defaultTypePickerOpenOpen the type dialog when a new discount mounts. | boolean | true |
sampleProductIdsProducts to prefer in the sample checkout. | string[] | – |
sampleShippingShipping rate in the sample checkout. | Money | null | $12.00 |
timeZoneThe store’s time zone for dates, presets and the schedule note. | string | "UTC" |
localeFormats money, dates and counts. | string | "en-US" |
nowFixes the clock, for tests and screenshots. | Date | number | – |
breadcrumbShown above the title, such as a link back to Discounts. | ReactNode | – |
onDuplicate, onDeactivateAdds Duplicate and End now to the overflow menu of saved discounts. | (discount: Discount) => void | – |
onChange, onDiscardDraft changes and discards. | (discount: Discount) => void, () => void | – |
generateCodeReplaces the built-in random code (WORD-XXXX). | () => string | – |
getProductImageReturns a thumbnail URL for pickers and the checkout. | (image: ProductImage) => string | – |
actionsRefImperative actions, such as nudging the save bar when someone tries to leave with unsaved changes. | Ref<{ save, discard, nudge, isDirty, openTypePicker }> | – |
labelsButton and save bar text. | Partial<DiscountBuilderLabels> | – |
DiscountPreview
The rail on its own, for a list’s side panel.
| Prop | Tür | Varsayılan |
|---|---|---|
discountThe discount to preview. | Discount | – |
productsUsed to build the sample cart. | Product[] | – |
statusFrom getDiscountStatus. | DiscountStatusInfo | null | – |
onClauseCalled when a sentence clause is clicked, to jump to its control. | (id) => void | – |
applyDiscount
applyDiscount(discount, cart, { collections }) returns line discounts, subtotal, discount and shipping totals, total, qualifies and a reason when it doesn’t.
| Prop | Tür | Varsayılan |
|---|
sampleCart
sampleCart(discount, products, { qualifies, collections, preferredIds, shipping }) returns a cart that crosses the minimum or just misses it, or null when every cart qualifies.
| Prop | Tür | Varsayılan |
|---|
| Tuş | Action |
|---|---|
| ⌘S | Creates or saves the discount. Ctrl+S on Windows and Linux. Works while typing. |
| ⌘⇧S | Saves a new discount as a draft. Ctrl+Shift+S on Windows and Linux. |
| ⌘G | Generates a new code and focuses the code field. Ctrl+G on Windows and Linux. |
| 1234 | In the type dialog, picks a type. |
| ↑↓ | Moves between types in the dialog and between rule rows. |
| Tab | In an empty code field, accepts the suggested code, such as SAVE15. |
| Esc | Closes the open dialog, picker or menu, or collapses the rule row you’re in. |
- Every sentence token is a real input or button with a full name, such as “Percentage off” or “Applies to, Ring Vase and Glass Carafe”. Errors are linked with aria-describedby and marked aria-invalid.
- Errors show when a field is left, not while typing, and clear on the keystroke that fixes them. A failed save moves focus to the first invalid control and opens its rule row.
- Rule rows are buttons with aria-expanded and aria-controls. Their value text is real text, so the collapsed card reads as a summary.
- Code availability, generated codes and save results are announced in polite live regions. The save bar’s errors use role=alert through IslandBar.
- The checkout result line is a status region, and color is never the only signal: savings and failures have icons and words.
- Inputs are 16px on touch screens, touch targets are at least 44px and all motion has a reduced-motion variant.
Şunlarla geliştirildi
Discount Builder bloğunun yapıldığı ücretsiz HextaUI bileşenleri. Her biri tek başına yüklenir.
Kod
13 dosya, components/blocks/discount-builder konumuna eklendi.