c.classList.remove('selected'));
const cardEl = document.querySelector('.product-card[data-product="' + productType + '"]');
if (cardEl) cardEl.classList.add('selected');
document.querySelectorAll('.select-btn[data-select]').forEach(btn => {
btn.classList.remove('selected');
btn.textContent = btn.getAttribute('data-select') === productType ? '✓ Selected' : 'Select This Bracelet';
});
buttonEl.classList.add('selected');
buttonEl.textContent = '✓ Selected';
document.getElementById('cart-summary').classList.add('show');
document.getElementById('checkout-btn').disabled = false;
document.getElementById('product-name').textContent = products[productType].name;
document.getElementById('selected-product-name').textContent = products[productType].name;
braceletPrice = 0;
selectedSize = null;
document.querySelectorAll('.size-option').forEach(el => el.classList.remove('selected'));
updateCartSummary();
updateOrderSummary();
syncPaymentRequestTotals();
}
function initSizeSelection() {
document.addEventListener('click', function (e) {
const option = e.target.closest('.size-option');
if (!option) return;
document.querySelectorAll('.size-option').forEach(el => el.classList.remove('selected'));
option.classList.add('selected');
selectedSize = option.getAttribute('data-size');
braceletPrice = parseFloat(option.getAttribute('data-price')) || 0;
updateCartSummary();
updateOrderSummary();
syncPaymentRequestTotals();
});
document.querySelectorAll('.customization-input').forEach(function (input) {
input.addEventListener('input', function () {
updateOrderSummary();
syncPaymentRequestTotals();
});
input.addEventListener('change', function () {
updateOrderSummary();
syncPaymentRequestTotals();
});
});
}
function initShippingListeners() {
const shippingSelect = document.getElementById('shipping-option');
shippingSelect.addEventListener('change', function () {
const selectedOption = shippingSelect.options[shippingSelect.selectedIndex];
shippingPrice = parseFloat(selectedOption.dataset.price || '0');
updateCartSummary();
updateOrderSummary();
syncPaymentRequestTotals();
});
document.getElementById('card-button').addEventListener('click', handleCardPayment);
document.getElementById('dashboard-button').addEventListener('click', function () {
window.open(BACKEND_URL + '/dashboard', '_blank');
});
}
function goToStep(step) {
if (step > currentStep) {
if (currentStep === 1 && !selectedProduct) {
alert('Please select a bracelet first.');
return;
}
if (currentStep === 2 && !validateCustomization()) {
return;
}
if (currentStep === 3 && !validateShippingForm()) {
return;
}
}
document.querySelectorAll('.flow-step').forEach(el => el.classList.remove('active'));
const target = document.getElementById('step-' + step);
if (target) target.classList.add('active');
document.querySelectorAll('.flow-dot').forEach((dot, idx) => {
dot.classList.toggle('active', idx < step);
});
currentStep = step;
if (step === 2 && selectedProduct) {
showCustomization(selectedProduct);
updateOrderSummary();
syncPaymentRequestTotals();
}
if (step === 4) {
updateOrderSummary();
syncPaymentRequestTotals();
}
}
function showCustomization(productType) {
// Only switch which customization block is visible.
document.querySelectorAll('.bracelet-customization').forEach(el => {
el.style.display = 'none';
});
const id = products[productType].customization + '-customization';
const block = document.getElementById(id);
if (block) block.style.display = 'block';
// Do NOT reset selectedSize/braceletPrice here; we handle resets when changing products.
updateCartSummary();
syncPaymentRequestTotals();
}
function validateCustomization() {
if (!selectedSize) {
alert('Please select a size.');
return false;
}
if (selectedProduct === 'birthstone') {
const val = document.getElementById('birthstone-input').value.trim();
if (!val) {
alert('Please enter your birthstone month.');
return false;
}
}
if (selectedProduct === 'custom') {
const feature = document.getElementById('custom-feature').value.trim();
if (!feature) {
alert('Please describe what you want your bracelet to feature.');
return false;
}
}
return true;
}
function validateShippingForm() {
const requiredIds = ['shipping-option', 'name', 'email', 'address1', 'city', 'state', 'postal', 'country'];
for (const id of requiredIds) {
const el = document.getElementById(id);
if (!el || !el.value.trim()) {
alert('Please fill in ' + (el.placeholder || id) + '.');
if (el) el.focus();
return false;
}
}
const emailVal = document.getElementById('email').value.trim();
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailRegex.test(emailVal)) {
alert('Please enter a valid email address.');
return false;
}
const shippingSelect = document.getElementById('shipping-option');
const opt = shippingSelect.options[shippingSelect.selectedIndex];
shippingPrice = parseFloat(opt.dataset.price || '0');
updateCartSummary();
syncPaymentRequestTotals();
return true;
}
function updateCartSummary() {
const nameSpan = document.getElementById('cart-product-name');
const priceSpan = document.getElementById('cart-product-price');
const shipSpan = document.getElementById('cart-shipping-price');
const totalSpan = document.getElementById('cart-total-price');
if (selectedProduct) {
nameSpan.textContent = products[selectedProduct].name;
priceSpan.textContent = '$' + braceletPrice.toFixed(2);
} else {
nameSpan.textContent = 'No product selected';
priceSpan.textContent = '$0.00';
}
shipSpan.textContent = '$' + shippingPrice.toFixed(2);
const total = getTotal();
totalSpan.textContent = '$' + total.toFixed(2);
const checkoutBtn = document.getElementById('checkout-btn');
if (checkoutBtn) {
checkoutBtn.textContent = 'Checkout - $' + total.toFixed(2);
}
syncPaymentRequestTotals();
}
function updateOrderSummary() {
const div = document.getElementById('order-summary');
let html = 'Order Summary
';
if (!selectedProduct) {
html += 'No product selected.
';
div.innerHTML = html;
return;
}
html += 'Product: ' + products[selectedProduct].name + '
';
html += 'Size: ' + (selectedSize ? (selectedSize.charAt(0).toUpperCase() + selectedSize.slice(1)) : 'Not selected') + '
';
if (selectedProduct === 'birthstone') {
const birthstone = document.getElementById('birthstone-input').value.trim();
const pers = document.getElementById('birthstone-personalization').value.trim();
if (birthstone) html += 'Birthstone: ' + birthstone + '
';
if (pers) html += 'Personalization: ' + pers + '
';
} else if (selectedProduct === 'heart') {
const pers = document.getElementById('heart-personalization').value.trim();
if (pers) html += 'Personalization: ' + pers + '
';
} else if (selectedProduct === 'custom') {
const feature = document.getElementById('custom-feature').value.trim();
const color = document.getElementById('custom-color').value || document.getElementById('custom-color-custom').value;
const color2 = document.getElementById('custom-color2').value;
const donation = document.getElementById('donation').value;
const pers = document.getElementById('custom-personalization').value.trim();
if (feature) html += 'Feature: ' + feature + '
';
if (color) html += 'Primary color: ' + color + '
';
if (color2) html += 'Second color: ' + color2 + '
';
if (donation && donation !== 'none') html += 'Donation: ' + donation + '
';
if (pers) html += 'Personalization: ' + pers + '
';
}
html += 'Bracelet Price: $' + braceletPrice.toFixed(2) + '
';
html += 'Shipping: $' + shippingPrice.toFixed(2) + '
';
html += 'Total: $' +
getTotal().toFixed(2) + '
';
div.innerHTML = html;
}
function prepareOrderData() {
const total = getTotal();
const type = selectedProduct;
const orderDetails = {
braceletType: type,
total: total,
size: selectedSize
};
if (type === 'birthstone') {
orderDetails.birthstone = document.getElementById('birthstone-input').value.trim();
orderDetails.personalization = document.getElementById('birthstone-personalization').value.trim();
} else if (type === 'heart') {
orderDetails.personalization = document.getElementById('heart-personalization').value.trim();
} else if (type === 'custom') {
orderDetails.feature = document.getElementById('custom-feature').value.trim();
orderDetails.color = document.getElementById('custom-color').value ||
document.getElementById('custom-color-custom').value.trim();
orderDetails.color2 = document.getElementById('custom-color2').value;
orderDetails.donation = document.getElementById('donation').value;
orderDetails.personalization = document.getElementById('custom-personalization').value.trim();
}
const customerInfo = {
name: document.getElementById('name').value.trim(),
email: document.getElementById('email').value.trim(),
phone: document.getElementById('phone').value.trim()
};
const shipping = {
address1: document.getElementById('address1').value.trim(),
address2: document.getElementById('address2').value.trim(),
city: document.getElementById('city').value.trim(),
state: document.getElementById('state').value.trim(),
postal: document.getElementById('postal').value.trim(),
country: document.getElementById('country').value.trim(),
shippingOption: document.getElementById('shipping-option').value
};
return { total, orderDetails, customerInfo, shipping };
}
async function saveOrderToBackend(paymentIntentId, orderData, recaptchaToken) {
try {
if (!recaptchaToken) {
try { recaptchaToken = await getRecaptchaToken('save_order'); } catch (e) {}
}
if (!recaptchaToken) {
return { success: false, error: 'reCAPTCHA unavailable; order was not saved.' };
}
const res = await fetch(saveOrderUrl, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
recaptchaToken: recaptchaToken,
recaptchaAction: 'save_order',
paymentIntentId: paymentIntentId,
status: 'completed',
customerInfo: orderData.customerInfo,
orderDetails: orderData.orderDetails,
shipping: orderData.shipping,
amount: orderData.total
})
});
const result = await res.json();
if (result.success) {
console.log('Order saved to backend:', result.orderId);
localStorage.setItem('hasOrdered', 'yes');
} else {
console.warn('Backend save failed:', result.error || result);
}
return result;
} catch (err) {
console.error('Failed to save order to backend:', err);
return { success: false, error: err.message };
}
}
async function handleCardPayment() {
const button = document.getElementById('card-button');
if (!validateShippingForm()) return;
if (!validateCustomization()) return;
let recaptchaToken;
try {
recaptchaToken = await getRecaptchaToken('card_payment');
} catch (e) {
alert(e && e.message ? e.message : 'reCAPTCHA failed.');
return;
}
button.disabled = true;
button.textContent = 'Processing...';
const orderData = prepareOrderData();
const amountCents = Math.round(orderData.total * 100);
let pi;
try {
pi = await postJSON(paymentIntentUrl, {
amount: amountCents,
recaptchaToken: recaptchaToken,
recaptchaAction: 'card_payment',
currency: 'usd',
orderDetails: orderData.orderDetails,
customerInfo: orderData.customerInfo,
shipping: orderData.shipping
});
} catch (err) {
document.getElementById('card-errors').textContent =
'Payment server error: ' + (err && err.message ? err.message : err);
button.disabled = false;
button.textContent = 'Pay Now';
return;
}
if (pi.error || !pi.clientSecret) {
document.getElementById('card-errors').textContent =
pi.error || 'Unable to create payment. Try again later.';
button.disabled = false;
button.textContent = 'Pay Now';
return;
}
const { error, paymentIntent } = await stripe.confirmCardPayment(pi.clientSecret, {
payment_method: {
card: card,
billing_details: {
name: orderData.customerInfo.name,
email: orderData.customerInfo.email,
phone: orderData.customerInfo.phone,
address: {
line1: orderData.shipping.address1,
line2: orderData.shipping.address2,
city: orderData.shipping.city,
state: orderData.shipping.state,
postal_code: orderData.shipping.postal,
country: orderData.shipping.country
}
}
}
});
if (error) {
let msg = error.message || 'Payment failed. Please try again.';
if (error.code === 'card_declined' && error.decline_code === 'insufficient_funds') {
msg = 'The card was declined by your bank, try again later (code: insufficient_funds).';
} else if (error.code === 'invalid_number' || error.code === 'incorrect_number') {
msg = 'Check the card number (code: ' + error.code + ').';
} else if (error.code === 'expired_card') {
msg = 'Your card has expired. Please use a different card.';
} else if (error.code === 'incorrect_cvc') {
msg = 'The CVC number is incorrect. Please check and try again.';
}
document.getElementById('card-errors').textContent = msg;
button.disabled = false;
button.textContent = 'Pay Now';
return;
}
if (paymentIntent && paymentIntent.status === 'succeeded') {
await saveOrderToBackend(paymentIntent.id, orderData, recaptchaToken);
showSuccess();
} else {
document.getElementById('card-errors').textContent =
'Payment processing... Please check your email for confirmation.';
}
button.disabled = false;
button.textContent = 'Pay Now';
}
function showSuccess() {
document.querySelectorAll('.flow-step').forEach(el => el.classList.remove('active'));
document.getElementById('step-success').classList.add('active');
document.querySelectorAll('.flow-dot').forEach(dot => dot.classList.add('active'));
document.getElementById('cart-summary').classList.remove('show');
}
async function testBackendConnection() {
try {
const res = await fetch(BACKEND_URL + '/health');
const data = await res.json();
console.log('Backend connected:', data.status || data);
} catch (err) {
console.warn('Backend connection test failed:', err.message);
}
}('Backend connected:', data.status || data);
} catch (err) {
console.warn('Backend connection test failed:', err.message);
}
}