More Forms Snippets
Promo Code Input — Apply Coupon HTML CSS JS
Promo Code Input · Forms · Plain HTML, CSS & JS · Live preview
What's included
Features
About this UI Snippet
Promo Code Input — Coupon Validation, Discount Types & Live Order Total

Almost every checkout has a "promo code" or "discount code" field, and it's deceptively easy to get wrong: codes need validating, different coupon types calculate differently, the order total must update without a page reload, and the user needs clear feedback whether a code worked or not. This snippet builds a complete, correct promo-code experience in plain HTML, CSS, and vanilla JavaScript — supporting percentage, fixed-amount, and free-shipping coupons, with an applied state, a remove control, and a live-recalculating total.
Three coupon types, one calculation function
Real stores run more than one kind of discount, so discountAmount() handles three: percent (20% off the subtotal), fixed (a flat $10 off, clamped so it never exceeds the subtotal and produces a negative price), and shipping (offsets the shipping line for free delivery). Each code in the CODES map declares its type and value, so adding a new coupon is a one-line data change, not new logic. The total always recomputes as subtotal + shipping − discount, floored at zero.
Validation that explains itself
Submitting a code runs through ordered checks, each with a specific message rather than a generic failure: an empty field ("Enter a promo code first"), a code that's already applied ("That code is already applied"), and an unrecognised code ("'BOGUS' is not a valid code"). Invalid input also flashes a red border on the field. This specificity matters at checkout — a shopper who typed a code from an email needs to know whether they fat-fingered it or it simply expired, not just that "something went wrong."
Case- and whitespace-insensitive matching
Codes are normalised with trim().toUpperCase() before lookup, and the input is visually uppercased via CSS, so "save20", " SAVE20 ", and "Save20" all resolve to the same coupon. This removes the single most common reason a valid code "doesn't work" — invisible whitespace or a lowercase letter — without the user ever having to think about it.
Applied and remove states
A successfully applied code hides the input and shows a green confirmation chip ("✓ SAVE20 applied") with a Remove link. Removing it restores the input, clears the discount line, recomputes the total, and refocuses the field — so a shopper can swap one code for a better one without confusion. The discount line itself only appears when a code is active, and its label adapts ("Discount (SAVE20)" vs "Free shipping") to match the coupon type.
Where the real validation lives
Client-side code-matching is for instant feedback only — it must never be the source of truth, because anyone can read the CODES map in the page source. In production the field still gives immediate UX, but the authoritative check (does this code exist, is it expired, is it limited to this customer, does it stack) happens server-side when the order is priced and again when it's placed. The FAQs cover wiring the front end to that real validation endpoint while keeping this snippet's instant-feedback feel.
Step by step
How to Use
- 1Paste HTML, CSS, and JSAn order summary renders with a subtotal, shipping, total, and a promo-code field. Try SAVE20, FREESHIP, or HALFOFF.
- 2Apply a valid codeEnter SAVE20 and click Apply — a discount line appears, the total drops, and a green "applied" chip replaces the field.
- 3See validation errorsEnter a made-up code or leave it blank — a specific red message explains exactly what's wrong, and the field flags invalid.
- 4Remove an applied codeClick Remove on the applied chip — the discount clears, the total restores, and the input comes back focused for a new code.
- 5Add your own couponsAdd entries to the CODES map with a type (percent / fixed / shipping) and value — no new logic needed.
- 6Validate codes server-sideReplace the client-side CODES lookup with a fetch to your coupon-validation endpoint, keeping the same applied/error UI states.
Real-world uses
Common Use Cases
Got questions?
Frequently Asked Questions
Replace the client-side CODES lookup in the submit handler with a fetch to your coupon-validation endpoint, passing the entered code and the cart contents; the server returns whether it's valid plus the discount type and amount. Show a "Checking…" state during the request, then the applied chip on success or the inline error on failure — the rest of the UI stays the same.
Anyone can read the CODES map in your page source, so client-side matching is purely for instant feedback. The authoritative check — does the code exist, is it expired, is it usage-limited or customer-specific, can it stack with other offers — must run server-side when the order is priced and again when it's placed, or the discount can be forged. Keep the client check for UX, never for enforcement.
Track applied as an array instead of a single value, render one chip per code, and sum their discounts in recalc() — but enforce your stacking rules (which combinations are allowed, caps on total discount) server-side. Most stores deliberately disallow stacking, which the single-code model here already enforces.
Give each code optional conditions (minSubtotal, eligibleProductIds) in its data, and check them before applying — if the cart doesn't qualify, show a specific message ("SAVE20 requires a $100 minimum") rather than silently applying a $0 discount. The same conditions must be re-validated server-side at checkout.
In React, hold the applied code and message in useState and derive the total with useMemo; in Vue, use ref()/computed(); in Angular, use a component field with a getter. The discount-type calculation is plain JavaScript, and the apply/remove handlers map directly to event handlers in each framework — swap the CODES lookup for an async validation call.
Promo Code Input — Export as HTML, React, Vue, Angular & Tailwind
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Promo Code Input</title>
<style>
*{box-sizing:border-box;margin:0;padding:0}
body{font-family:system-ui,-apple-system,sans-serif;background:#f1f5f9;min-height:100vh;display:flex;align-items:center;justify-content:center;padding:24px}
.pci-card{background:#fff;border-radius:16px;padding:22px;width:100%;max-width:380px;box-shadow:0 18px 44px rgba(15,23,42,.1)}
.pci-card h3{font-size:16px;font-weight:800;color:#0f172a;margin-bottom:16px}
.pci-line{display:flex;justify-content:space-between;font-size:13.5px;color:#475569;margin-bottom:10px;font-variant-numeric:tabular-nums}
.pci-line[hidden]{display:none}
.pci-discount{color:#16a34a;font-weight:700}
.pci-total{border-top:1.5px solid #e2e8f0;padding-top:13px;margin-top:3px;font-size:16px;font-weight:800;color:#0f172a}
.pci-promo{border-top:1px dashed #e2e8f0;border-bottom:1px dashed #e2e8f0;padding:15px 0;margin:14px 0}
.pci-promo label{display:block;font-size:11.5px;font-weight:700;color:#64748b;text-transform:uppercase;letter-spacing:.03em;margin-bottom:9px}
.pci-form{display:flex;gap:8px}
.pci-form[hidden]{display:none}
.pci-form input{flex:1;border:1.5px solid #e2e8f0;border-radius:9px;padding:10px 12px;font-size:13.5px;font-family:inherit;color:#0f172a;text-transform:uppercase;letter-spacing:.04em;transition:border-color .15s,box-shadow .15s}
.pci-form input:focus{outline:none;border-color:#6366f1;box-shadow:0 0 0 3px rgba(99,102,241,.15)}
.pci-form input.invalid{border-color:#ef4444;box-shadow:0 0 0 3px rgba(239,68,68,.12)}
.pci-form button{background:#0f172a;color:#fff;border:none;border-radius:9px;padding:0 18px;font-size:13px;font-weight:700;cursor:pointer;transition:background .15s,opacity .15s}
.pci-form button:hover{background:#1e293b}
.pci-form button:disabled{opacity:.6;cursor:default}
.pci-applied{display:flex;align-items:center;justify-content:space-between;background:#ecfdf5;border:1px solid #a7f3d0;border-radius:9px;padding:9px 12px}
.pci-applied[hidden]{display:none}
.pci-applied-tag{font-size:12.5px;color:#047857;font-weight:600;display:flex;align-items:center;gap:5px}
.pci-applied-tag::before{content:'✓';font-weight:800}
.pci-applied-tag b{font-weight:800}
.pci-applied button{background:none;border:none;color:#059669;font-size:12px;font-weight:700;cursor:pointer;text-decoration:underline}
.pci-msg{font-size:12px;font-weight:600;margin-top:8px}
.pci-msg.err{color:#dc2626}
.pci-msg.ok{color:#16a34a}
.pci-hint{font-size:11.5px;color:#94a3b8;margin-top:14px;text-align:center}
.pci-hint b{color:#64748b;font-weight:700}
</style>
</head>
<body>
<div class="pci-card">
<h3>Order summary</h3>
<div class="pci-line"><span>Subtotal</span><span id="pciSubtotal">$240.00</span></div>
<div class="pci-line pci-discount" id="pciDiscountLine" hidden><span id="pciDiscountLabel">Discount</span><span id="pciDiscount">−$0.00</span></div>
<div class="pci-line"><span>Shipping</span><span>$8.00</span></div>
<div class="pci-promo">
<label for="pciInput">Promo code</label>
<div class="pci-applied" id="pciApplied" hidden>
<span class="pci-applied-tag"><b id="pciAppliedCode"></b> applied</span>
<button type="button" id="pciRemove" aria-label="Remove code">Remove</button>
</div>
<form class="pci-form" id="pciForm" novalidate>
<input type="text" id="pciInput" placeholder="Enter code" autocomplete="off" spellcheck="false">
<button type="submit" id="pciApply">Apply</button>
</form>
<p class="pci-msg" id="pciMsg" hidden></p>
</div>
<div class="pci-line pci-total"><span>Total</span><span id="pciTotal">$248.00</span></div>
<p class="pci-hint">Try <b>SAVE20</b>, <b>FREESHIP</b>, or <b>HALFOFF</b>.</p>
</div>
<script>
var SUBTOTAL = 240;
var SHIPPING = 8;
var CODES = {
SAVE20: { type: 'percent', value: 20, label: 'SAVE20 (20% off)' },
HALFOFF: { type: 'percent', value: 50, label: 'HALFOFF (50% off)' },
FREESHIP: { type: 'shipping', value: SHIPPING, label: 'FREESHIP (free shipping)' },
TENOFF: { type: 'fixed', value: 10, label: 'TENOFF ($10 off)' },
};
var applied = null;
var form = document.getElementById('pciForm');
var input = document.getElementById('pciInput');
var applyBtn = document.getElementById('pciApply');
var msg = document.getElementById('pciMsg');
function money(n) { return '$' + n.toFixed(2); }
function discountAmount(code) {
var c = CODES[code];
if (c.type === 'percent') return SUBTOTAL * (c.value / 100);
if (c.type === 'fixed') return Math.min(c.value, SUBTOTAL);
if (c.type === 'shipping') return c.value; // offsets shipping
return 0;
}
function recalc() {
var discount = applied ? discountAmount(applied) : 0;
var line = document.getElementById('pciDiscountLine');
if (applied) {
line.hidden = false;
document.getElementById('pciDiscountLabel').textContent = CODES[applied].type === 'shipping' ? 'Free shipping' : 'Discount (' + applied + ')';
document.getElementById('pciDiscount').textContent = '−' + money(discount);
} else {
line.hidden = true;
}
var total = SUBTOTAL + SHIPPING - discount;
document.getElementById('pciTotal').textContent = money(Math.max(0, total));
}
function showMsg(text, ok) {
msg.textContent = text;
msg.className = 'pci-msg ' + (ok ? 'ok' : 'err');
msg.hidden = false;
}
form.addEventListener('submit', function (e) {
e.preventDefault();
var code = input.value.trim().toUpperCase();
input.classList.remove('invalid');
if (!code) { input.classList.add('invalid'); showMsg('Enter a promo code first.', false); return; }
if (applied === code) { showMsg('That code is already applied.', false); return; }
if (!CODES[code]) { input.classList.add('invalid'); showMsg('"' + code + '" is not a valid code.', false); return; }
applied = code;
recalc();
document.getElementById('pciForm').hidden = true;
document.getElementById('pciMsg').hidden = true;
document.getElementById('pciAppliedCode').textContent = code;
document.getElementById('pciApplied').hidden = false;
showMsg(CODES[code].label + ' applied!', true);
});
document.getElementById('pciRemove').addEventListener('click', function () {
applied = null;
recalc();
document.getElementById('pciApplied').hidden = true;
document.getElementById('pciForm').hidden = false;
input.value = '';
msg.hidden = true;
input.focus();
});
document.getElementById('pciSubtotal').textContent = money(SUBTOTAL);
recalc();
</script>
</body>
</html><!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Promo Code Input</title>
<!-- Tailwind CSS v3+ — arbitrary value classes (e.g. bg-[#0f172a]) are valid -->
<script src="https://cdn.tailwindcss.com"></script>
<style>
* {
box-sizing:border-box;margin:0;padding:0
}
body {
font-family:system-ui,-apple-system,sans-serif;background:#f1f5f9;min-height:100vh;display:flex;align-items:center;justify-content:center;padding:24px
}
.pci-card h3 {
font-size:16px;font-weight:800;color:#0f172a;margin-bottom:16px
}
.pci-line[hidden] {
display:none
}
.pci-promo label {
display:block;font-size:11.5px;font-weight:700;color:#64748b;text-transform:uppercase;letter-spacing:.03em;margin-bottom:9px
}
.pci-form[hidden] {
display:none
}
.pci-form input {
flex:1;border:1.5px solid #e2e8f0;border-radius:9px;padding:10px 12px;font-size:13.5px;font-family:inherit;color:#0f172a;text-transform:uppercase;letter-spacing:.04em;transition:border-color .15s,box-shadow .15s
}
.pci-form input:focus {
outline:none;border-color:#6366f1;box-shadow:0 0 0 3px rgba(99,102,241,.15)
}
.pci-form input.invalid {
border-color:#ef4444;box-shadow:0 0 0 3px rgba(239,68,68,.12)
}
.pci-form button {
background:#0f172a;color:#fff;border:none;border-radius:9px;padding:0 18px;font-size:13px;font-weight:700;cursor:pointer;transition:background .15s,opacity .15s
}
.pci-form button:hover {
background:#1e293b
}
.pci-form button:disabled {
opacity:.6;cursor:default
}
.pci-applied[hidden] {
display:none
}
.pci-applied-tag::before {
content:'✓';font-weight:800
}
.pci-applied-tag b {
font-weight:800
}
.pci-applied button {
background:none;border:none;color:#059669;font-size:12px;font-weight:700;cursor:pointer;text-decoration:underline
}
.pci-msg {
font-size:12px;font-weight:600;margin-top:8px
}
.pci-msg.err {
color:#dc2626
}
.pci-msg.ok {
color:#16a34a
}
.pci-hint b {
color:#64748b;font-weight:700
}
</style>
</head>
<body>
<div class="pci-card bg-[#fff] rounded-2xl p-[22px] w-full max-w-[380px] shadow-[0_18px_44px_rgba(15,23,42,.1)]">
<h3>Order summary</h3>
<div class="pci-line flex justify-between text-[13.5px] text-[#475569] mb-2.5 [font-variant-numeric:tabular-nums]"><span>Subtotal</span><span id="pciSubtotal">$240.00</span></div>
<div class="pci-line flex justify-between text-[13.5px] text-[#475569] mb-2.5 [font-variant-numeric:tabular-nums] text-[#16a34a] font-bold" id="pciDiscountLine" hidden><span id="pciDiscountLabel">Discount</span><span id="pciDiscount">−$0.00</span></div>
<div class="pci-line flex justify-between text-[13.5px] text-[#475569] mb-2.5 [font-variant-numeric:tabular-nums]"><span>Shipping</span><span>$8.00</span></div>
<div class="pci-promo border-t border-dashed border-t-[#e2e8f0] border-b border-b-[#e2e8f0] py-[15px] px-0 my-3.5 mx-0">
<label for="pciInput">Promo code</label>
<div class="pci-applied flex items-center justify-between bg-[#ecfdf5] border border-[#a7f3d0] rounded-[9px] py-[9px] px-3" id="pciApplied" hidden>
<span class="pci-applied-tag text-[12.5px] text-[#047857] font-semibold flex items-center gap-[5px]"><b id="pciAppliedCode"></b> applied</span>
<button type="button" id="pciRemove" aria-label="Remove code">Remove</button>
</div>
<form class="pci-form flex gap-2" id="pciForm" novalidate>
<input type="text" id="pciInput" placeholder="Enter code" autocomplete="off" spellcheck="false">
<button type="submit" id="pciApply">Apply</button>
</form>
<p class="pci-msg" id="pciMsg" hidden></p>
</div>
<div class="pci-line flex justify-between text-[13.5px] text-[#475569] mb-2.5 [font-variant-numeric:tabular-nums] border-t-[1.5px] border-t-[#e2e8f0] pt-[13px] mt-[3px] text-base font-extrabold text-[#0f172a]"><span>Total</span><span id="pciTotal">$248.00</span></div>
<p class="pci-hint text-[11.5px] text-[#94a3b8] mt-3.5 text-center">Try <b>SAVE20</b>, <b>FREESHIP</b>, or <b>HALFOFF</b>.</p>
</div>
<script>
var SUBTOTAL = 240;
var SHIPPING = 8;
var CODES = {
SAVE20: { type: 'percent', value: 20, label: 'SAVE20 (20% off)' },
HALFOFF: { type: 'percent', value: 50, label: 'HALFOFF (50% off)' },
FREESHIP: { type: 'shipping', value: SHIPPING, label: 'FREESHIP (free shipping)' },
TENOFF: { type: 'fixed', value: 10, label: 'TENOFF ($10 off)' },
};
var applied = null;
var form = document.getElementById('pciForm');
var input = document.getElementById('pciInput');
var applyBtn = document.getElementById('pciApply');
var msg = document.getElementById('pciMsg');
function money(n) { return '$' + n.toFixed(2); }
function discountAmount(code) {
var c = CODES[code];
if (c.type === 'percent') return SUBTOTAL * (c.value / 100);
if (c.type === 'fixed') return Math.min(c.value, SUBTOTAL);
if (c.type === 'shipping') return c.value; // offsets shipping
return 0;
}
function recalc() {
var discount = applied ? discountAmount(applied) : 0;
var line = document.getElementById('pciDiscountLine');
if (applied) {
line.hidden = false;
document.getElementById('pciDiscountLabel').textContent = CODES[applied].type === 'shipping' ? 'Free shipping' : 'Discount (' + applied + ')';
document.getElementById('pciDiscount').textContent = '−' + money(discount);
} else {
line.hidden = true;
}
var total = SUBTOTAL + SHIPPING - discount;
document.getElementById('pciTotal').textContent = money(Math.max(0, total));
}
function showMsg(text, ok) {
msg.textContent = text;
msg.className = 'pci-msg ' + (ok ? 'ok' : 'err');
msg.hidden = false;
}
form.addEventListener('submit', function (e) {
e.preventDefault();
var code = input.value.trim().toUpperCase();
input.classList.remove('invalid');
if (!code) { input.classList.add('invalid'); showMsg('Enter a promo code first.', false); return; }
if (applied === code) { showMsg('That code is already applied.', false); return; }
if (!CODES[code]) { input.classList.add('invalid'); showMsg('"' + code + '" is not a valid code.', false); return; }
applied = code;
recalc();
document.getElementById('pciForm').hidden = true;
document.getElementById('pciMsg').hidden = true;
document.getElementById('pciAppliedCode').textContent = code;
document.getElementById('pciApplied').hidden = false;
showMsg(CODES[code].label + ' applied!', true);
});
document.getElementById('pciRemove').addEventListener('click', function () {
applied = null;
recalc();
document.getElementById('pciApplied').hidden = true;
document.getElementById('pciForm').hidden = false;
input.value = '';
msg.hidden = true;
input.focus();
});
document.getElementById('pciSubtotal').textContent = money(SUBTOTAL);
recalc();
</script>
</body>
</html>import React, { useEffect } from 'react';
// CSS — optionally move to PromoCodeInput.module.css
const css = `
*{box-sizing:border-box;margin:0;padding:0}
body{font-family:system-ui,-apple-system,sans-serif;background:#f1f5f9;min-height:100vh;display:flex;align-items:center;justify-content:center;padding:24px}
.pci-card{background:#fff;border-radius:16px;padding:22px;width:100%;max-width:380px;box-shadow:0 18px 44px rgba(15,23,42,.1)}
.pci-card h3{font-size:16px;font-weight:800;color:#0f172a;margin-bottom:16px}
.pci-line{display:flex;justify-content:space-between;font-size:13.5px;color:#475569;margin-bottom:10px;font-variant-numeric:tabular-nums}
.pci-line[hidden]{display:none}
.pci-discount{color:#16a34a;font-weight:700}
.pci-total{border-top:1.5px solid #e2e8f0;padding-top:13px;margin-top:3px;font-size:16px;font-weight:800;color:#0f172a}
.pci-promo{border-top:1px dashed #e2e8f0;border-bottom:1px dashed #e2e8f0;padding:15px 0;margin:14px 0}
.pci-promo label{display:block;font-size:11.5px;font-weight:700;color:#64748b;text-transform:uppercase;letter-spacing:.03em;margin-bottom:9px}
.pci-form{display:flex;gap:8px}
.pci-form[hidden]{display:none}
.pci-form input{flex:1;border:1.5px solid #e2e8f0;border-radius:9px;padding:10px 12px;font-size:13.5px;font-family:inherit;color:#0f172a;text-transform:uppercase;letter-spacing:.04em;transition:border-color .15s,box-shadow .15s}
.pci-form input:focus{outline:none;border-color:#6366f1;box-shadow:0 0 0 3px rgba(99,102,241,.15)}
.pci-form input.invalid{border-color:#ef4444;box-shadow:0 0 0 3px rgba(239,68,68,.12)}
.pci-form button{background:#0f172a;color:#fff;border:none;border-radius:9px;padding:0 18px;font-size:13px;font-weight:700;cursor:pointer;transition:background .15s,opacity .15s}
.pci-form button:hover{background:#1e293b}
.pci-form button:disabled{opacity:.6;cursor:default}
.pci-applied{display:flex;align-items:center;justify-content:space-between;background:#ecfdf5;border:1px solid #a7f3d0;border-radius:9px;padding:9px 12px}
.pci-applied[hidden]{display:none}
.pci-applied-tag{font-size:12.5px;color:#047857;font-weight:600;display:flex;align-items:center;gap:5px}
.pci-applied-tag::before{content:'✓';font-weight:800}
.pci-applied-tag b{font-weight:800}
.pci-applied button{background:none;border:none;color:#059669;font-size:12px;font-weight:700;cursor:pointer;text-decoration:underline}
.pci-msg{font-size:12px;font-weight:600;margin-top:8px}
.pci-msg.err{color:#dc2626}
.pci-msg.ok{color:#16a34a}
.pci-hint{font-size:11.5px;color:#94a3b8;margin-top:14px;text-align:center}
.pci-hint b{color:#64748b;font-weight:700}
`;
export default function PromoCodeInput() {
// Auto-generated escape hatch: the original snippet's vanilla JS runs once
// after mount and queries the rendered DOM. For idiomatic React, lift this
// into state + handlers.
useEffect(() => {
const _listeners = [];
const _originalAddEventListener = EventTarget.prototype.addEventListener;
EventTarget.prototype.addEventListener = function(type, listener, options) {
_listeners.push({ target: this, type, listener, options });
return _originalAddEventListener.call(this, type, listener, options);
};
var SUBTOTAL = 240;
var SHIPPING = 8;
var CODES = {
SAVE20: { type: 'percent', value: 20, label: 'SAVE20 (20% off)' },
HALFOFF: { type: 'percent', value: 50, label: 'HALFOFF (50% off)' },
FREESHIP: { type: 'shipping', value: SHIPPING, label: 'FREESHIP (free shipping)' },
TENOFF: { type: 'fixed', value: 10, label: 'TENOFF ($10 off)' },
};
var applied = null;
var form = document.getElementById('pciForm');
var input = document.getElementById('pciInput');
var applyBtn = document.getElementById('pciApply');
var msg = document.getElementById('pciMsg');
function money(n) { return '$' + n.toFixed(2); }
function discountAmount(code) {
var c = CODES[code];
if (c.type === 'percent') return SUBTOTAL * (c.value / 100);
if (c.type === 'fixed') return Math.min(c.value, SUBTOTAL);
if (c.type === 'shipping') return c.value; // offsets shipping
return 0;
}
function recalc() {
var discount = applied ? discountAmount(applied) : 0;
var line = document.getElementById('pciDiscountLine');
if (applied) {
line.hidden = false;
document.getElementById('pciDiscountLabel').textContent = CODES[applied].type === 'shipping' ? 'Free shipping' : 'Discount (' + applied + ')';
document.getElementById('pciDiscount').textContent = '−' + money(discount);
} else {
line.hidden = true;
}
var total = SUBTOTAL + SHIPPING - discount;
document.getElementById('pciTotal').textContent = money(Math.max(0, total));
}
function showMsg(text, ok) {
msg.textContent = text;
msg.className = 'pci-msg ' + (ok ? 'ok' : 'err');
msg.hidden = false;
}
form.addEventListener('submit', function (e) {
e.preventDefault();
var code = input.value.trim().toUpperCase();
input.classList.remove('invalid');
if (!code) { input.classList.add('invalid'); showMsg('Enter a promo code first.', false); return; }
if (applied === code) { showMsg('That code is already applied.', false); return; }
if (!CODES[code]) { input.classList.add('invalid'); showMsg('"' + code + '" is not a valid code.', false); return; }
applied = code;
recalc();
document.getElementById('pciForm').hidden = true;
document.getElementById('pciMsg').hidden = true;
document.getElementById('pciAppliedCode').textContent = code;
document.getElementById('pciApplied').hidden = false;
showMsg(CODES[code].label + ' applied!', true);
});
document.getElementById('pciRemove').addEventListener('click', function () {
applied = null;
recalc();
document.getElementById('pciApplied').hidden = true;
document.getElementById('pciForm').hidden = false;
input.value = '';
msg.hidden = true;
input.focus();
});
document.getElementById('pciSubtotal').textContent = money(SUBTOTAL);
recalc();
// Cleanup: restore addEventListener and remove all listeners
return () => {
EventTarget.prototype.addEventListener = _originalAddEventListener;
for (const { target, type, listener, options } of _listeners) {
target.removeEventListener(type, listener, options);
}
};
}, []);
return (
<>
<style>{css}</style>
<div className="pci-card">
<h3>Order summary</h3>
<div className="pci-line"><span>Subtotal</span><span id="pciSubtotal">$240.00</span></div>
<div className="pci-line pci-discount" id="pciDiscountLine" hidden><span id="pciDiscountLabel">Discount</span><span id="pciDiscount">−$0.00</span></div>
<div className="pci-line"><span>Shipping</span><span>$8.00</span></div>
<div className="pci-promo">
<label htmlFor="pciInput">Promo code</label>
<div className="pci-applied" id="pciApplied" hidden>
<span className="pci-applied-tag"><b id="pciAppliedCode"></b> applied</span>
<button type="button" id="pciRemove" aria-label="Remove code">Remove</button>
</div>
<form className="pci-form" id="pciForm" novalidate>
<input type="text" id="pciInput" placeholder="Enter code" autocomplete="off" spellcheck="false" />
<button type="submit" id="pciApply">Apply</button>
</form>
<p className="pci-msg" id="pciMsg" hidden></p>
</div>
<div className="pci-line pci-total"><span>Total</span><span id="pciTotal">$248.00</span></div>
<p className="pci-hint">Try <b>SAVE20</b>, <b>FREESHIP</b>, or <b>HALFOFF</b>.</p>
</div>
</>
);
}import React, { useEffect } from 'react';
// Requires Tailwind CSS v3+ — https://tailwindcss.com/docs/installation
// Arbitrary value classes (e.g. bg-[#0f172a]) are valid Tailwind v3+
export default function PromoCodeInput() {
// Auto-generated escape hatch: the original snippet's vanilla JS runs once
// after mount and queries the rendered DOM. For idiomatic React, lift this
// into state + handlers.
useEffect(() => {
const _listeners = [];
const _originalAddEventListener = EventTarget.prototype.addEventListener;
EventTarget.prototype.addEventListener = function(type, listener, options) {
_listeners.push({ target: this, type, listener, options });
return _originalAddEventListener.call(this, type, listener, options);
};
var SUBTOTAL = 240;
var SHIPPING = 8;
var CODES = {
SAVE20: { type: 'percent', value: 20, label: 'SAVE20 (20% off)' },
HALFOFF: { type: 'percent', value: 50, label: 'HALFOFF (50% off)' },
FREESHIP: { type: 'shipping', value: SHIPPING, label: 'FREESHIP (free shipping)' },
TENOFF: { type: 'fixed', value: 10, label: 'TENOFF ($10 off)' },
};
var applied = null;
var form = document.getElementById('pciForm');
var input = document.getElementById('pciInput');
var applyBtn = document.getElementById('pciApply');
var msg = document.getElementById('pciMsg');
function money(n) { return '$' + n.toFixed(2); }
function discountAmount(code) {
var c = CODES[code];
if (c.type === 'percent') return SUBTOTAL * (c.value / 100);
if (c.type === 'fixed') return Math.min(c.value, SUBTOTAL);
if (c.type === 'shipping') return c.value; // offsets shipping
return 0;
}
function recalc() {
var discount = applied ? discountAmount(applied) : 0;
var line = document.getElementById('pciDiscountLine');
if (applied) {
line.hidden = false;
document.getElementById('pciDiscountLabel').textContent = CODES[applied].type === 'shipping' ? 'Free shipping' : 'Discount (' + applied + ')';
document.getElementById('pciDiscount').textContent = '−' + money(discount);
} else {
line.hidden = true;
}
var total = SUBTOTAL + SHIPPING - discount;
document.getElementById('pciTotal').textContent = money(Math.max(0, total));
}
function showMsg(text, ok) {
msg.textContent = text;
msg.className = 'pci-msg ' + (ok ? 'ok' : 'err');
msg.hidden = false;
}
form.addEventListener('submit', function (e) {
e.preventDefault();
var code = input.value.trim().toUpperCase();
input.classList.remove('invalid');
if (!code) { input.classList.add('invalid'); showMsg('Enter a promo code first.', false); return; }
if (applied === code) { showMsg('That code is already applied.', false); return; }
if (!CODES[code]) { input.classList.add('invalid'); showMsg('"' + code + '" is not a valid code.', false); return; }
applied = code;
recalc();
document.getElementById('pciForm').hidden = true;
document.getElementById('pciMsg').hidden = true;
document.getElementById('pciAppliedCode').textContent = code;
document.getElementById('pciApplied').hidden = false;
showMsg(CODES[code].label + ' applied!', true);
});
document.getElementById('pciRemove').addEventListener('click', function () {
applied = null;
recalc();
document.getElementById('pciApplied').hidden = true;
document.getElementById('pciForm').hidden = false;
input.value = '';
msg.hidden = true;
input.focus();
});
document.getElementById('pciSubtotal').textContent = money(SUBTOTAL);
recalc();
// Cleanup: restore addEventListener and remove all listeners
return () => {
EventTarget.prototype.addEventListener = _originalAddEventListener;
for (const { target, type, listener, options } of _listeners) {
target.removeEventListener(type, listener, options);
}
};
}, []);
return (
<>
<style>{`
* {
box-sizing:border-box;margin:0;padding:0
}
body {
font-family:system-ui,-apple-system,sans-serif;background:#f1f5f9;min-height:100vh;display:flex;align-items:center;justify-content:center;padding:24px
}
.pci-card h3 {
font-size:16px;font-weight:800;color:#0f172a;margin-bottom:16px
}
.pci-line[hidden] {
display:none
}
.pci-promo label {
display:block;font-size:11.5px;font-weight:700;color:#64748b;text-transform:uppercase;letter-spacing:.03em;margin-bottom:9px
}
.pci-form[hidden] {
display:none
}
.pci-form input {
flex:1;border:1.5px solid #e2e8f0;border-radius:9px;padding:10px 12px;font-size:13.5px;font-family:inherit;color:#0f172a;text-transform:uppercase;letter-spacing:.04em;transition:border-color .15s,box-shadow .15s
}
.pci-form input:focus {
outline:none;border-color:#6366f1;box-shadow:0 0 0 3px rgba(99,102,241,.15)
}
.pci-form input.invalid {
border-color:#ef4444;box-shadow:0 0 0 3px rgba(239,68,68,.12)
}
.pci-form button {
background:#0f172a;color:#fff;border:none;border-radius:9px;padding:0 18px;font-size:13px;font-weight:700;cursor:pointer;transition:background .15s,opacity .15s
}
.pci-form button:hover {
background:#1e293b
}
.pci-form button:disabled {
opacity:.6;cursor:default
}
.pci-applied[hidden] {
display:none
}
.pci-applied-tag::before {
content:'✓';font-weight:800
}
.pci-applied-tag b {
font-weight:800
}
.pci-applied button {
background:none;border:none;color:#059669;font-size:12px;font-weight:700;cursor:pointer;text-decoration:underline
}
.pci-msg {
font-size:12px;font-weight:600;margin-top:8px
}
.pci-msg.err {
color:#dc2626
}
.pci-msg.ok {
color:#16a34a
}
.pci-hint b {
color:#64748b;font-weight:700
}
`}</style>
<div className="pci-card bg-[#fff] rounded-2xl p-[22px] w-full max-w-[380px] shadow-[0_18px_44px_rgba(15,23,42,.1)]">
<h3>Order summary</h3>
<div className="pci-line flex justify-between text-[13.5px] text-[#475569] mb-2.5 [font-variant-numeric:tabular-nums]"><span>Subtotal</span><span id="pciSubtotal">$240.00</span></div>
<div className="pci-line flex justify-between text-[13.5px] text-[#475569] mb-2.5 [font-variant-numeric:tabular-nums] text-[#16a34a] font-bold" id="pciDiscountLine" hidden><span id="pciDiscountLabel">Discount</span><span id="pciDiscount">−$0.00</span></div>
<div className="pci-line flex justify-between text-[13.5px] text-[#475569] mb-2.5 [font-variant-numeric:tabular-nums]"><span>Shipping</span><span>$8.00</span></div>
<div className="pci-promo border-t border-dashed border-t-[#e2e8f0] border-b border-b-[#e2e8f0] py-[15px] px-0 my-3.5 mx-0">
<label htmlFor="pciInput">Promo code</label>
<div className="pci-applied flex items-center justify-between bg-[#ecfdf5] border border-[#a7f3d0] rounded-[9px] py-[9px] px-3" id="pciApplied" hidden>
<span className="pci-applied-tag text-[12.5px] text-[#047857] font-semibold flex items-center gap-[5px]"><b id="pciAppliedCode"></b> applied</span>
<button type="button" id="pciRemove" aria-label="Remove code">Remove</button>
</div>
<form className="pci-form flex gap-2" id="pciForm" novalidate>
<input type="text" id="pciInput" placeholder="Enter code" autocomplete="off" spellcheck="false" />
<button type="submit" id="pciApply">Apply</button>
</form>
<p className="pci-msg" id="pciMsg" hidden></p>
</div>
<div className="pci-line flex justify-between text-[13.5px] text-[#475569] mb-2.5 [font-variant-numeric:tabular-nums] border-t-[1.5px] border-t-[#e2e8f0] pt-[13px] mt-[3px] text-base font-extrabold text-[#0f172a]"><span>Total</span><span id="pciTotal">$248.00</span></div>
<p className="pci-hint text-[11.5px] text-[#94a3b8] mt-3.5 text-center">Try <b>SAVE20</b>, <b>FREESHIP</b>, or <b>HALFOFF</b>.</p>
</div>
</>
);
}<template>
<div class="pci-card">
<h3>Order summary</h3>
<div class="pci-line"><span>Subtotal</span><span id="pciSubtotal">$240.00</span></div>
<div class="pci-line pci-discount" id="pciDiscountLine" hidden><span id="pciDiscountLabel">Discount</span><span id="pciDiscount">−$0.00</span></div>
<div class="pci-line"><span>Shipping</span><span>$8.00</span></div>
<div class="pci-promo">
<label for="pciInput">Promo code</label>
<div class="pci-applied" id="pciApplied" hidden>
<span class="pci-applied-tag"><b id="pciAppliedCode"></b> applied</span>
<button type="button" id="pciRemove" aria-label="Remove code">Remove</button>
</div>
<form class="pci-form" id="pciForm" novalidate>
<input type="text" id="pciInput" placeholder="Enter code" autocomplete="off" spellcheck="false">
<button type="submit" id="pciApply">Apply</button>
</form>
<p class="pci-msg" id="pciMsg" hidden></p>
</div>
<div class="pci-line pci-total"><span>Total</span><span id="pciTotal">$248.00</span></div>
<p class="pci-hint">Try <b>SAVE20</b>, <b>FREESHIP</b>, or <b>HALFOFF</b>.</p>
</div>
</template>
<script setup>
import { onMounted } from 'vue';
var SUBTOTAL = 240;
var SHIPPING = 8;
var CODES = {
SAVE20: { type: 'percent', value: 20, label: 'SAVE20 (20% off)' },
HALFOFF: { type: 'percent', value: 50, label: 'HALFOFF (50% off)' },
FREESHIP: { type: 'shipping', value: SHIPPING, label: 'FREESHIP (free shipping)' },
TENOFF: { type: 'fixed', value: 10, label: 'TENOFF ($10 off)' },
};
var applied = null;
let form;
let input;
let applyBtn;
let msg;
function money(n) { return '$' + n.toFixed(2); }
function discountAmount(code) {
var c = CODES[code];
if (c.type === 'percent') return SUBTOTAL * (c.value / 100);
if (c.type === 'fixed') return Math.min(c.value, SUBTOTAL);
if (c.type === 'shipping') return c.value; // offsets shipping
return 0;
}
function recalc() {
var discount = applied ? discountAmount(applied) : 0;
var line = document.getElementById('pciDiscountLine');
if (applied) {
line.hidden = false;
document.getElementById('pciDiscountLabel').textContent = CODES[applied].type === 'shipping' ? 'Free shipping' : 'Discount (' + applied + ')';
document.getElementById('pciDiscount').textContent = '−' + money(discount);
} else {
line.hidden = true;
}
var total = SUBTOTAL + SHIPPING - discount;
document.getElementById('pciTotal').textContent = money(Math.max(0, total));
}
function showMsg(text, ok) {
msg.textContent = text;
msg.className = 'pci-msg ' + (ok ? 'ok' : 'err');
msg.hidden = false;
}
onMounted(() => {
form = document.getElementById('pciForm');
input = document.getElementById('pciInput');
applyBtn = document.getElementById('pciApply');
msg = document.getElementById('pciMsg');
form.addEventListener('submit', function (e) {
e.preventDefault();
var code = input.value.trim().toUpperCase();
input.classList.remove('invalid');
if (!code) { input.classList.add('invalid'); showMsg('Enter a promo code first.', false); return; }
if (applied === code) { showMsg('That code is already applied.', false); return; }
if (!CODES[code]) { input.classList.add('invalid'); showMsg('"' + code + '" is not a valid code.', false); return; }
applied = code;
recalc();
document.getElementById('pciForm').hidden = true;
document.getElementById('pciMsg').hidden = true;
document.getElementById('pciAppliedCode').textContent = code;
document.getElementById('pciApplied').hidden = false;
showMsg(CODES[code].label + ' applied!', true);
});
document.getElementById('pciRemove').addEventListener('click', function () {
applied = null;
recalc();
document.getElementById('pciApplied').hidden = true;
document.getElementById('pciForm').hidden = false;
input.value = '';
msg.hidden = true;
input.focus();
});
document.getElementById('pciSubtotal').textContent = money(SUBTOTAL);
recalc();
});
</script>
<style scoped>
*{box-sizing:border-box;margin:0;padding:0}
body{font-family:system-ui,-apple-system,sans-serif;background:#f1f5f9;min-height:100vh;display:flex;align-items:center;justify-content:center;padding:24px}
.pci-card{background:#fff;border-radius:16px;padding:22px;width:100%;max-width:380px;box-shadow:0 18px 44px rgba(15,23,42,.1)}
.pci-card h3{font-size:16px;font-weight:800;color:#0f172a;margin-bottom:16px}
.pci-line{display:flex;justify-content:space-between;font-size:13.5px;color:#475569;margin-bottom:10px;font-variant-numeric:tabular-nums}
.pci-line[hidden]{display:none}
.pci-discount{color:#16a34a;font-weight:700}
.pci-total{border-top:1.5px solid #e2e8f0;padding-top:13px;margin-top:3px;font-size:16px;font-weight:800;color:#0f172a}
.pci-promo{border-top:1px dashed #e2e8f0;border-bottom:1px dashed #e2e8f0;padding:15px 0;margin:14px 0}
.pci-promo label{display:block;font-size:11.5px;font-weight:700;color:#64748b;text-transform:uppercase;letter-spacing:.03em;margin-bottom:9px}
.pci-form{display:flex;gap:8px}
.pci-form[hidden]{display:none}
.pci-form input{flex:1;border:1.5px solid #e2e8f0;border-radius:9px;padding:10px 12px;font-size:13.5px;font-family:inherit;color:#0f172a;text-transform:uppercase;letter-spacing:.04em;transition:border-color .15s,box-shadow .15s}
.pci-form input:focus{outline:none;border-color:#6366f1;box-shadow:0 0 0 3px rgba(99,102,241,.15)}
.pci-form input.invalid{border-color:#ef4444;box-shadow:0 0 0 3px rgba(239,68,68,.12)}
.pci-form button{background:#0f172a;color:#fff;border:none;border-radius:9px;padding:0 18px;font-size:13px;font-weight:700;cursor:pointer;transition:background .15s,opacity .15s}
.pci-form button:hover{background:#1e293b}
.pci-form button:disabled{opacity:.6;cursor:default}
.pci-applied{display:flex;align-items:center;justify-content:space-between;background:#ecfdf5;border:1px solid #a7f3d0;border-radius:9px;padding:9px 12px}
.pci-applied[hidden]{display:none}
.pci-applied-tag{font-size:12.5px;color:#047857;font-weight:600;display:flex;align-items:center;gap:5px}
.pci-applied-tag::before{content:'✓';font-weight:800}
.pci-applied-tag b{font-weight:800}
.pci-applied button{background:none;border:none;color:#059669;font-size:12px;font-weight:700;cursor:pointer;text-decoration:underline}
.pci-msg{font-size:12px;font-weight:600;margin-top:8px}
.pci-msg.err{color:#dc2626}
.pci-msg.ok{color:#16a34a}
.pci-hint{font-size:11.5px;color:#94a3b8;margin-top:14px;text-align:center}
.pci-hint b{color:#64748b;font-weight:700}
</style>// @ts-nocheck
// Note: vanilla JS DOM manipulation is preserved as-is inside ngAfterViewInit().
// For idiomatic Angular, replace document.getElementById() with @ViewChild() refs
// and move state into component properties with two-way binding.
import { Component, AfterViewInit, ViewEncapsulation } from '@angular/core';
import { CommonModule } from '@angular/common';
@Component({
selector: 'app-promo-code-input',
standalone: true,
imports: [CommonModule],
encapsulation: ViewEncapsulation.None,
template: `
<div class="pci-card">
<h3>Order summary</h3>
<div class="pci-line"><span>Subtotal</span><span id="pciSubtotal">$240.00</span></div>
<div class="pci-line pci-discount" id="pciDiscountLine" hidden><span id="pciDiscountLabel">Discount</span><span id="pciDiscount">−$0.00</span></div>
<div class="pci-line"><span>Shipping</span><span>$8.00</span></div>
<div class="pci-promo">
<label for="pciInput">Promo code</label>
<div class="pci-applied" id="pciApplied" hidden>
<span class="pci-applied-tag"><b id="pciAppliedCode"></b> applied</span>
<button type="button" id="pciRemove" aria-label="Remove code">Remove</button>
</div>
<form class="pci-form" id="pciForm" novalidate>
<input type="text" id="pciInput" placeholder="Enter code" autocomplete="off" spellcheck="false">
<button type="submit" id="pciApply">Apply</button>
</form>
<p class="pci-msg" id="pciMsg" hidden></p>
</div>
<div class="pci-line pci-total"><span>Total</span><span id="pciTotal">$248.00</span></div>
<p class="pci-hint">Try <b>SAVE20</b>, <b>FREESHIP</b>, or <b>HALFOFF</b>.</p>
</div>
`,
styles: [`
*{box-sizing:border-box;margin:0;padding:0}
body{font-family:system-ui,-apple-system,sans-serif;background:#f1f5f9;min-height:100vh;display:flex;align-items:center;justify-content:center;padding:24px}
.pci-card{background:#fff;border-radius:16px;padding:22px;width:100%;max-width:380px;box-shadow:0 18px 44px rgba(15,23,42,.1)}
.pci-card h3{font-size:16px;font-weight:800;color:#0f172a;margin-bottom:16px}
.pci-line{display:flex;justify-content:space-between;font-size:13.5px;color:#475569;margin-bottom:10px;font-variant-numeric:tabular-nums}
.pci-line[hidden]{display:none}
.pci-discount{color:#16a34a;font-weight:700}
.pci-total{border-top:1.5px solid #e2e8f0;padding-top:13px;margin-top:3px;font-size:16px;font-weight:800;color:#0f172a}
.pci-promo{border-top:1px dashed #e2e8f0;border-bottom:1px dashed #e2e8f0;padding:15px 0;margin:14px 0}
.pci-promo label{display:block;font-size:11.5px;font-weight:700;color:#64748b;text-transform:uppercase;letter-spacing:.03em;margin-bottom:9px}
.pci-form{display:flex;gap:8px}
.pci-form[hidden]{display:none}
.pci-form input{flex:1;border:1.5px solid #e2e8f0;border-radius:9px;padding:10px 12px;font-size:13.5px;font-family:inherit;color:#0f172a;text-transform:uppercase;letter-spacing:.04em;transition:border-color .15s,box-shadow .15s}
.pci-form input:focus{outline:none;border-color:#6366f1;box-shadow:0 0 0 3px rgba(99,102,241,.15)}
.pci-form input.invalid{border-color:#ef4444;box-shadow:0 0 0 3px rgba(239,68,68,.12)}
.pci-form button{background:#0f172a;color:#fff;border:none;border-radius:9px;padding:0 18px;font-size:13px;font-weight:700;cursor:pointer;transition:background .15s,opacity .15s}
.pci-form button:hover{background:#1e293b}
.pci-form button:disabled{opacity:.6;cursor:default}
.pci-applied{display:flex;align-items:center;justify-content:space-between;background:#ecfdf5;border:1px solid #a7f3d0;border-radius:9px;padding:9px 12px}
.pci-applied[hidden]{display:none}
.pci-applied-tag{font-size:12.5px;color:#047857;font-weight:600;display:flex;align-items:center;gap:5px}
.pci-applied-tag::before{content:'✓';font-weight:800}
.pci-applied-tag b{font-weight:800}
.pci-applied button{background:none;border:none;color:#059669;font-size:12px;font-weight:700;cursor:pointer;text-decoration:underline}
.pci-msg{font-size:12px;font-weight:600;margin-top:8px}
.pci-msg.err{color:#dc2626}
.pci-msg.ok{color:#16a34a}
.pci-hint{font-size:11.5px;color:#94a3b8;margin-top:14px;text-align:center}
.pci-hint b{color:#64748b;font-weight:700}
`]
})
export class PromoCodeInputComponent implements AfterViewInit {
ngAfterViewInit(): void {
var SUBTOTAL = 240;
var SHIPPING = 8;
var CODES = {
SAVE20: { type: 'percent', value: 20, label: 'SAVE20 (20% off)' },
HALFOFF: { type: 'percent', value: 50, label: 'HALFOFF (50% off)' },
FREESHIP: { type: 'shipping', value: SHIPPING, label: 'FREESHIP (free shipping)' },
TENOFF: { type: 'fixed', value: 10, label: 'TENOFF ($10 off)' },
};
var applied = null;
var form = document.getElementById('pciForm');
var input = document.getElementById('pciInput');
var applyBtn = document.getElementById('pciApply');
var msg = document.getElementById('pciMsg');
function money(n) { return '$' + n.toFixed(2); }
function discountAmount(code) {
var c = CODES[code];
if (c.type === 'percent') return SUBTOTAL * (c.value / 100);
if (c.type === 'fixed') return Math.min(c.value, SUBTOTAL);
if (c.type === 'shipping') return c.value; // offsets shipping
return 0;
}
function recalc() {
var discount = applied ? discountAmount(applied) : 0;
var line = document.getElementById('pciDiscountLine');
if (applied) {
line.hidden = false;
document.getElementById('pciDiscountLabel').textContent = CODES[applied].type === 'shipping' ? 'Free shipping' : 'Discount (' + applied + ')';
document.getElementById('pciDiscount').textContent = '−' + money(discount);
} else {
line.hidden = true;
}
var total = SUBTOTAL + SHIPPING - discount;
document.getElementById('pciTotal').textContent = money(Math.max(0, total));
}
function showMsg(text, ok) {
msg.textContent = text;
msg.className = 'pci-msg ' + (ok ? 'ok' : 'err');
msg.hidden = false;
}
form.addEventListener('submit', function (e) {
e.preventDefault();
var code = input.value.trim().toUpperCase();
input.classList.remove('invalid');
if (!code) { input.classList.add('invalid'); showMsg('Enter a promo code first.', false); return; }
if (applied === code) { showMsg('That code is already applied.', false); return; }
if (!CODES[code]) { input.classList.add('invalid'); showMsg('"' + code + '" is not a valid code.', false); return; }
applied = code;
recalc();
document.getElementById('pciForm').hidden = true;
document.getElementById('pciMsg').hidden = true;
document.getElementById('pciAppliedCode').textContent = code;
document.getElementById('pciApplied').hidden = false;
showMsg(CODES[code].label + ' applied!', true);
});
document.getElementById('pciRemove').addEventListener('click', function () {
applied = null;
recalc();
document.getElementById('pciApplied').hidden = true;
document.getElementById('pciForm').hidden = false;
input.value = '';
msg.hidden = true;
input.focus();
});
document.getElementById('pciSubtotal').textContent = money(SUBTOTAL);
recalc();
// Bind inline-handler functions to the component so the template can call them
Object.assign(this, { money, discountAmount, recalc, showMsg });
}
}