Restore to commit 74e578279624c6045ca440a3459ebfa1f8d54191
This commit is contained in:
931
chat/public/upgrade.html
Normal file
931
chat/public/upgrade.html
Normal 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">×</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>© 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>
|
||||
Reference in New Issue
Block a user