Skip to content

Instantly share code, notes, and snippets.

@thinkphp
Last active August 3, 2025 15:59
Show Gist options
  • Save thinkphp/cede6ac8164d7796cbf2920d3f63569b to your computer and use it in GitHub Desktop.
Save thinkphp/cede6ac8164d7796cbf2920d3f63569b to your computer and use it in GitHub Desktop.
shipping-address.html
<!--- https://docs.stripe.com/checkout/embedded/quickstart?lang=php -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Shipping Address</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
padding: 20px;
}
.container {
max-width: 800px;
margin: 0 auto;
background: white;
border-radius: 20px;
box-shadow: 0 20px 40px rgba(0,0,0,0.1);
overflow: hidden;
animation: slideUp 0.6s ease-out;
}
@keyframes slideUp {
from {
opacity: 0;
transform: translateY(30px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.header {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 30px;
text-align: center;
}
.header h1 {
font-size: 2.5rem;
margin-bottom: 10px;
font-weight: 300;
}
.header p {
opacity: 0.9;
font-size: 1.1rem;
}
.form-container {
padding: 40px;
}
.form-row {
display: flex;
gap: 20px;
margin-bottom: 25px;
}
.form-group {
flex: 1;
position: relative;
}
.form-group.full-width {
flex: 100%;
}
label {
display: block;
margin-bottom: 8px;
font-weight: 600;
color: #333;
font-size: 0.95rem;
position: relative;
}
.required::after {
content: '*';
color: #e74c3c;
margin-left: 4px;
}
input, select, textarea {
width: 100%;
padding: 15px;
border: 2px solid #e1e8ed;
border-radius: 12px;
font-size: 1rem;
transition: all 0.3s ease;
background: #fafbfc;
}
input:focus, select:focus, textarea:focus {
outline: none;
border-color: #667eea;
background: white;
box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
transform: translateY(-2px);
}
input.error, select.error {
border-color: #e74c3c;
background: #fdf2f2;
}
.error-message {
color: #e74c3c;
font-size: 0.85rem;
margin-top: 5px;
opacity: 0;
transform: translateY(-10px);
transition: all 0.3s ease;
}
.error-message.show {
opacity: 1;
transform: translateY(0);
}
.checkbox-group {
margin: 30px 0;
padding: 20px;
background: #f8f9fa;
border-radius: 12px;
border: 2px solid #e9ecef;
}
.checkbox-item {
display: flex;
align-items: center;
margin-bottom: 15px;
cursor: pointer;
}
.checkbox-item:last-child {
margin-bottom: 0;
}
.checkbox-item input[type="checkbox"] {
width: 20px;
height: 20px;
margin-right: 12px;
cursor: pointer;
accent-color: #667eea;
}
.checkbox-item label {
margin: 0;
cursor: pointer;
font-weight: normal;
}
.billing-address {
margin-top: 20px;
padding: 20px;
background: #f8f9fa;
border-radius: 12px;
border: 2px solid #e9ecef;
display: none;
animation: fadeIn 0.3s ease;
}
.billing-address.show {
display: block;
}
@keyframes fadeIn {
from { opacity: 0; transform: translateY(10px); }
to { opacity: 1; transform: translateY(0); }
}
.button-group {
display: flex;
gap: 15px;
margin-top: 40px;
}
.btn {
flex: 1;
padding: 18px;
border: none;
border-radius: 12px;
font-size: 1.1rem;
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.btn-primary {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
}
.btn-primary:hover {
transform: translateY(-3px);
box-shadow: 0 10px 25px rgba(102, 126, 234, 0.3);
}
.btn-secondary {
background: #6c757d;
color: white;
}
.btn-secondary:hover {
background: #5a6268;
transform: translateY(-2px);
}
.progress-bar {
height: 4px;
background: #e9ecef;
margin-bottom: 30px;
}
.progress-fill {
height: 100%;
background: linear-gradient(90deg, #667eea, #764ba2);
width: 66%;
transition: width 0.3s ease;
}
@media (max-width: 768px) {
.form-row {
flex-direction: column;
gap: 15px;
}
.container {
margin: 10px;
border-radius: 15px;
}
.form-container {
padding: 25px;
}
.header {
padding: 25px;
}
.header h1 {
font-size: 2rem;
}
.button-group {
flex-direction: column;
}
}
.success-message {
background: #d4edda;
color: #155724;
padding: 15px;
border-radius: 8px;
margin-bottom: 20px;
border: 1px solid #c3e6cb;
display: none;
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>🎨 Shipping Address</h1>
<p>Complete your details for artwork delivery</p>
</div>
<div class="progress-bar">
<div class="progress-fill"></div>
</div>
<div class="form-container">
<div class="success-message" id="successMessage">
✅ Your information has been saved successfully!
</div>
<form id="shippingForm" method="POST" action="process_shipping.php">
<!-- Personal Information -->
<h3 style="margin-bottom: 25px; color: #333; font-size: 1.3rem;">📋 Personal Information</h3>
<div class="form-row">
<div class="form-group">
<label for="firstName" class="required">First Name</label>
<input type="text" id="firstName" name="firstName" required>
<div class="error-message" id="firstNameError"></div>
</div>
<div class="form-group">
<label for="lastName" class="required">Last Name</label>
<input type="text" id="lastName" name="lastName" required>
<div class="error-message" id="lastNameError"></div>
</div>
</div>
<div class="form-row">
<div class="form-group">
<label for="email" class="required">Email Address</label>
<input type="email" id="email" name="email" required>
<div class="error-message" id="emailError"></div>
</div>
<div class="form-group">
<label for="phone" class="required">Phone Number</label>
<input type="tel" id="phone" name="phone" required>
<div class="error-message" id="phoneError"></div>
</div>
</div>
<!-- Shipping Address -->
<h3 style="margin: 40px 0 25px 0; color: #333; font-size: 1.3rem;">🏠 Shipping Address</h3>
<div class="form-row">
<div class="form-group">
<label for="country" class="required">Country</label>
<select id="country" name="country" required>
<option value="">Select Country</option>
<optgroup label="Europe">
<option value="RO">Romania</option>
<option value="DE">Germany</option>
<option value="FR">France</option>
<option value="IT">Italy</option>
<option value="ES">Spain</option>
<option value="GB">United Kingdom</option>
<option value="NL">Netherlands</option>
<option value="BE">Belgium</option>
<option value="AT">Austria</option>
<option value="CH">Switzerland</option>
<option value="SE">Sweden</option>
<option value="NO">Norway</option>
<option value="DK">Denmark</option>
<option value="FI">Finland</option>
<option value="PL">Poland</option>
<option value="CZ">Czech Republic</option>
<option value="HU">Hungary</option>
<option value="BG">Bulgaria</option>
<option value="GR">Greece</option>
<option value="PT">Portugal</option>
<option value="IE">Ireland</option>
<option value="HR">Croatia</option>
<option value="SI">Slovenia</option>
<option value="SK">Slovakia</option>
<option value="EE">Estonia</option>
<option value="LV">Latvia</option>
<option value="LT">Lithuania</option>
<option value="LU">Luxembourg</option>
<option value="MT">Malta</option>
<option value="CY">Cyprus</option>
</optgroup>
<optgroup label="North America">
<option value="US">United States</option>
<option value="CA">Canada</option>
<option value="MX">Mexico</option>
</optgroup>
<optgroup label="Asia">
<option value="JP">Japan</option>
<option value="CN">China</option>
<option value="KR">South Korea</option>
<option value="SG">Singapore</option>
<option value="HK">Hong Kong</option>
<option value="TW">Taiwan</option>
<option value="IN">India</option>
<option value="TH">Thailand</option>
<option value="MY">Malaysia</option>
<option value="PH">Philippines</option>
<option value="ID">Indonesia</option>
<option value="VN">Vietnam</option>
</optgroup>
<optgroup label="Oceania">
<option value="AU">Australia</option>
<option value="NZ">New Zealand</option>
</optgroup>
<optgroup label="South America">
<option value="BR">Brazil</option>
<option value="AR">Argentina</option>
<option value="CL">Chile</option>
<option value="CO">Colombia</option>
<option value="PE">Peru</option>
<option value="UY">Uruguay</option>
</optgroup>
<optgroup label="Africa">
<option value="ZA">South Africa</option>
<option value="EG">Egypt</option>
<option value="MA">Morocco</option>
<option value="NG">Nigeria</option>
<option value="KE">Kenya</option>
</optgroup>
<optgroup label="Middle East">
<option value="AE">United Arab Emirates</option>
<option value="SA">Saudi Arabia</option>
<option value="IL">Israel</option>
<option value="TR">Turkey</option>
<option value="QA">Qatar</option>
<option value="KW">Kuwait</option>
</optgroup>
</select>
<div class="error-message" id="countryError"></div>
</div>
<div class="form-group">
<label for="state" class="required">State/Province/Region</label>
<input type="text" id="state" name="state" required placeholder="e.g. California, Bucharest, Bayern">
<div class="error-message" id="stateError"></div>
</div>
</div>
<div class="form-row">
<div class="form-group">
<label for="city" class="required">City</label>
<input type="text" id="city" name="city" required>
<div class="error-message" id="cityError"></div>
</div>
<div class="form-group">
<label for="postalCode" class="required">Postal Code/ZIP</label>
<input type="text" id="postalCode" name="postalCode" required placeholder="e.g. 010101, 90210, SW1A 1AA">
<div class="error-message" id="postalCodeError"></div>
</div>
</div>
<div class="form-group full-width">
<label for="address" class="required">Street Address</label>
<input type="text" id="address" name="address" required placeholder="e.g. 123 Main Street, Apt 4B">
<div class="error-message" id="addressError"></div>
</div>
<div class="form-group full-width">
<label for="notes">Special Instructions for Artwork Delivery</label>
<textarea id="notes" name="notes" rows="3" placeholder="e.g. Weekdays only, fragile handling required, doorman available..."></textarea>
</div>
<div class="button-group">
<button type="button" class="btn btn-secondary" onclick="goBack()">
← Back to Cart
</button>
<button type="submit" class="btn btn-primary">
Continue to Payment →
</button>
</div>
</form>
</div>
</div>
<script>
// Form validation
const form = document.getElementById('shippingForm');
const fields = ['firstName', 'lastName', 'email', 'phone', 'country', 'state', 'city', 'postalCode', 'address'];
// Validators
const validators = {
firstName: (value) => value.length >= 2 ? null : 'First name must be at least 2 characters',
lastName: (value) => value.length >= 2 ? null : 'Last name must be at least 2 characters',
email: (value) => {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return emailRegex.test(value) ? null : 'Please enter a valid email address';
},
phone: (value) => {
const phoneRegex = /^[0-9+\-\s\(\)]{8,}$/;
return phoneRegex.test(value) ? null : 'Please enter a valid phone number';
},
country: (value) => value ? null : 'Please select a country',
state: (value) => value.length >= 2 ? null : 'Please enter state/province/region',
city: (value) => value.length >= 2 ? null : 'City must be at least 2 characters',
postalCode: (value) => {
// Flexible validation for international postal codes
return value.length >= 3 ? null : 'Please enter a valid postal code';
},
address: (value) => value.length >= 5 ? null : 'Address must be at least 5 characters'
};
// Add event listeners for real-time validation
fields.forEach(fieldName => {
const field = document.getElementById(fieldName);
const errorElement = document.getElementById(fieldName + 'Error');
field.addEventListener('blur', () => validateField(fieldName));
field.addEventListener('input', () => {
if (field.classList.contains('error')) {
validateField(fieldName);
}
});
});
function validateField(fieldName) {
const field = document.getElementById(fieldName);
const errorElement = document.getElementById(fieldName + 'Error');
const error = validators[fieldName] ? validators[fieldName](field.value.trim()) : null;
if (error) {
field.classList.add('error');
errorElement.textContent = error;
errorElement.classList.add('show');
return false;
} else {
field.classList.remove('error');
errorElement.classList.remove('show');
return true;
}
}
// Toggle billing address
// Removed - no longer needed
// Form submission handling
form.addEventListener('submit', function(e) {
e.preventDefault();
let isValid = true;
fields.forEach(fieldName => {
if (!validateField(fieldName)) {
isValid = false;
}
});
if (isValid) {
// Simulate saving data
const successMessage = document.getElementById('successMessage');
successMessage.style.display = 'block';
// Scroll to top
window.scrollTo({top: 0, behavior: 'smooth'});
// After 2 seconds, redirect to payment page
setTimeout(() => {
// Here you could redirect to actual payment processing
console.log('Redirecting to payment processing...');
// window.location.href = 'payment.php';
// For demo, just hide the message
successMessage.style.display = 'none';
}, 2000);
// Here you could send data via AJAX
const formData = new FormData(form);
console.log('Form data:', Object.fromEntries(formData));
} else {
// Scroll to first error field
const firstError = document.querySelector('.error');
if (firstError) {
firstError.scrollIntoView({behavior: 'smooth', block: 'center'});
}
}
});
function goBack() {
// Redirect back to shopping cart
window.history.back();
// or window.location.href = 'cart.php';
}
// Auto-complete for cities based on country
document.getElementById('country').addEventListener('change', function() {
const stateField = document.getElementById('state');
const cityField = document.getElementById('city');
const selectedCountry = this.value;
// Update placeholder for state/province based on country
if (selectedCountry === 'US') {
stateField.placeholder = 'e.g. California, New York, Texas';
} else if (selectedCountry === 'RO') {
stateField.placeholder = 'e.g. Bucharest, Cluj, Timis';
} else if (selectedCountry === 'DE') {
stateField.placeholder = 'e.g. Bayern, Berlin, Hamburg';
} else if (['GB', 'FR', 'IT', 'ES'].includes(selectedCountry)) {
stateField.placeholder = 'e.g. London, Paris, Rome, Madrid';
} else {
stateField.placeholder = 'State/Province/Region';
}
stateField.focus();
});
// Automatic postal code formatting - flexible for all countries
document.getElementById('postalCode').addEventListener('input', function() {
const country = document.getElementById('country').value;
let value = this.value.toUpperCase();
// Keep only alphanumeric characters and spaces for international postal codes
if (country === 'RO') {
// Romania - digits only, 6 characters
value = value.replace(/\D/g, '').substring(0, 6);
} else if (country === 'US') {
// USA - format XXXXX or XXXXX-XXXX
value = value.replace(/[^0-9]/g, '');
if (value.length > 5) {
value = value.substring(0, 5) + '-' + value.substring(5, 9);
}
} else if (country === 'GB') {
// UK - complex format, keep all characters
value = value.replace(/[^A-Z0-9\s]/g, '').substring(0, 8);
} else if (country === 'CA') {
// Canada - format AXA XAX
value = value.replace(/[^A-Z0-9]/g, '');
if (value.length > 3) {
value = value.substring(0, 3) + ' ' + value.substring(3, 6);
}
} else {
// Other countries - general format
value = value.replace(/[^A-Z0-9\s-]/g, '').substring(0, 10);
}
this.value = value;
});
// Automatic phone formatting
document.getElementById('phone').addEventListener('input', function() {
let value = this.value.replace(/\D/g, '');
// Keep international format flexible
this.value = value;
});
// Additional animations
window.addEventListener('load', function() {
const formGroups = document.querySelectorAll('.form-group');
formGroups.forEach((group, index) => {
setTimeout(() => {
group.style.opacity = '0';
group.style.transform = 'translateY(20px)';
group.style.transition = 'all 0.4s ease';
setTimeout(() => {
group.style.opacity = '1';
group.style.transform = 'translateY(0)';
}, 50);
}, index * 50);
});
});
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment