Simple Checkout with State Errors
by oktaviardi pratama
HTML
<div class="container" id="checkoutForm">
<h1>Checkout</h1>
<form id="orderForm">
<div class="form-group">
<label for="name">Full Name *</label>
<input type="text" id="name" name="name" placeholder="John Doe">
<div class="error-message" id="nameError">Please enter your name</div>
</div>
<div class="form-group">
<label for="phone">Phone Number *</label>
<input type="tel" id="phone" name="phone" placeholder="+1 (555) 123-4567">
<div class="error-message" id="phoneError">Please enter a valid phone number</div>
</div>
<div class="form-group">
<label for="address">Delivery Address *</label>
<textarea id="address" name="address" placeholder="123 Main Street, City, State, ZIP"></textarea>
<div class="error-message" id="addressError">Please enter your delivery address</div>
</div>
<div class="product-section">
<div class="product-title">Product</div>
<div class="product-item">
<div class="product-info">
<div class="product-name">Premium Wireless Headphones</div>
<div class="product-price">$79.99</div>
</div>
<div class="quantity-control">
<button type="button" class="qty-btn" id="decreaseBtn" onclick="updateQuantity(-1)">−</button>
<span class="qty-value" id="quantity">1</span>
<button type="button" class="qty-btn" id="increaseBtn" onclick="updateQuantity(1)">+</button>
</div>
</div>
<div class="total-section">
<div class="total-row">
<span>Total:</span>
...
CSS
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
background-color: #f5f5f5;
padding: 20px;
}
.container {
max-width: 600px;
margin: 0 auto;
background-color: white;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
padding: 30px;
}
h1 {
text-align: center;
color: #333;
margin-bottom: 30px;
}
.form-group {
margin-bottom: 20px;
}
label {
display: block;
font-weight: 600;
color: #555;
margin-bottom: 8px;
font-size: 14px;
}
input[type="text"],
input[type="tel"],
textarea {
width: 100%;
padding: 12px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 14px;
transition: border-color 0.3s;
}
input[type="text"]:focus,
input[type="tel"]:focus,
textarea:focus {
outline: none;
border-color: #4CAF50;
}
input.error,
textarea.error {
border-color: #f44336;
}
textarea {
resize: vertical;
min-height: 80px;
}
.error-message {
color: #f44336;
font-size: 12px;
margin-top: 5px;
display: none;
}
.error-message.show {
display: block;
}
.product-section {
background-color: #f9f9f9;
padding: 20px;
border-radius: 6px;
margin: 30px 0;
}
.product-title {
...
JavaScript
// State management
const state = {
quantity: 1,
pricePerUnit: 79.99,
errors: {
name: '',
phone: '',
address: ''
}
};
// Update quantity
function updateQuantity(change) {
state.quantity += change;
if (state.quantity < 1) {
state.quantity = 1;
}
document.getElementById('quantity').textContent = state.quantity;
const total = (state.quantity * state.pricePerUnit).toFixed(2);
document.getElementById('totalPrice').textContent = '$' + total;
document.getElementById('decreaseBtn').disabled = state.quantity === 1;
}
// Validation functions
function validateName(value) {
return value.trim() ? '' : 'Please enter your name';
}
function validatePhone(value) {
const phoneRegex = /^[\d\s\-\+\(\)]+$/;
return value.trim() && phoneRegex.test(value) && value.replace(/\D/g, '').length >= 10
? ''
: 'Please enter a valid phone number';
}
function validateAddress(value) {
return value.trim() ? '' : 'Please enter your delivery address';
}
// Set error state and update UI
function setError(field, message) {
state.errors[field] = message;
const input = document.getElementById(field);
const errorElement = document.getElementById(field + 'Error');
if (message) {
input.classList.add('error');
errorElement.textContent = message;
errorElement.classList.add('show');
} else {
input.classList.remove('error');
...