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:
2025-10-07 17:22:51 +01:00
parent bfdcee8602
commit 3ddbc40bb5
2655 changed files with 516264 additions and 2 deletions

216
static/js/subscriptions.js Normal file
View 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();
})();