Last active
March 25, 2025 16:29
-
-
Save bryanwoods/a870bb5e5dfc2c996ca1e5024f675b61 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!-- new flow! all client side js with cloudinary widget --> | |
<div class="page-width"> | |
<div class="form-container"> | |
<!-- Dynamic heading container that will change for each step --> | |
<div id="step-heading-container"> | |
<h2 id="form-title">What's your child's name?</h2> | |
<h3 id="form-subtitle">This will be the name of the main character in your story</h3> | |
</div> | |
<!-- Progress bar with integrated checkmark for final step --> | |
<div style="position: relative; margin-bottom: 2rem;"> | |
<table style="width: 100%; border-collapse: collapse;"> | |
<tr> | |
<td id="step1" style="background-color: white; height: 8px; width: 20%;"></td> | |
<td id="step2" style="background-color: rgba(255, 255, 255, 0.3); height: 8px; width: 20%;"></td> | |
<td id="step3" style="background-color: rgba(255, 255, 255, 0.3); height: 8px; width: 20%;"></td> | |
<td id="step4" style="background-color: rgba(255, 255, 255, 0.3); height: 8px; width: 20%;"></td> | |
<td id="step5" style="background-color: rgba(255, 255, 255, 0.3); height: 8px; width: 20%; position: relative;"></td> | |
</tr> | |
</table> | |
<!-- Checkmark positioned at the end of the progress bar --> | |
<div id="final-step-indicator" style="display: none; position: absolute; top: -14px; right: -14px;"> | |
<span style="background-color: #ffffff; color: #3d3085; border-radius: 50%; width: 28px; height: 28px; display: inline-flex; align-items: center; justify-content: center; font-weight: bold; font-size: 16px; box-shadow: 0 2px 4px rgba(0,0,0,0.2);">✓</span> | |
</div> | |
</div> | |
<form id="story-builder-form" class="story-form" enctype="multipart/form-data" action="javascript:void(0);" method="post"> | |
<div class="step-container"> | |
<div class="form-step active" data-step="1"> | |
<div class="form-group"> | |
<label for="child_name">Child's Name</label> | |
<input type="text" id="child_name" name="properties[Child Name]" placeholder="Jesse" data-clarity-unmask="True"> | |
</div> | |
<button type="button" class="continue-button">Continue</button> | |
</div> | |
<div class="form-step" data-step="2"> | |
<div class="form-group"> | |
<label for="age">Age</label> | |
<input type="number" id="age" name="properties[Age]" min="0" max="18" placeholder="2-18" data-clarity-unmask="True"> | |
</div> | |
<div class="button-group"> | |
<button type="button" class="back-button">Back</button> | |
<button type="button" class="continue-button">Continue</button> | |
</div> | |
</div> | |
<div class="form-step" data-step="3"> | |
<div class="form-group"> | |
<label for="hobby_1">Favorite Hobbies</label> | |
<input type="text" id="hobby_1" name="properties[Hobby 1]" placeholder="Drawing, acting, etc." data-clarity-unmask="True"> | |
</div> | |
<div class="button-group"> | |
<button type="button" class="back-button">Back</button> | |
<button type="button" class="continue-button">Continue</button> | |
</div> | |
</div> | |
<div class="form-step" data-step="4"> | |
<div class="form-group"> | |
<label for="favorite_song">Favorite Song</label> | |
<input type="text" id="favorite_song" name="properties[Favorite Song]" placeholder="Song Name - Artist Name" data-clarity-unmask="True"> | |
</div> | |
<div class="button-group"> | |
<button type="button" class="back-button">Back</button> | |
<button type="button" class="continue-button">Continue</button> | |
</div> | |
</div> | |
<div class="form-step" data-step="5"> | |
<div class="form-group"> | |
<label for="child_photo" class="pd-form__label">Photo of the child</label> | |
<input type="hidden" id="child_photo_url" name="properties[Child Photo URL]" data-clarity-unmask="True"> | |
<div id="upload-container"> | |
<button type="button" id="upload_photo_button" class="upload-button">Upload Photo</button> | |
<div id="upload-status" style="display: none;"> | |
<div class="upload-feedback"> | |
<span id="upload-message"></span> | |
<div id="upload-preview" style="display: none;"> | |
<img id="preview-image" src="" alt="Preview" style="max-width: 100%; max-height: 200px; margin-top: 10px;"> | |
</div> | |
</div> | |
</div> | |
</div> | |
<p class="hint-text">No photo right now? No problem! You can upload it later.</p> | |
</div> | |
<div class="button-group"> | |
<button type="button" class="back-button">Back</button> | |
<button type="button" id="add-to-cart-button" class="add-to-cart-button">Continue</button> | |
</div> | |
</div> | |
</div> | |
</form> | |
</div> | |
</div> | |
<style> | |
.continue-button, .back-button, .submit-button, .add-to-cart-button, .upload-button { | |
background-color: #ffffff; | |
color: black; | |
padding: 0.75rem 1.5rem; | |
border: 1px solid #000; | |
border-radius: 4px; | |
cursor: pointer; | |
font-weight: 500; | |
transition: all 0.2s ease; | |
} | |
.continue-button:hover, .back-button:hover, .submit-button:hover, .add-to-cart-button:hover, .upload-button:hover { | |
background-color: #f0f0f0; | |
transform: translateY(-1px); | |
box-shadow: 0 2px 4px rgba(0,0,0,0.1); | |
} | |
.continue-button:disabled, .back-button:disabled, .add-to-cart-button:disabled, .upload-button:disabled { | |
opacity: 0.7; | |
cursor: not-allowed; | |
background-color: #e0e0e0; | |
transform: none; | |
box-shadow: none; | |
} | |
.form-container { | |
max-width: 600px; | |
margin: 0 auto; | |
padding: 2rem; | |
} | |
#step-heading-container { | |
margin-bottom: 2.5rem; | |
text-align: center; | |
} | |
#form-title { | |
margin-bottom: 0.75rem; | |
font-size: 2.8rem; | |
line-height: 1.2; | |
} | |
#form-subtitle { | |
font-weight: normal; | |
font-size: 1.5rem; | |
line-height: 1.5; | |
margin-top: 0; | |
opacity: 0.85; | |
letter-spacing: 0.01em; | |
max-width: 90%; | |
margin-left: auto; | |
margin-right: auto; | |
} | |
/* Special styling for final step */ | |
.final-step-glow { | |
box-shadow: 0 0 8px 2px rgba(255, 255, 255, 0.7); | |
animation: pulse 2s infinite ease-in-out; | |
} | |
@keyframes pulse { | |
0% { box-shadow: 0 0 8px 2px rgba(255, 255, 255, 0.7); } | |
50% { box-shadow: 0 0 12px 4px rgba(255, 255, 255, 0.9); } | |
100% { box-shadow: 0 0 8px 2px rgba(255, 255, 255, 0.7); } | |
} | |
.story-form { | |
display: flex; | |
flex-direction: column; | |
} | |
.step-container { | |
position: relative; | |
overflow: hidden; | |
min-height: 200px; | |
} | |
.form-step { | |
display: none; | |
opacity: 0; | |
transform: translateY(20px); | |
transition: opacity 0.4s ease, transform 0.4s ease; | |
position: absolute; | |
width: 100%; | |
} | |
.form-step.active { | |
display: block; | |
opacity: 1; | |
transform: translateY(0); | |
position: relative; | |
} | |
.form-group { | |
display: flex; | |
flex-direction: column; | |
gap: 0.5rem; | |
margin-bottom: 1.5rem; | |
} | |
.form-group label { | |
font-weight: 500; | |
} | |
.form-group input { | |
padding: 0.75rem; | |
border: 1px solid #ddd; | |
border-radius: 4px; | |
font-size: 16px; | |
} | |
.button-group { | |
display: flex; | |
justify-content: space-between; | |
gap: 1rem; | |
} | |
.continue-button, .back-button, .submit-button, .final-button, .upload-button { | |
background-color: #ffffff; | |
color: black; | |
padding: 0.75rem 1.5rem; | |
border: 1px solid #000; | |
border-radius: 4px; | |
cursor: pointer; | |
font-weight: 500; | |
transition: all 0.2s ease; | |
} | |
.continue-button:hover, .back-button:hover, .submit-button:hover, .final-button:hover, .upload-button:hover { | |
background-color: #f0f0f0; | |
transform: translateY(-1px); | |
box-shadow: 0 2px 4px rgba(0,0,0,0.1); | |
} | |
.continue-button:disabled, .back-button:disabled, .final-button:disabled, .upload-button:disabled { | |
opacity: 0.7; | |
cursor: not-allowed; | |
background-color: #e0e0e0; | |
transform: none; | |
box-shadow: none; | |
} | |
.upload-button { | |
width: 100%; | |
margin-bottom: 10px; | |
} | |
.hint-text { | |
font-size: 14px; | |
color: #666; | |
margin-top: 5px; | |
} | |
.upload-feedback { | |
margin-top: 10px; | |
padding: 10px; | |
border-radius: 4px; | |
} | |
/* Feedback message styling */ | |
.form-feedback-message { | |
margin: 10px 0; | |
padding: 10px; | |
border-radius: 4px; | |
font-size: 14px; | |
font-weight: 500; | |
text-align: center; | |
} | |
.error-message { | |
background-color: #ffebee; | |
color: #c62828; | |
border: 1px solid #ef9a9a; | |
} | |
.success-message { | |
background-color: #e8f5e9; | |
color: #2e7d32; | |
border: 1px solid #a5d6a7; | |
} | |
/* Better visual feedback for processing state */ | |
.continue-button.processing, .add-to-cart-button.processing, .upload-button.processing { | |
position: relative; | |
padding-left: 2.5rem; | |
} | |
.continue-button.processing:before, .add-to-cart-button.processing:before, .upload-button.processing:before { | |
content: ""; | |
position: absolute; | |
left: 12px; | |
top: 50%; | |
width: 18px; | |
height: 18px; | |
margin-top: -9px; | |
border: 2px solid rgba(0, 0, 0, 0.2); | |
border-top-color: #000; | |
border-radius: 50%; | |
animation: spinner 0.8s linear infinite; | |
} | |
@keyframes spinner { | |
to {transform: rotate(360deg);} | |
} | |
/* Focus states for better accessibility */ | |
.continue-button:focus, .back-button:focus, .final-button:focus, input:focus, .upload-button:focus { | |
outline: 2px solid #3d3085; | |
outline-offset: 2px; | |
} | |
.input-error { | |
border-color: #c62828 !important; | |
background-color: #ffebee !important; | |
} | |
/* Responsive adjustments */ | |
@media (max-width: 768px) { | |
#form-title { | |
font-size: 2.2rem; | |
} | |
#form-subtitle { | |
font-size: 1.3rem; | |
} | |
/* Make clickable areas larger on mobile */ | |
.continue-button, .back-button, .final-button, .upload-button { | |
padding: 12px 24px; | |
font-size: 16px; | |
} | |
input { | |
padding: 12px; | |
font-size: 16px; | |
} | |
} | |
</style> | |
<!-- Include Cloudinary Upload Widget script --> | |
<script src="https://upload-widget.cloudinary.com/global/all.js"></script> | |
<script> | |
document.addEventListener("DOMContentLoaded", () => { | |
const form = document.querySelector("form[novalidate]"); | |
form && form.removeAttribute("novalidate"); | |
const steps = document.querySelectorAll('.form-step'); | |
const totalSteps = steps.length; | |
let currentStep = 1; | |
const stepPaths = [ | |
'childs-name', | |
'childs-age', | |
'favorite-hobby-1', | |
'favorite-song', | |
'childs-photo' | |
]; | |
// Define step-specific headings and subheadings | |
const stepHeadings = { | |
1: { | |
title: "Tell us about the hero of your story!", | |
subtitle: "Enter the child's first name and we'll make them the main character." | |
}, | |
2: { | |
title: "How old is the child?", | |
subtitle: "We'll tailor the adventure to match their age for the perfect fit!" | |
}, | |
3: { | |
title: "What's their favorite hobby?", | |
subtitle: "Their favorite hobby becomes part of the story—making it even more special!" | |
}, | |
4: { | |
title: "What's your child's favorite song?", | |
subtitle: "A song they love makes their adventure even more personal!" | |
}, | |
5: { | |
title: "Upload a photo", | |
subtitle: "No photo right now? No problem! You can upload it later. We use their photo to create a character that looks just like them!" | |
} | |
}; | |
// Set up Cloudinary widget | |
const cloudinaryWidget = cloudinary.createUploadWidget( | |
{ | |
cloudName: 'hsloxib24', | |
uploadPreset: 'customer_photos', // Your Cloudinary collection name | |
sources: ['local', 'camera', 'url'], | |
multiple: false, | |
maxFiles: 1, | |
resourceType: 'image', | |
styles: { | |
palette: { | |
window: "#FFFFFF", | |
windowBorder: "#90A0B3", | |
tabIcon: "#0078FF", | |
menuIcons: "#5A616A", | |
textDark: "#000000", | |
textLight: "#FFFFFF", | |
link: "#0078FF", | |
action: "#FF620C", | |
inactiveTabIcon: "#0E2F5A", | |
error: "#F44235", | |
inProgress: "#0078FF", | |
complete: "#20B832", | |
sourceBg: "#E4EBF1" | |
} | |
} | |
}, | |
(error, result) => { | |
const uploadButton = document.getElementById('upload_photo_button'); | |
const uploadStatus = document.getElementById('upload-status'); | |
const uploadMessage = document.getElementById('upload-message'); | |
const uploadPreview = document.getElementById('upload-preview'); | |
const previewImage = document.getElementById('preview-image'); | |
const photoUrlInput = document.getElementById('child_photo_url'); | |
if (error) { | |
// Handle error | |
uploadStatus.style.display = 'block'; | |
uploadMessage.textContent = 'Upload failed: ' + error.message; | |
uploadMessage.style.color = '#c62828'; | |
// Re-enable upload button | |
uploadButton.disabled = false; | |
uploadButton.textContent = 'Try Again'; | |
uploadButton.classList.remove('processing'); | |
console.error('Cloudinary upload error:', error); | |
return; | |
} | |
if (result.event === 'success') { | |
// Handle successful upload | |
const secureUrl = result.info.secure_url; | |
// Store URL in hidden input | |
photoUrlInput.value = secureUrl; | |
// Update UI to show success | |
uploadStatus.style.display = 'block'; | |
uploadMessage.textContent = 'Photo uploaded successfully!'; | |
uploadMessage.style.color = '#2e7d32'; | |
// Show preview | |
previewImage.src = secureUrl; | |
uploadPreview.style.display = 'block'; | |
// Change button text | |
uploadButton.textContent = 'Change Photo'; | |
uploadButton.disabled = false; | |
uploadButton.classList.remove('processing'); | |
console.log('Photo uploaded:', secureUrl); | |
} else if (result.event === 'abort') { | |
// Handle upload cancellation | |
uploadButton.textContent = 'Upload Photo'; | |
uploadButton.disabled = false; | |
uploadButton.classList.remove('processing'); | |
} else if (result.event === 'start') { | |
// Set button to processing state | |
uploadButton.textContent = 'Uploading...'; | |
uploadButton.disabled = true; | |
uploadButton.classList.add('processing'); | |
} | |
} | |
); | |
// Connect upload button to Cloudinary widget | |
document.getElementById('upload_photo_button').addEventListener('click', () => { | |
cloudinaryWidget.open(); | |
}); | |
// Adding visual feedback for submission state | |
function addFeedbackMessage(message, isError = false) { | |
// Remove any existing feedback messages | |
const existingMessages = document.querySelectorAll('.form-feedback-message'); | |
existingMessages.forEach(el => el.remove()); | |
// Create and insert feedback message | |
const messageElement = document.createElement('div'); | |
messageElement.className = `form-feedback-message ${isError ? 'error-message' : 'success-message'}`; | |
messageElement.textContent = message; | |
// Insert after the active step | |
const activeStep = document.querySelector('.form-step.active'); | |
if (activeStep) { | |
// Insert inside the button group or after the input | |
const buttonGroup = activeStep.querySelector('.button-group'); | |
if (buttonGroup) { | |
buttonGroup.insertAdjacentElement('beforebegin', messageElement); | |
} else { | |
const input = activeStep.querySelector('input'); | |
if (input) { | |
input.insertAdjacentElement('afterend', messageElement); | |
} | |
} | |
} | |
} | |
// Function to update the heading and subheading | |
function updateHeadings(step) { | |
const headingInfo = stepHeadings[step]; | |
document.getElementById('form-title').textContent = headingInfo.title; | |
document.getElementById('form-subtitle').textContent = headingInfo.subtitle; | |
} | |
// Enhanced progress update function with special final step treatment | |
function updateProgressBar(step) { | |
// First reset all steps to inactive | |
for (let i = 1; i <= 5; i++) { | |
const stepElement = document.getElementById('step' + i); | |
stepElement.style.backgroundColor = 'rgba(255, 255, 255, 0.3)'; | |
stepElement.classList.remove('final-step-glow'); | |
} | |
// Then set completed steps to active | |
for (let i = 1; i <= step; i++) { | |
document.getElementById('step' + i).style.backgroundColor = 'white'; | |
} | |
// Special handling for final step | |
if (step === 5) { | |
// Add glowing effect to the final step | |
const finalStep = document.getElementById('step5'); | |
finalStep.classList.add('final-step-glow'); | |
// Show the checkmark indicator | |
document.getElementById('final-step-indicator').style.display = 'block'; | |
} else { | |
// Hide the checkmark for non-final steps | |
document.getElementById('final-step-indicator').style.display = 'none'; | |
} | |
// Update headings for this step | |
updateHeadings(step); | |
} | |
function updateURL() { | |
const childName = document.getElementById('child_name').value; | |
const age = document.getElementById('age').value; | |
const hobby1 = document.getElementById('hobby_1').value; | |
const favoriteSong = document.getElementById('favorite_song').value; | |
let params = new URLSearchParams(window.location.search); | |
if (currentStep > 1 && childName) params.set('childsName', childName); | |
if (currentStep > 2 && age) params.set('childsAge', age); | |
if (currentStep > 3 && hobby1) params.set('hobby1', hobby1); | |
if (currentStep > 4 && favoriteSong) params.set('favoriteSong', favoriteSong); | |
const urlParams = new URLSearchParams(window.location.search); | |
const variantId = urlParams.get('variant'); | |
if (variantId) params.set('variant', variantId); | |
const basePath = '/pages/create-their-story/'; | |
const newPath = basePath + stepPaths[currentStep - 1]; | |
const newURL = newPath + '?' + params.toString(); | |
window.history.pushState({ step: currentStep }, '', newURL); | |
} | |
// Update the nextStep function with improved error handling and feedback | |
function nextStep() { | |
if (currentStep >= 5) { | |
return; | |
} | |
// Get the current step element and continue button | |
const currentStepElement = document.querySelector(`.form-step[data-step="${currentStep}"]`); | |
const continueButton = currentStepElement.querySelector('.continue-button'); | |
// Remove any existing feedback messages | |
const existingMessages = currentStepElement.querySelectorAll('.form-feedback-message'); | |
existingMessages.forEach(el => el.remove()); | |
// Validate input field if present | |
const input = currentStepElement.querySelector('input'); | |
if (input && input.required && !input.value.trim()) { | |
addFeedbackMessage(`Please enter your ${input.placeholder || 'information'}`, true); | |
input.focus(); | |
return; | |
} | |
// Disable the button to prevent multiple submissions | |
if (continueButton) { | |
continueButton.disabled = true; | |
continueButton.textContent = "Processing..."; | |
continueButton.classList.add('processing'); | |
} | |
// Proceed with transition | |
setTimeout(() => { | |
currentStepElement.classList.remove('active'); | |
currentStep++; | |
const nextStepElement = document.querySelector(`.form-step[data-step="${currentStep}"]`); | |
nextStepElement.classList.add('active'); | |
updateProgressBar(currentStep); | |
updateURL(); | |
// Focus on the input field in the new step | |
const newInput = nextStepElement.querySelector('input'); | |
if (newInput) { | |
newInput.focus(); | |
} | |
// Re-enable the previous button for back navigation | |
if (continueButton) { | |
continueButton.disabled = false; | |
continueButton.textContent = "Continue"; | |
continueButton.classList.remove('processing'); | |
} | |
}, 200); | |
} | |
function prevStep() { | |
// Remove any error messages when going back | |
const currentStepElement = document.querySelector(`.form-step[data-step="${currentStep}"]`); | |
const existingMessages = currentStepElement.querySelectorAll('.form-feedback-message'); | |
existingMessages.forEach(el => el.remove()); | |
currentStepElement.classList.remove('active'); | |
currentStep--; | |
const prevStepElement = document.querySelector(`.form-step[data-step="${currentStep}"]`); | |
setTimeout(() => { | |
prevStepElement.classList.add('active'); | |
updateProgressBar(currentStep); | |
updateURL(); | |
// Focus on the input field in the previous step | |
const prevInput = prevStepElement.querySelector('input'); | |
if (prevInput) { | |
prevInput.focus(); | |
} | |
// Make sure the continue button in this step is enabled | |
const prevContinueButton = prevStepElement.querySelector('.continue-button'); | |
if (prevContinueButton) { | |
prevContinueButton.disabled = false; | |
prevContinueButton.textContent = "Continue"; | |
prevContinueButton.classList.remove('processing'); | |
} | |
}, 200); | |
} | |
function addToCart() { | |
// Get the add to cart button | |
const addToCartBtn = document.getElementById('add-to-cart-button'); | |
// Clear any existing feedback | |
const existingMessages = document.querySelectorAll('.form-feedback-message'); | |
existingMessages.forEach(el => el.remove()); | |
// Disable the button to prevent multiple submissions | |
if (addToCartBtn) { | |
addToCartBtn.disabled = true; | |
addToCartBtn.textContent = "Adding to Cart..."; | |
addToCartBtn.classList.add('processing'); | |
} | |
// Add a processing message | |
addFeedbackMessage('Adding to cart...'); | |
// Get form values | |
const childName = document.getElementById('child_name').value; | |
const age = document.getElementById('age').value; | |
const hobby1 = document.getElementById('hobby_1').value; | |
const favoriteSong = document.getElementById('favorite_song').value; | |
const childPhotoUrl = document.getElementById('child_photo_url').value; | |
// Get the variant ID from URL | |
const urlParams = new URLSearchParams(window.location.search); | |
const variantId = urlParams.get('variant'); | |
if (!variantId) { | |
addFeedbackMessage('Missing product information. Please try again from the product page.', true); | |
// Re-enable the button | |
if (addToCartBtn) { | |
addToCartBtn.disabled = false; | |
addToCartBtn.textContent = "Add to Cart"; | |
addToCartBtn.classList.remove('processing'); | |
} | |
return; | |
} | |
// Create line item properties (these become attached to the specific product) | |
const properties = {}; | |
if (childName) properties['Child Name'] = childName; | |
if (age) properties['Child Age'] = age; | |
if (hobby1) properties['Hobby 1'] = hobby1; | |
if (favoriteSong) properties['Favorite Song'] = favoriteSong; | |
if (childPhotoUrl) properties['Child Photo URL'] = childPhotoUrl; | |
console.log('Adding item to cart with properties:', properties); | |
// Set up a timeout to detect slow responses | |
let processingTimeout = setTimeout(() => { | |
addFeedbackMessage('This is taking longer than expected. Please wait a moment...', false); | |
}, 5000); | |
// Similar to the gist implementation - use fetch to add to cart | |
fetch('/cart/add.js', { | |
method: 'POST', | |
headers: { | |
'Content-Type': 'application/json', | |
}, | |
body: JSON.stringify({ | |
id: variantId, | |
quantity: 1, | |
properties: properties | |
}) | |
}) | |
.then(response => { | |
if (!response.ok) { | |
return response.json().then(error => { | |
throw new Error(error.description || 'Failed to add item to cart'); | |
}); | |
} | |
return response.json(); | |
}) | |
.then(item => { | |
// Clear the timeout as we've successfully processed | |
clearTimeout(processingTimeout); | |
// Display success message briefly before redirect | |
addFeedbackMessage('Successfully added to cart!'); | |
console.log('Item added to cart:', item); | |
// Redirect to cart page (standard Shopify flow) | |
setTimeout(() => { | |
window.location.href = '/cart'; | |
}, 500); | |
}) | |
.catch(error => { | |
// Clear the timeout | |
clearTimeout(processingTimeout); | |
// Log the error | |
console.error('Add to cart error:', error); | |
// Show user-friendly error message | |
addFeedbackMessage(`Something went wrong: ${error.message}. Please try again.`, true); | |
// Re-enable the button | |
if (addToCartBtn) { | |
addToCartBtn.disabled = false; | |
addToCartBtn.textContent = "Add to Cart"; | |
addToCartBtn.classList.remove('processing'); | |
} | |
}); | |
} | |
// Add event listeners for continue and back buttons | |
document.querySelectorAll('.continue-button').forEach(button => { | |
button.addEventListener('click', nextStep); | |
}); | |
document.querySelector('#add-to-cart-button').addEventListener('click', addToCart); | |
document.querySelectorAll('.back-button').forEach(button => { | |
button.addEventListener('click', prevStep); | |
}); | |
// Add improved keyboard support | |
document.addEventListener('keydown', function(event) { | |
if (event.key === 'Enter') { | |
// Prevent default form submission | |
event.preventDefault(); | |
const activeStep = document.querySelector('.form-step.active'); | |
const stepNumber = parseInt(activeStep.getAttribute('data-step')); | |
// Check if buttons are already in processing state | |
const continueButton = activeStep.querySelector('.continue-button'); | |
if (stepNumber === totalSteps) { | |
const addToCartBtn = activeStep.querySelector('#add-to-cart-button'); | |
if (addToCartBtn && !addToCartBtn.disabled && !addToCartBtn.classList.contains('processing')) { | |
addToCart(); | |
} | |
} else { | |
if (continueButton && !continueButton.disabled && !continueButton.classList.contains('processing')) { | |
nextStep(); | |
} | |
} | |
} | |
}); | |
// Handle network connectivity issues | |
window.addEventListener('online', function() { | |
const processingButtons = document.querySelectorAll('.processing'); | |
if (processingButtons.length > 0) { | |
addFeedbackMessage('Your internet connection is back. Please try again.', false); | |
// Re-enable any processing buttons | |
processingButtons.forEach(button => { | |
button.disabled = false; | |
button.textContent = button.classList.contains('final-button') ? "Continue to Checkout" : "Continue"; | |
button.classList.remove('processing'); | |
}); | |
} | |
}); | |
window.addEventListener('offline', function() { | |
addFeedbackMessage('You appear to be offline. Please check your internet connection.', true); | |
}); | |
// Focus on the first input when the page loads | |
const firstInput = document.querySelector('.form-step.active input'); | |
if (firstInput) { | |
firstInput.focus(); | |
} | |
// Set initial progress bar state and headings | |
updateProgressBar(1); | |
updateURL(); | |
// Helper function to show input validation errors | |
function showInputError(input, message) { | |
// Remove any existing feedback messages | |
const existingMessages = document.querySelectorAll('.form-feedback-message'); | |
existingMessages.forEach(el => el.remove()); | |
// Add error class to input | |
input.classList.add('input-error'); | |
// Add error message if provided | |
if (message) { | |
addFeedbackMessage(message, true); | |
} | |
// Focus on the input | |
input.focus(); | |
} | |
function clearInputError(input) { | |
// Remove error class | |
input.classList.remove('input-error'); | |
// Remove any existing feedback messages | |
const existingMessages = document.querySelectorAll('.form-feedback-message'); | |
existingMessages.forEach(el => el.remove()); | |
} | |
// Add event listeners to clear validation errors on input | |
document.querySelectorAll('input').forEach(input => { | |
input.addEventListener('input', () => { | |
clearInputError(input); | |
}); | |
}); | |
}); | |
// Check for variant ID in URL params | |
const urlParams = new URLSearchParams(window.location.search); | |
if (!urlParams.get('variant')) { | |
console.error('No variant ID provided in URL'); | |
} | |
</script> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment