Improve subscription UI with large tier buttons
- Replace dropdown tier selection with attractive visual buttons - Add tier-button CSS with hover effects and selection states - Remove 'or pay by card' divider from subscription form for cleaner UI - Update JavaScript to handle tier button selection events - Fix Stripe module import conflict by renaming stripe directory to stripe_config - Add responsive grid layout for tier buttons on mobile devices
This commit is contained in:
216
static/js/subscriptions.js
Normal file
216
static/js/subscriptions.js
Normal file
@@ -0,0 +1,216 @@
|
||||
(async () => {
|
||||
const cfg = await fetch("/config").then(r => r.json());
|
||||
const stripe = Stripe(cfg.publishableKey);
|
||||
|
||||
const subscriptionForm = document.getElementById("subscription-form");
|
||||
const subscriptionResult = document.getElementById("subscription-result");
|
||||
const tierSelect = document.getElementById("subscription-tier");
|
||||
const emailInput = document.getElementById("subscriber-email");
|
||||
const nameInput = document.getElementById("subscriber-name");
|
||||
const submitButton = document.getElementById("subscription-submit-button");
|
||||
|
||||
let subscriptionElements, subscriptionCardElement;
|
||||
|
||||
// Initialize tier button selection
|
||||
function initializeTierButtons() {
|
||||
const tierButtons = document.querySelectorAll('.tier-button');
|
||||
const tierInput = document.getElementById('subscription-tier');
|
||||
|
||||
tierButtons.forEach(button => {
|
||||
button.addEventListener('click', function() {
|
||||
// Remove selected class from all buttons
|
||||
tierButtons.forEach(btn => btn.classList.remove('selected'));
|
||||
// Add selected class to clicked button
|
||||
this.classList.add('selected');
|
||||
|
||||
// Update the tier input value
|
||||
const selectedTier = this.getAttribute('data-tier');
|
||||
if (tierInput) {
|
||||
tierInput.value = selectedTier;
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Initialize basic card element (display only, no payment processing)
|
||||
function initializeBasicCardElement() {
|
||||
// Clean up existing elements
|
||||
if (subscriptionElements) {
|
||||
subscriptionElements.destroy();
|
||||
}
|
||||
|
||||
// Create basic elements for card display
|
||||
subscriptionElements = stripe.elements({
|
||||
appearance: {
|
||||
theme: document.documentElement.getAttribute('data-theme') === 'dark' ? 'night' : 'stripe'
|
||||
}
|
||||
});
|
||||
|
||||
// Create and mount card element
|
||||
subscriptionCardElement = subscriptionElements.create("card", {
|
||||
style: {
|
||||
base: {
|
||||
fontSize: '16px',
|
||||
color: getComputedStyle(document.documentElement).getPropertyValue('--text-primary'),
|
||||
'::placeholder': {
|
||||
color: getComputedStyle(document.documentElement).getPropertyValue('--text-secondary'),
|
||||
},
|
||||
},
|
||||
}
|
||||
});
|
||||
subscriptionCardElement.mount("#subscription-card-element");
|
||||
}
|
||||
|
||||
// Handle subscription payment
|
||||
async function handleSubscriptionPayment(e) {
|
||||
e.preventDefault();
|
||||
|
||||
const tier_id = tierSelect.value;
|
||||
const email = emailInput.value;
|
||||
const name = nameInput.value;
|
||||
|
||||
if (!tier_id || !email) {
|
||||
subscriptionResult.textContent = "❌ Please fill in all required fields.";
|
||||
return;
|
||||
}
|
||||
|
||||
subscriptionResult.textContent = "Creating subscription...";
|
||||
submitButton.disabled = true;
|
||||
|
||||
try {
|
||||
// Create subscription
|
||||
const res = await fetch("/create-subscription", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ tier_id, email, name })
|
||||
});
|
||||
|
||||
const data = await res.json();
|
||||
|
||||
if (data.error) {
|
||||
subscriptionResult.textContent = "❌ " + data.error;
|
||||
submitButton.disabled = false;
|
||||
return;
|
||||
}
|
||||
|
||||
const clientSecret = data.clientSecret;
|
||||
subscriptionResult.textContent = "Processing payment...";
|
||||
|
||||
// Confirm payment with the card element
|
||||
const { error } = await stripe.confirmPayment({
|
||||
elements: subscriptionElements,
|
||||
confirmParams: {
|
||||
return_url: window.location.href,
|
||||
},
|
||||
redirect: "if_required"
|
||||
});
|
||||
|
||||
if (error) {
|
||||
subscriptionResult.textContent = "❌ " + error.message;
|
||||
} else {
|
||||
subscriptionResult.innerHTML = "✅ Thank you! Your subscription is now active.<br>You'll receive an email confirmation shortly.";
|
||||
showSubscriptionSuccessAnimation();
|
||||
|
||||
// Add to supporter wall
|
||||
const name = nameInput.value.trim();
|
||||
if (name && window.supporterWall) {
|
||||
const tierData = await getTierData(tier_id);
|
||||
await window.supporterWall.addSupporter(name, tierData.amount * 100, tierData.currency);
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
subscriptionResult.textContent = "❌ Subscription failed. Please try again.";
|
||||
console.error(err);
|
||||
} finally {
|
||||
submitButton.disabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Get tier data
|
||||
async function getTierData(tierId) {
|
||||
const tiers = await fetch("/subscription-tiers").then(r => r.json());
|
||||
return tiers.tiers[tierId];
|
||||
}
|
||||
|
||||
// Success animation for subscriptions
|
||||
function showSubscriptionSuccessAnimation() {
|
||||
subscriptionResult.style.background = 'linear-gradient(135deg, #28a745, #20c997)';
|
||||
subscriptionResult.style.color = 'white';
|
||||
subscriptionResult.style.padding = '12px';
|
||||
subscriptionResult.style.borderRadius = '8px';
|
||||
subscriptionResult.style.animation = 'bounce 0.6s ease-out';
|
||||
}
|
||||
|
||||
// Update submit button text based on tier
|
||||
async function updateSubmitButtonText() {
|
||||
const tier_id = tierSelect.value;
|
||||
|
||||
if (tier_id) {
|
||||
try {
|
||||
const tierData = await getTierData(tier_id);
|
||||
if (tierData.interval === 'month') {
|
||||
submitButton.textContent = `Start Monthly Support - ${tierData.currency} ${tierData.amount}/${tierData.interval}`;
|
||||
} else {
|
||||
submitButton.textContent = `Start Yearly Support - ${tierData.currency} ${tierData.amount}/${tierData.interval}`;
|
||||
}
|
||||
} catch (err) {
|
||||
submitButton.textContent = "Start Support";
|
||||
}
|
||||
} else {
|
||||
submitButton.textContent = "Start Support";
|
||||
}
|
||||
}
|
||||
|
||||
// Event listeners
|
||||
subscriptionForm.addEventListener("submit", handleSubscriptionPayment);
|
||||
tierSelect.addEventListener("change", updateSubmitButtonText);
|
||||
|
||||
// Tab switching functionality
|
||||
const oneTimeTab = document.getElementById("one-time-tab");
|
||||
const recurringTab = document.getElementById("recurring-tab");
|
||||
const donationForm = document.getElementById("donation-form");
|
||||
|
||||
function switchToOneTime() {
|
||||
oneTimeTab.classList.add("active");
|
||||
recurringTab.classList.remove("active");
|
||||
donationForm.classList.add("active");
|
||||
subscriptionForm.classList.remove("active");
|
||||
}
|
||||
|
||||
function switchToRecurring() {
|
||||
recurringTab.classList.add("active");
|
||||
oneTimeTab.classList.remove("active");
|
||||
subscriptionForm.classList.add("active");
|
||||
donationForm.classList.remove("active");
|
||||
|
||||
// Initialize card element when switching to subscription tab
|
||||
if (!subscriptionElements) {
|
||||
initializeBasicCardElement();
|
||||
}
|
||||
}
|
||||
|
||||
oneTimeTab.addEventListener("click", switchToOneTime);
|
||||
recurringTab.addEventListener("click", switchToRecurring);
|
||||
|
||||
// Update elements theme when theme changes
|
||||
if (window.themeManager) {
|
||||
const originalSetTheme = window.themeManager.setTheme;
|
||||
window.themeManager.setTheme = function(theme) {
|
||||
originalSetTheme.call(this, theme);
|
||||
// Reinitialize card element with new theme
|
||||
setTimeout(() => {
|
||||
if (subscriptionForm.classList.contains("active")) {
|
||||
initializeBasicCardElement();
|
||||
}
|
||||
}, 100);
|
||||
};
|
||||
}
|
||||
|
||||
// Initialize card element on page load if subscription form is visible
|
||||
if (subscriptionForm.classList.contains("active")) {
|
||||
initializeBasicCardElement();
|
||||
}
|
||||
|
||||
// Initialize tier button selection
|
||||
initializeTierButtons();
|
||||
})();
|
||||
Reference in New Issue
Block a user