Restore to commit 74e578279624c6045ca440a3459ebfa1f8d54191

This commit is contained in:
southseact-3d
2026-02-07 20:32:41 +00:00
commit ed67b7741b
252 changed files with 99814 additions and 0 deletions

931
chat/public/upgrade.html Normal file
View File

@@ -0,0 +1,931 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Upgrade Plan | Plugin Compass</title>
<link rel="icon" type="image/png" href="/assets/Plugin.png">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link
href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;500;600;700&family=Inter:wght@400;500;600;700&display=swap"
rel="stylesheet">
<script>
// Simple auth check
fetch('/api/me')
.then(res => {
if (res.status === 401) {
window.location.href = '/login?next=' + encodeURIComponent(window.location.pathname);
}
})
.catch(() => {
// If api fails, assume logged out or valid error handling elsewhere
});
</script>
<link rel="stylesheet" href="/chat/styles.css">
<script src="https://cdn.tailwindcss.com"></script>
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" rel="stylesheet">
<script>
tailwind.config = {
theme: {
extend: {
fontFamily: {
sans: ['Inter', 'sans-serif'],
display: ['Space Grotesk', 'sans-serif'],
},
colors: {
brand: {
50: '#f0fdf4',
100: '#dcfce7',
200: '#bbf7d0',
300: '#86efac',
400: '#4ade80',
500: '#22c55e',
600: '#16a34a',
700: '#15803d',
800: '#166534',
900: '#008060', // Shopify Green
950: '#004c3f',
}
}
}
}
}
</script>
<style>
:root {
--shopify-green: #008060;
--shopify-green-dark: #004c3f;
--shopify-green-light: #e3f5ef;
--border: #e5e7eb;
--muted: #6b7280;
}
body {
background: #fffbeb; /* amber-50 to match select-plan */
min-height: 100vh;
}
.glass-panel {
background: rgba(255, 255, 255, 0.7);
backdrop-filter: blur(20px);
-webkit-backdrop-filter: blur(20px);
border: 1px solid rgba(255, 255, 255, 0.5);
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.05);
}
.premium-card {
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
.premium-card:hover {
transform: translateY(-8px);
box-shadow: 0 25px 50px rgba(0, 128, 96, 0.15);
}
.gradient-text {
background: linear-gradient(135deg, #008060, #004c3f);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
.check-icon {
background: transparent;
color: #16a34a; /* green-600 */
border-radius: 0;
padding: 0;
margin-right: 0.75rem;
}
/* Custom scrollbar */
::-webkit-scrollbar {
width: 8px;
}
::-webkit-scrollbar-track {
background: transparent;
}
::-webkit-scrollbar-thumb {
background: #cbd5e1;
border-radius: 4px;
}
::-webkit-scrollbar-thumb:hover {
background: #94a3b8;
}
.currency-dropdown.open #currency-btn {
border-color: #008060;
box-shadow: 0 0 0 3px rgba(0, 128, 96, 0.1);
}
.currency-option {
transition: all 0.15s ease;
}
.currency-option:hover {
background-color: #dcfce7;
color: #15803d;
}
</style>
<style>
.payment-modal-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(15, 23, 42, 0.7);
backdrop-filter: blur(8px);
z-index: 100000;
opacity: 0;
visibility: hidden;
transition: all 0.3s ease;
display: flex;
align-items: center;
justify-content: center;
}
.payment-modal-overlay.active {
opacity: 1;
visibility: visible;
}
.payment-modal {
background: #fff;
border-radius: 12px;
width: 95vw;
max-width: 1100px;
max-height: 96vh;
box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25);
transform: scale(0.95) translateY(8px);
transition: all 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);
display: flex;
flex-direction: column;
overflow: hidden;
height: auto;
}
.payment-modal-overlay.active .payment-modal {
transform: scale(1) translateY(0);
}
.payment-modal-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 20px 24px;
border-bottom: 1px solid #e2e8f0;
background: #fff;
flex-shrink: 0;
}
.payment-modal-header h3 { margin: 0; font-size: 18px; font-weight: 700; color: #1e293b; }
.payment-modal-close { background: none; border: none; font-size: 28px; cursor: pointer; color: #64748b; }
/* Make the frame container flexible and scrollable so iframe content fills the modal and isn't clipped */
.payment-frame-container { display: flex; flex: 1 1 auto; overflow: auto; min-height: 60vh; background: #f8fafc; height: auto; }
.payment-frame-container iframe { display: block; width: 100%; height: calc(96vh - 72px); max-height: calc(96vh - 72px); border: none; }
</style>
<!-- Dodo Payments -->
<script src="https://cdn.jsdelivr.net/npm/dodopayments-checkout@latest/dist/index.js"></script>
<!-- PostHog Analytics -->
<script src="/posthog.js"></script>
</head>
<body class="font-sans text-slate-800 antialiased selection:bg-brand-200 selection:text-brand-900">
<!-- Simple App Header -->
<header class="fixed top-0 w-full z-50 px-6 py-4">
<div class="max-w-7xl mx-auto flex justify-between items-center glass-panel rounded-2xl px-6 py-3">
<div class="flex items-center gap-3">
<img src="/assets/Plugin.png" alt="Plugin Compass" style="width: 40px; height: 40px; border-radius: 10px;">
<div>
<h1 class="font-display font-bold text-lg leading-tight text-slate-900">Plugin Compass</h1>
<p class="text-xs text-slate-500 font-medium uppercase tracking-wide">Upgrade</p>
</div>
</div>
<a href="/apps"
class="group flex items-center gap-2 text-sm font-semibold text-slate-600 hover:text-brand-900 transition-colors bg-white/50 hover:bg-white px-4 py-2 rounded-xl border border-transparent hover:border-slate-200">
<i class="fa-solid fa-arrow-left transition-transform group-hover:-translate-x-1"></i>
Back to Apps
</a>
</div>
</header>
<main class="pt-32 pb-20 px-4 sm:px-6">
<div class="max-w-7xl mx-auto">
<div class="text-center max-w-3xl mx-auto mb-16">
<!-- Currency Dropdown - Centered -->
<div class="relative currency-dropdown inline-flex" id="currency-dropdown">
<button id="currency-btn" class="bg-white border border-gray-200 rounded-full px-4 py-2 pr-10 text-sm font-medium text-gray-700 hover:border-green-700 hover:bg-gray-50 transition-all cursor-pointer flex items-center gap-2">
<span id="currency-flag">🇺🇸</span>
<span id="currency-code">USD</span>
<i class="fa-solid fa-chevron-down absolute right-3 top-1/2 -translate-y-1/2 text-gray-500 pointer-events-none text-xs transition-transform" id="currency-chevron"></i>
</button>
<div id="currency-options" class="hidden absolute top-full left-1/2 -translate-x-1/2 mt-2 w-full bg-white border border-gray-200 rounded-xl shadow-lg z-50 overflow-hidden min-w-[120px]">
<button class="currency-option w-full px-4 py-2 text-sm text-gray-700 hover:bg-green-50 hover:text-gray-900 font-medium text-left flex items-center gap-2 justify-center" data-value="USD">
🇺🇸 <span class="ml-1">USD</span>
</button>
<button class="currency-option w-full px-4 py-2 text-sm text-gray-700 hover:bg-green-50 hover:text-gray-900 font-medium text-left flex items-center gap-2 justify-center" data-value="GBP">
🇬🇧 <span class="ml-1">GBP</span>
</button>
<button class="currency-option w-full px-4 py-2 text-sm text-gray-700 hover:bg-green-50 hover:text-gray-900 font-medium text-left flex items-center gap-2 justify-center" data-value="EUR">
🇪🇺 <span class="ml-1">EUR</span>
</button>
</div>
</div>
<!-- Billing Toggle -->
<div
class="mt-8 inline-flex items-center bg-white p-1.5 rounded-full border border-slate-200 shadow-sm">
<button id="monthly-toggle"
class="px-6 py-2.5 rounded-full text-sm font-bold bg-brand-900 text-white shadow-md transition-all">
Monthly
</button>
<button id="yearly-toggle"
class="px-6 py-2.5 rounded-full text-sm font-bold text-slate-500 hover:text-slate-900 transition-all flex items-center gap-2">
Yearly <span
class="bg-amber-100 text-amber-700 text-[10px] px-2 py-0.5 rounded-full uppercase tracking-wide">-20%</span>
</button>
</div>
</div>
<!-- Pricing Grid -->
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6 w-full max-w-5xl mx-auto justify-center">
<!-- Hobby (Free) plan removed from upgrade flow -->
<!-- The free Hobby plan option is intentionally not selectable on this page. Users can still select Hobby from account settings. -->
<!-- Starter Plan -->
<div class="premium-card bg-white rounded-[2rem] p-8 border border-slate-200 shadow-xl shadow-slate-200/50 flex flex-col relative overflow-hidden"
data-plan="starter">
<div class="absolute top-0 left-0 w-full h-1 bg-green-700"></div>
<div class="mb-8">
<h3 class="font-display text-2xl font-bold text-slate-900">Starter</h3>
<p class="text-slate-500 text-sm mt-2">Great for small business needs.</p>
</div>
<div class="mb-8 flex items-baseline gap-1">
<span id="starter-price" class="text-4xl font-bold text-slate-900">$7.50</span>
<span class="text-slate-500 font-medium price-duration">/mo</span>
</div>
<ul class="space-y-4 mb-8 flex-grow">
<li class="flex items-start gap-3 text-sm text-slate-700">
<i class="fa-solid fa-check text-green-600 mr-3 mt-0.5"></i>
<span>Up to 10 active apps</span>
</li>
<li class="flex items-start gap-3 text-sm text-slate-700">
<i class="fa-solid fa-check text-green-600 mr-3 mt-0.5"></i>
<span>
<a href="/credits" target="_blank" class="inline-flex items-center text-green-700 hover:text-green-600 mr-1" title="Learn more about AI credits">
<i class="fa-solid fa-circle-question"></i>
</a>
100,000 monthly AI credits
</span>
</li>
<li class="flex items-start gap-3 text-sm text-slate-700">
<i class="fa-solid fa-check text-green-600 mr-3 mt-0.5"></i>
<span>Access to templates</span>
</li>
<li class="flex items-start gap-3 text-sm text-slate-700">
<i class="fa-solid fa-check text-green-600 mr-3 mt-0.5"></i>
<span>Faster queue than Hobby</span>
</li>
</ul>
<button
class="upgrade-btn block w-full py-4 px-6 rounded-xl border-2 border-green-700 text-green-700 font-bold hover:bg-green-50 transition-colors text-center"
data-plan="starter">
Choose Starter
</button>
</div>
<!-- Pro Plan -->
<div class="premium-card bg-white rounded-[2rem] p-8 border-2 border-green-700 shadow-2xl shadow-green-900/10 flex flex-col relative z-10"
data-plan="professional">
<div class="absolute top-0 inset-x-0 h-1.5 bg-gradient-to-r from-green-700 to-green-600"></div>
<div class="absolute top-4 right-4">
<span
class="inline-flex items-center gap-1.5 px-3 py-1 bg-green-50 text-green-700 text-xs font-bold uppercase tracking-wider rounded-full border border-green-100">
<i class="fa-solid fa-star text-[10px]"></i> Popular
</span>
</div>
<div class="mb-8 mt-2">
<h3 class="font-display text-2xl font-bold text-slate-900">Professional</h3>
<p class="text-slate-500 text-sm mt-2">For serious builders shipping plugins.</p>
</div>
<div class="mb-8 flex items-baseline gap-1">
<span id="pro-price" class="text-5xl font-bold text-slate-900 tracking-tight">$25</span>
<span class="text-slate-500 font-medium price-duration">/mo</span>
</div>
<ul class="space-y-4 mb-8 flex-grow">
<li class="flex items-start gap-3 text-sm text-slate-700 font-medium">
<div class="bg-brand-100 text-brand-700 rounded-full p-1"><i
class="fa-solid fa-check text-xs"></i></div>
<span>Up to 20 active apps</span>
</li>
<li class="flex items-start gap-3 text-sm text-slate-700 font-medium">
<div class="bg-brand-100 text-brand-700 rounded-full p-1"><i
class="fa-solid fa-check text-xs"></i></div>
<span>Choice of AI models</span>
</li>
<li class="flex items-start gap-3 text-sm text-slate-700 font-medium">
<div class="bg-brand-100 text-brand-700 rounded-full p-1"><i
class="fa-solid fa-check text-xs"></i></div>
<span>
<a href="/credits" target="_blank" class="inline-flex items-center text-green-700 hover:text-green-600 mr-1" title="Learn more about AI credits">
<i class="fa-solid fa-circle-question"></i>
</a>
5,000,000 monthly AI credits
</span>
</li>
<li class="flex items-start gap-3 text-sm text-slate-700 font-medium">
<div class="bg-brand-100 text-brand-700 rounded-full p-1"><i
class="fa-solid fa-check text-xs"></i></div>
<span>Access to templates</span>
</li>
<li class="flex items-start gap-3 text-sm text-slate-700 font-medium">
<div class="bg-brand-100 text-brand-700 rounded-full p-1"><i
class="fa-solid fa-check text-xs"></i></div>
<span>Priority queue (ahead of Hobby)</span>
</li>
</ul>
<button
class="upgrade-btn relative w-full py-4 px-6 rounded-xl bg-green-700 text-white font-bold hover:bg-green-600 transition-all shadow-lg shadow-green-700/25 hover:shadow-green-700/40 text-center group overflow-hidden"
data-plan="professional">
<span class="relative z-10 flex items-center justify-center gap-2">
Upgrade to Pro <i
class="fa-solid fa-arrow-right transition-transform group-hover:translate-x-1"></i>
</span>
<div
class="absolute inset-0 bg-white/10 translate-y-full group-hover:translate-y-0 transition-transform duration-300">
</div>
</button>
</div>
<!-- Enterprise Plan -->
<div class="premium-card bg-white rounded-[2rem] p-8 border border-slate-200 shadow-xl shadow-slate-200/50 flex flex-col relative overflow-hidden"
data-plan="enterprise">
<div class="absolute top-0 left-0 w-full h-1 bg-gradient-to-r from-green-700 to-green-600"></div>
<div class="mb-8">
<h3 class="font-display text-2xl font-bold text-slate-900">Enterprise</h3>
<p class="text-slate-500 text-sm mt-2">Maximum power and volume.</p>
</div>
<div class="mb-8 flex items-baseline gap-1">
<span id="enterprise-price" class="text-4xl font-bold text-slate-900">$75</span>
<span class="text-slate-500 font-medium price-duration">/mo</span>
</div>
<ul class="space-y-4 mb-8 flex-grow">
<li class="flex items-start gap-3 text-sm text-slate-700">
<i class="fa-solid fa-check text-green-600 mr-3 mt-0.5"></i>
<span>Unlimited active apps</span>
</li>
<li class="flex items-start gap-3 text-sm text-slate-700">
<i class="fa-solid fa-check text-green-600 mr-3 mt-0.5"></i>
<span>Fastest queue priority</span>
</li>
<li class="flex items-start gap-3 text-sm text-slate-700">
<i class="fa-solid fa-check text-green-600 mr-3 mt-0.5"></i>
<span>
<a href="/credits" target="_blank" class="inline-flex items-center text-green-700 hover:text-green-600 mr-1" title="Learn more about AI credits">
<i class="fa-solid fa-circle-question"></i>
</a>
50,000,000 monthly AI credits
</span>
</li>
<li class="flex items-start gap-3 text-sm text-slate-700">
<i class="fa-solid fa-check text-green-600 mr-3 mt-0.5"></i>
<span>Access to templates</span>
</li>
</ul>
<button
class="upgrade-btn block w-full py-4 px-6 rounded-xl border-2 border-green-700 text-green-700 font-bold hover:bg-green-50 transition-colors text-center"
data-plan="enterprise">
Choose Enterprise
</button>
</div>
</div>
<!-- Payment Modal (uses same embedding method as settings/select-plan) -->
<div class="payment-modal-overlay" id="payment-modal">
<div class="payment-modal">
<div class="payment-modal-header">
<h3>Secure Checkout</h3>
<button type="button" class="payment-modal-close" id="payment-modal-close">&times;</button>
</div>
<div id="payment-frame-container" class="payment-frame-container"></div>
</div>
</div>
</main>
<script>
// Track upgrade popup source
const urlParams = new URLSearchParams(window.location.search);
const upgradeSource = urlParams.get('source') || 'unknown';
let isProcessing = false;
let checkoutModal = null;
async function trackUpgradePopupSource() {
try {
const authHeaders = getAuthHeaders();
await fetch('/api/upgrade-popup-tracking', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
...authHeaders
},
body: JSON.stringify({ source: upgradeSource }),
credentials: 'include',
});
} catch (err) {
console.error('Failed to track upgrade popup source:', err);
}
}
// Helper to get auth headers from localStorage
function getAuthHeaders() {
try {
const userStr = localStorage.getItem('wordpress_plugin_ai_user') || localStorage.getItem('shopify_ai_user');
if (userStr) {
const user = JSON.parse(userStr);
if (user && user.sessionToken) {
return { 'Authorization': `Bearer ${user.sessionToken}` };
}
}
} catch (err) {
console.warn('Failed to get auth headers from localStorage', err);
}
return {};
}
// Track source when page loads
trackUpgradePopupSource();
// Fetch current user plan and update UI accordingly
async function fetchUserPlan() {
try {
const authHeaders = getAuthHeaders();
const response = await fetch('/api/me', {
method: 'GET',
headers: { ...authHeaders },
credentials: 'include',
});
if (response.ok) {
const data = await response.json();
const currentPlan = (data.account?.plan || 'hobby').toLowerCase();
// If user is already on enterprise, hide upgrade buttons and show message
if (currentPlan === 'enterprise') {
const enterpriseCard = document.querySelector('[data-plan="enterprise"]');
if (enterpriseCard) {
const enterpriseBtn = enterpriseCard.querySelector('.upgrade-btn');
if (enterpriseBtn) {
enterpriseBtn.textContent = 'Current Plan';
enterpriseBtn.classList.add('bg-brand-900', 'text-white', 'border-brand-900');
enterpriseBtn.classList.remove('hover:bg-slate-50', 'hover:border-slate-300');
enterpriseBtn.disabled = true;
}
}
// Update other plan buttons to show downgrade option only
document.querySelectorAll('.upgrade-btn').forEach(btn => {
const plan = btn.getAttribute('data-plan');
if (plan !== 'enterprise' && plan !== 'hobby') {
btn.textContent = 'Downgrade to ' + plan.charAt(0).toUpperCase() + plan.slice(1);
}
});
// Show notification at top of page
const heroSection = document.querySelector('main .max-w-7xl .text-center');
if (heroSection) {
const notification = document.createElement('div');
notification.className = 'bg-brand-50 border border-brand-200 text-brand-800 px-4 py-2 rounded-lg text-sm font-medium mb-6';
notification.textContent = 'You are already on the Enterprise plan with full access to all features.';
heroSection.insertBefore(notification, heroSection.firstChild);
}
}
}
} catch (err) {
console.error('Failed to fetch user plan:', err);
}
}
// Fetch user plan when page loads
fetchUserPlan();
// Currency configuration
const currencyConfig = {
USD: { rate: 1, symbol: '$', flag: '🇺🇸' },
GBP: { rate: 0.79, symbol: '£', flag: '🇬🇧' },
EUR: { rate: 1, symbol: '€', flag: '🇪🇺' }
};
const baseMonthlyPrices = {
hobby: 0,
starter: 7.5,
pro: 25,
enterprise: 75
};
let currentCurrency = 'USD';
let currentBillingPeriod = 'monthly';
function updateDisplay() {
const config = currencyConfig[currentCurrency];
const isYearly = currentBillingPeriod === 'yearly';
const formatPrice = (basePrice) => {
if (basePrice === 0) return config.symbol + '0';
if (currentCurrency === 'GBP') {
if (basePrice === 25) {
return isYearly ? '£200' : '£20';
}
if (basePrice === 75) {
return isYearly ? '£600' : '£60';
}
}
if (currentCurrency === 'EUR') {
if (basePrice === 7.5) {
return isYearly ? '€75' : '€7.50';
}
if (basePrice === 25) {
return isYearly ? '€250' : '€25';
}
if (basePrice === 75) {
return isYearly ? '€750' : '€75';
}
}
let finalPrice = basePrice * config.rate;
if (basePrice === 7.5) {
finalPrice = (isYearly ? 50 : 5);
} else if (basePrice === 25) {
finalPrice = (isYearly ? 200 : 20);
} else if (basePrice === 75) {
finalPrice = (isYearly ? 600 : 60);
} else {
finalPrice = Math.round(finalPrice * 10) / 10;
if (finalPrice % 1 === 0) finalPrice = Math.round(finalPrice);
}
if (currentCurrency === 'USD' && basePrice === 7.5 && !isYearly) return '$7.50';
return config.symbol + finalPrice;
};
const hobbyPrice = document.getElementById('hobby-price');
const starterPrice = document.getElementById('starter-price');
const proPrice = document.getElementById('pro-price');
const entPrice = document.getElementById('enterprise-price');
if (hobbyPrice) hobbyPrice.textContent = formatPrice(baseMonthlyPrices.hobby);
if (starterPrice) starterPrice.textContent = formatPrice(baseMonthlyPrices.starter);
if (proPrice) proPrice.textContent = formatPrice(baseMonthlyPrices.pro);
if (entPrice) entPrice.textContent = formatPrice(baseMonthlyPrices.enterprise);
document.querySelectorAll('.price-duration').forEach(el => {
el.textContent = isYearly ? '/mo (billed yearly)' : '/mo';
});
// Update toggle button styles
const monthlyBtn = document.getElementById('monthly-toggle');
const yearlyBtn = document.getElementById('yearly-toggle');
if (monthlyBtn && yearlyBtn) {
if (isYearly) {
yearlyBtn.classList.add('bg-brand-900', 'text-white');
yearlyBtn.classList.remove('text-slate-500', 'hover:text-slate-900');
monthlyBtn.classList.remove('bg-brand-900', 'text-white');
monthlyBtn.classList.add('text-slate-500', 'hover:text-slate-900');
} else {
monthlyBtn.classList.add('bg-brand-900', 'text-white');
monthlyBtn.classList.remove('text-slate-500', 'hover:text-slate-900');
yearlyBtn.classList.remove('bg-brand-900', 'text-white');
yearlyBtn.classList.add('text-slate-500', 'hover:text-slate-900');
}
}
// Update currency dropdown button
const flagEl = document.getElementById('currency-flag');
const codeEl = document.getElementById('currency-code');
if (flagEl) flagEl.textContent = config.flag;
if (codeEl) codeEl.textContent = currentCurrency;
}
async function autoDetectCurrency() {
try {
const response = await fetch('https://ipapi.co/json/');
const data = await response.json();
if (data.currency && currencyConfig[data.currency]) {
currentCurrency = data.currency;
updateDisplay();
}
} catch (err) {
console.log('Currency detection failed, defaulting to USD');
}
}
// Monthly/Yearly Listeners
document.getElementById('monthly-toggle')?.addEventListener('click', () => {
currentBillingPeriod = 'monthly';
updateDisplay();
});
document.getElementById('yearly-toggle')?.addEventListener('click', () => {
currentBillingPeriod = 'yearly';
updateDisplay();
});
// Currency Dropdown Listeners
const currencyBtn = document.getElementById('currency-btn');
const currencyOptions = document.getElementById('currency-options');
const currencyChevron = document.getElementById('currency-chevron');
const currencyDropdown = document.getElementById('currency-dropdown');
currencyBtn?.addEventListener('click', (e) => {
e.stopPropagation();
const isOpen = !currencyOptions.classList.contains('hidden');
if (isOpen) {
currencyOptions.classList.add('hidden');
currencyChevron.style.transform = 'translateY(-50%) rotate(0deg)';
currencyDropdown.classList.remove('open');
} else {
currencyOptions.classList.remove('hidden');
currencyChevron.style.transform = 'translateY(-50%) rotate(180deg)';
currencyDropdown.classList.add('open');
}
});
document.querySelectorAll('.currency-option').forEach(option => {
option.addEventListener('click', (e) => {
currentCurrency = e.currentTarget.getAttribute('data-value');
updateDisplay();
currencyOptions.classList.add('hidden');
currencyChevron.style.transform = 'translateY(-50%) rotate(0deg)';
currencyDropdown.classList.remove('open');
});
});
document.addEventListener('click', () => {
if (currencyOptions) {
currencyOptions.classList.add('hidden');
currencyChevron.style.transform = 'translateY(-50%) rotate(0deg)';
currencyDropdown.classList.remove('open');
}
});
// Initialize display and detection
updateDisplay();
autoDetectCurrency();
// Simple Billing Toggle Logic (legacy - kept for reference, now handled by updateDisplay)
const monthlyBtn = document.getElementById('monthly-toggle');
const yearlyBtn = document.getElementById('yearly-toggle');
const proPrice = document.getElementById('pro-price');
const entPrice = document.getElementById('enterprise-price');
const durations = document.querySelectorAll('.price-duration');
// Prices
const prices = {
monthly: { starter: 7.5, pro: 25, ent: 75 },
yearly: { starter: 6, pro: 20, ent: 60 } // calculated as monthly cost when paying yearly
};
// Handle plan selection (Upgrade Logic with Dodo Payments)
document.querySelectorAll('.premium-card').forEach(card => {
card.addEventListener('click', async (e) => {
// Let links within the card work normally
if (e.target.closest('a')) return;
e.preventDefault();
if (isProcessing) return;
const plan = card.getAttribute('data-plan');
if (!plan) return;
isProcessing = true;
const btn = card.querySelector('.upgrade-btn');
const originalText = btn ? btn.innerHTML : 'Processing...';
// Disable button
if (btn) {
btn.disabled = true;
btn.innerHTML = '<i class="fa-solid fa-circle-notch fa-spin"></i> Processing...';
btn.classList.add('opacity-75', 'cursor-not-allowed');
}
try {
const authHeaders = getAuthHeaders();
// Hobby plan is free - use select-plan endpoint
if (plan === 'hobby') {
const response = await fetch('/api/select-plan', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
...authHeaders
},
body: JSON.stringify({ plan }),
credentials: 'include',
});
const data = await response.json();
if (response.ok && data.ok) {
window.location.href = '/settings?msg=plan_updated';
} else {
throw new Error(data.error || 'Failed to update plan. Please try again.');
}
} else {
// Paid plans use Dodo Payments subscription checkout
const response = await fetch('/api/subscription/checkout', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
...authHeaders
},
body: JSON.stringify({
plan: plan,
billingCycle: currentBillingPeriod,
currency: currentCurrency.toLowerCase(),
inline: true,
previousPlan: (typeof window.currentPlan !== 'undefined' ? window.currentPlan : '')
}),
credentials: 'include',
});
const data = await response.json();
if (response.ok && data.sessionId) {
// Prefer inlineCheckoutUrl when available (settings page embeds an iframe)
const checkoutUrl = data.inlineCheckoutUrl || data.checkoutUrl;
if (checkoutUrl) {
// Use same embedding strategy as settings page: open payment modal with iframe
await openInlineCheckout(checkoutUrl, data.sessionId, plan);
} else {
throw new Error('No checkout URL provided');
}
} else {
throw new Error(data.error || 'Failed to start checkout');
}
}
} catch (error) {
console.error('Error updating plan:', error);
alert(error.message || 'Failed to update plan. Please try again.');
if (btn) {
btn.disabled = false;
btn.innerHTML = originalText;
btn.classList.remove('opacity-75', 'cursor-not-allowed');
}
isProcessing = false;
}
});
});
async function ensureDodoPaymentsLoaded() {
return new Promise((resolve) => {
if (typeof DodoPaymentsCheckout !== 'undefined' && DodoPaymentsCheckout.DodoPayments && DodoPaymentsCheckout.DodoPayments.Checkout) {
resolve();
return;
}
const existingScript = document.querySelector('script[src="https://cdn.jsdelivr.net/npm/dodopayments-checkout@latest/dist/index.js"]');
if (existingScript) {
const checkLoaded = () => {
if (typeof DodoPaymentsCheckout !== 'undefined' && DodoPaymentsCheckout.DodoPayments && DodoPaymentsCheckout.DodoPayments.Checkout) {
resolve();
} else {
setTimeout(checkLoaded, 100);
}
};
checkLoaded();
return;
}
const script = document.createElement('script');
script.src = 'https://cdn.jsdelivr.net/npm/dodopayments-checkout@latest/dist/index.js';
script.async = true;
const timeout = setTimeout(() => {
console.warn('DodoPayments script loading timed out, will use iframe fallback');
resolve();
}, 5000);
script.onload = () => {
clearTimeout(timeout);
resolve();
};
script.onerror = () => {
clearTimeout(timeout);
console.error('Failed to load DodoPayments script, will use iframe fallback');
resolve();
};
document.head.appendChild(script);
});
}
async function openInlineCheckout(checkoutUrl, sessionId, plan) {
// Embed the Dodo checkout in the shared payment modal (same as settings/select-plan)
const modal = document.getElementById('payment-modal');
const container = document.getElementById('payment-frame-container');
if (!modal || !container) {
// Fallback: open checkout in a new window
window.open(checkoutUrl, 'dodo-payment-checkout', 'width=600,height=700');
return;
}
// Show iframe in modal
container.innerHTML = `<iframe src="${checkoutUrl}" allowpaymentrequest="true"></iframe>`;
modal.classList.add('active');
let pollCount = 0;
const maxPolls = 180;
const pollInterval = 1000;
const checkPaymentStatus = async () => {
try {
pollCount++;
const resp = await fetch(`/api/subscription/confirm?session_id=${encodeURIComponent(sessionId)}`, {
credentials: 'same-origin'
});
if (resp.ok) {
const data = await resp.json();
if (data.ok) {
clearInterval(pollTimer);
// Close modal and redirect to settings so account data is refreshed
modal.classList.remove('active');
container.innerHTML = '';
window.location.href = '/settings?msg=plan_updated';
return;
}
}
if (pollCount >= maxPolls) {
clearInterval(pollTimer);
modal.classList.remove('active');
container.innerHTML = '';
alert('Payment confirmation timeout. Please check your account status.');
isProcessing = false;
}
} catch (error) {
console.error('Error checking payment status:', error);
}
};
const pollTimer = setInterval(checkPaymentStatus, pollInterval);
// Close handlers
const closeBtn = document.getElementById('payment-modal-close');
if (closeBtn) {
closeBtn.onclick = () => {
clearInterval(pollTimer);
modal.classList.remove('active');
container.innerHTML = '';
isProcessing = false;
};
}
modal.onclick = (e) => {
if (e.target === modal) {
clearInterval(pollTimer);
modal.classList.remove('active');
container.innerHTML = '';
isProcessing = false;
}
};
const handleEsc = (e) => {
if (e.key === 'Escape') {
clearInterval(pollTimer);
modal.classList.remove('active');
container.innerHTML = '';
isProcessing = false;
}
};
document.addEventListener('keydown', handleEsc);
}
</script>
<footer
style="margin-top: 80px; padding: 40px 24px; border-top: 1px solid #e1e3e5; text-align: center; color: #6d7175; font-size: 0.875rem;">
<p>&copy; 2026 Plugin Compass. All rights reserved.</p>
<div style="margin-top: 12px; display: flex; justify-content: center; gap: 16px;">
<a href="/terms" style="color: #008060; text-decoration: none;">Terms</a>
<a href="/privacy" style="color: #008060; text-decoration: none;">Privacy</a>
<a href="/contact" style="color: #008060; text-decoration: none;">Contact Us</a>
</div>
</footer>
</body>
</html>