Restore to commit 74e578279624c6045ca440a3459ebfa1f8d54191
199
chat/public/404.html
Normal file
@@ -0,0 +1,199 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" class="scroll-smooth">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>404 - Page Not Found | Plugin Compass</title>
|
||||
<link rel="icon" type="image/png" href="/assets/Plugin.png">
|
||||
<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">
|
||||
<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=Inter:wght@300;400;500;600;700&family=Space+Grotesk:wght@300;400;500;600;700&display=swap"
|
||||
rel="stylesheet">
|
||||
|
||||
<script>
|
||||
tailwind.config = {
|
||||
theme: {
|
||||
extend: {
|
||||
fontFamily: {
|
||||
sans: ['Inter', 'sans-serif'],
|
||||
display: ['Space Grotesk', 'sans-serif'],
|
||||
},
|
||||
animation: {
|
||||
'blob': 'blob 7s infinite',
|
||||
},
|
||||
keyframes: {
|
||||
blob: {
|
||||
'0%': { transform: 'translate(0px, 0px) scale(1)' },
|
||||
'33%': { transform: 'translate(30px, -50px) scale(1.1)' },
|
||||
'66%': { transform: 'translate(-20px, 20px) scale(0.9)' },
|
||||
'100%': { transform: 'translate(0px, 0px) scale(1)' },
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style>
|
||||
.glass-nav {
|
||||
background: rgba(251, 246, 239, 0.9);
|
||||
backdrop-filter: blur(12px);
|
||||
-webkit-backdrop-filter: blur(12px);
|
||||
border-bottom: 1px solid rgba(0, 66, 37, 0.1);
|
||||
}
|
||||
|
||||
.hero-gradient-text {
|
||||
background: linear-gradient(to right, #004225, #006B3D, #057857);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
}
|
||||
</style>
|
||||
|
||||
<!-- PostHog Analytics -->
|
||||
<script src="/posthog.js"></script>
|
||||
</head>
|
||||
|
||||
<body class="bg-amber-50 text-gray-900 font-sans antialiased overflow-x-hidden min-h-screen flex flex-col">
|
||||
|
||||
<!-- Navigation -->
|
||||
<nav class="fixed w-full z-50 glass-nav transition-all duration-300">
|
||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div class="flex justify-between items-center h-20">
|
||||
<!-- Logo -->
|
||||
<a href="/" class="flex-shrink-0 flex items-center gap-2 cursor-pointer">
|
||||
<img src="/assets/Plugin.png" alt="Plugin Compass" class="w-8 h-8 rounded-lg">
|
||||
<span class="font-bold text-xl tracking-tight text-gray-800">Plugin<span
|
||||
class="text-green-700">Compass</span></span>
|
||||
</a>
|
||||
|
||||
<div class="flex items-center gap-4">
|
||||
<a href="/login"
|
||||
class="text-gray-700 hover:text-gray-900 font-medium text-sm transition-colors">Sign In</a>
|
||||
<a href="/signup"
|
||||
class="bg-green-700 hover:bg-green-600 text-white px-5 py-2.5 rounded-full font-medium text-sm transition-all shadow-lg shadow-green-700/20 hover:shadow-green-700/40 transform hover:-translate-y-0.5">
|
||||
Get Started
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<!-- Main Content -->
|
||||
<main class="flex-grow flex items-center justify-center relative pt-20 overflow-hidden">
|
||||
<!-- Background Blobs -->
|
||||
<div
|
||||
class="absolute top-1/2 left-1/4 -translate-x-1/2 -translate-y-1/2 w-96 h-96 bg-green-600/20 rounded-full mix-blend-multiply filter blur-[100px] opacity-40 animate-blob">
|
||||
</div>
|
||||
<div
|
||||
class="absolute top-1/2 right-1/4 translate-x-1/2 -translate-y-1/4 w-96 h-96 bg-green-500/20 rounded-full mix-blend-multiply filter blur-[100px] opacity-40 animate-blob animation-delay-2000">
|
||||
</div>
|
||||
|
||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 relative z-10 text-center">
|
||||
<div class="mb-8">
|
||||
<span class="text-9xl font-extrabold text-green-700/10 select-none font-display">404</span>
|
||||
</div>
|
||||
|
||||
<h1 class="text-4xl md:text-6xl font-bold tracking-tight mb-6 leading-tight text-gray-900 font-display">
|
||||
Oops! Looks like you're <br>
|
||||
<span class="hero-gradient-text">lost in the boilerplate</span>
|
||||
</h1>
|
||||
|
||||
<p class="mt-4 text-xl text-gray-700 max-w-2xl mx-auto mb-10 leading-relaxed">
|
||||
The page you're looking for doesn't exist. Let's get you back on track to building your Wordpress
|
||||
plugin.
|
||||
</p>
|
||||
|
||||
<div class="flex flex-col sm:flex-row justify-center items-center gap-4 mb-16">
|
||||
<a href="/apps"
|
||||
class="w-full sm:w-auto px-8 py-4 bg-green-700 text-white rounded-full font-bold hover:bg-green-600 transition-colors shadow-[0_0_20px_rgba(22,163,74,0.3)]">
|
||||
Go to Dashboard
|
||||
</a>
|
||||
<a href="/"
|
||||
class="w-full sm:w-auto px-8 py-4 bg-amber-100 text-gray-900 border border-amber-300 rounded-full font-semibold hover:bg-amber-200 transition-colors backdrop-blur-sm flex items-center justify-center gap-2">
|
||||
Back to Home
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-6 max-w-4xl mx-auto text-left">
|
||||
<a href="/pricing"
|
||||
class="p-6 rounded-2xl bg-white border border-green-100 hover:border-green-400 transition-all group">
|
||||
<div
|
||||
class="w-10 h-10 rounded-lg bg-green-50 flex items-center justify-center text-green-700 mb-4 group-hover:scale-110 transition-transform">
|
||||
<i class="fa-solid fa-tag"></i>
|
||||
</div>
|
||||
<h3 class="font-bold text-gray-900 mb-1">Check Pricing</h3>
|
||||
<p class="text-sm text-gray-600">Find the perfect plan for your app.</p>
|
||||
</a>
|
||||
<a href="/apps"
|
||||
class="p-6 rounded-2xl bg-white border border-green-100 hover:border-green-400 transition-all group">
|
||||
<div
|
||||
class="w-10 h-10 rounded-lg bg-green-50 flex items-center justify-center text-green-700 mb-4 group-hover:scale-110 transition-transform">
|
||||
<i class="fa-solid fa-rocket"></i>
|
||||
</div>
|
||||
<h3 class="font-bold text-gray-900 mb-1">Start Building</h3>
|
||||
<p class="text-sm text-gray-600">Create a new WordPress plugin in minutes.</p>
|
||||
</a>
|
||||
<a href="#"
|
||||
class="p-6 rounded-2xl bg-white border border-green-100 hover:border-green-400 transition-all group">
|
||||
<div
|
||||
class="w-10 h-10 rounded-lg bg-green-50 flex items-center justify-center text-green-700 mb-4 group-hover:scale-110 transition-transform">
|
||||
<i class="fa-solid fa-book"></i>
|
||||
</div>
|
||||
<h3 class="font-bold text-gray-900 mb-1">Documentation</h3>
|
||||
<p class="text-sm text-gray-600">Learn how to make the most of AI.</p>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<!-- Footer -->
|
||||
<footer class="bg-white border-t border-green-200 pt-16 pb-8">
|
||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div class="grid grid-cols-2 md:grid-cols-4 gap-12 mb-16">
|
||||
<div class="col-span-2 md:col-span-1">
|
||||
<div class="flex items-center gap-2 mb-6">
|
||||
<img src="/assets/Plugin.png" alt="Plugin Compass" class="w-8 h-8">
|
||||
<span class="font-bold text-xl tracking-tight text-gray-800">Plugin<span
|
||||
class="text-green-700">Compass</span></span>
|
||||
</div>
|
||||
<p class="text-gray-600 text-sm leading-relaxed">
|
||||
The smart way for WordPress site owners to replace expensive plugin subscriptions with custom
|
||||
solutions. Save thousands monthly.
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<h4 class="font-bold text-gray-900 mb-6">Product</h4>
|
||||
<ul class="space-y-4 text-sm">
|
||||
<li><a href="/features" class="text-gray-600 hover:text-green-700">Features</a></li>
|
||||
<li><a href="/pricing" class="text-gray-600 hover:text-green-700">Pricing</a></li>
|
||||
<li><a href="#" class="text-gray-600 hover:text-green-700">Templates</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div>
|
||||
<h4 class="font-bold text-gray-900 mb-6">Resources</h4>
|
||||
<ul class="space-y-4 text-sm">
|
||||
<li><a href="/docs" class="text-gray-600 hover:text-green-700">Documentation</a></li>
|
||||
<li><a href="/faq" class="text-gray-600 hover:text-green-700">FAQ</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div>
|
||||
<h4 class="font-bold text-gray-900 mb-6">Legal</h4>
|
||||
<ul class="space-y-4 text-sm">
|
||||
<li><a href="/privacy.html" class="text-gray-600 hover:text-green-700">Privacy Policy</a></li>
|
||||
<li><a href="/terms" class="text-gray-600 hover:text-green-700">Terms of Service</a></li>
|
||||
<li><a href="/contact" class="text-gray-600 hover:text-green-700">Contact Us</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="border-t border-gray-100 pt-8 flex justify-center">
|
||||
<p class="text-gray-500 text-xs text-center">© 2026 Plugin Compass. All rights reserved.</p>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
||||
94
chat/public/admin-accounts.html
Normal file
@@ -0,0 +1,94 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Admin - Accounts</title>
|
||||
<link rel="stylesheet" href="/styles.css" />
|
||||
|
||||
<!-- PostHog Analytics -->
|
||||
<script src="/posthog.js"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="sidebar-overlay"></div>
|
||||
<div class="app-shell">
|
||||
<aside class="sidebar">
|
||||
<div class="brand">
|
||||
<div class="brand-mark">A</div>
|
||||
<div>
|
||||
<div class="brand-title">Admin</div>
|
||||
<div class="brand-sub">Site management</div>
|
||||
</div>
|
||||
<button id="close-sidebar" class="ghost" style="margin-left: auto; display: none;">×</button>
|
||||
</div>
|
||||
<div class="sidebar-section">
|
||||
<div class="section-heading">Navigation</div>
|
||||
<a class="ghost" href="/admin/build">Build models</a>
|
||||
<a class="ghost" href="/admin/plan">Plan models</a>
|
||||
<a class="ghost" href="/admin/plans">Plans</a>
|
||||
<a class="ghost" href="/admin/accounts">Accounts</a>
|
||||
<a class="ghost" href="/admin/affiliates">Affiliates</a>
|
||||
<a class="ghost" href="/admin/withdrawals">Withdrawals</a>
|
||||
<a class="ghost" href="/admin/tracking">Tracking</a>
|
||||
<a class="ghost" href="/admin/resources">Resources</a>
|
||||
<a class="ghost" href="/admin/contact-messages">Contact Messages</a>
|
||||
<a class="ghost" href="/admin/login">Login</a>
|
||||
</div>
|
||||
</aside>
|
||||
<main class="main">
|
||||
<div class="admin-shell">
|
||||
<div class="topbar" style="margin-bottom: 12px;">
|
||||
<button id="menu-toggle">
|
||||
<span></span><span></span><span></span>
|
||||
</button>
|
||||
<div>
|
||||
<div class="pill">Admin</div>
|
||||
<div class="title" style="margin-top: 6px;">Accounts</div>
|
||||
<div class="crumb">View all user accounts, plans, and billing status.</div>
|
||||
</div>
|
||||
<div class="admin-actions">
|
||||
<a class="ghost" href="/admin">Back to models</a>
|
||||
<button id="admin-refresh" class="ghost">Refresh</button>
|
||||
<button id="admin-logout" class="primary">Logout</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="admin-card" style="overflow:auto;">
|
||||
<header style="align-items:center;">
|
||||
<div>
|
||||
<h3>Accounts</h3>
|
||||
<p class="crumb" style="margin-top:4px;">Email, plan, status, and renewal information.</p>
|
||||
</div>
|
||||
<div class="pill" id="accounts-count">0 accounts</div>
|
||||
</header>
|
||||
<div class="status-line" id="admin-status"></div>
|
||||
<div class="admin-table">
|
||||
<table style="width:100%; border-collapse:collapse; min-width:700px;">
|
||||
<thead style="position:sticky; top:0; background:var(--panel); z-index:1;">
|
||||
<tr
|
||||
style="text-align:left; font-size:13px; color: var(--muted); border-bottom:2px solid var(--border);">
|
||||
<th style="padding:12px 8px;">Email</th>
|
||||
<th style="padding:12px 8px;">Plan</th>
|
||||
<th style="padding:12px 8px;">Status</th>
|
||||
<th style="padding:12px 8px;">Billing email</th>
|
||||
<th style="padding:12px 8px;">Renews</th>
|
||||
<th style="padding:12px 8px;">Created</th>
|
||||
<th style="padding:12px 8px;">Last login</th>
|
||||
<th style="padding:12px 8px;">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="accounts-table">
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<script src="/admin.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
94
chat/public/admin-affiliates.html
Normal file
@@ -0,0 +1,94 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Admin - Affiliate Accounts</title>
|
||||
<link rel="stylesheet" href="/styles.css" />
|
||||
|
||||
<!-- PostHog Analytics -->
|
||||
<script src="/posthog.js"></script>
|
||||
</head>
|
||||
|
||||
<body data-page="affiliates">
|
||||
<div class="sidebar-overlay"></div>
|
||||
<div class="app-shell">
|
||||
<aside class="sidebar">
|
||||
<div class="brand">
|
||||
<div class="brand-mark">A</div>
|
||||
<div>
|
||||
<div class="brand-title">Admin</div>
|
||||
<div class="brand-sub">Site management</div>
|
||||
</div>
|
||||
<button id="close-sidebar" class="ghost" style="margin-left: auto; display: none;">×</button>
|
||||
</div>
|
||||
<div class="sidebar-section">
|
||||
<div class="section-heading">Navigation</div>
|
||||
<a class="ghost" href="/admin/build">Build models</a>
|
||||
<a class="ghost" href="/admin/plan">Plan models</a>
|
||||
<a class="ghost" href="/admin/plans">Plans</a>
|
||||
<a class="ghost" href="/admin/accounts">Accounts</a>
|
||||
<a class="ghost" href="/admin/affiliates">Affiliates</a>
|
||||
<a class="ghost" href="/admin/withdrawals">Withdrawals</a>
|
||||
<a class="ghost" href="/admin/tracking">Tracking</a>
|
||||
<a class="ghost" href="/admin/resources">Resources</a>
|
||||
<a class="ghost" href="/admin/contact-messages">Contact Messages</a>
|
||||
<a class="ghost" href="/admin/login">Login</a>
|
||||
</div>
|
||||
</aside>
|
||||
<main class="main">
|
||||
<div class="admin-shell">
|
||||
<div class="topbar" style="margin-bottom: 12px;">
|
||||
<button id="menu-toggle">
|
||||
<span></span><span></span><span></span>
|
||||
</button>
|
||||
<div>
|
||||
<div class="pill">Admin</div>
|
||||
<div class="title" style="margin-top: 6px;">Affiliate Accounts</div>
|
||||
<div class="crumb">View all affiliate accounts, earnings, and tracking links.</div>
|
||||
</div>
|
||||
<div class="admin-actions">
|
||||
<a class="ghost" href="/admin">Back to models</a>
|
||||
<button id="admin-refresh" class="ghost">Refresh</button>
|
||||
<button id="admin-logout" class="primary">Logout</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="admin-card" style="overflow:auto;">
|
||||
<header style="align-items:center;">
|
||||
<div>
|
||||
<h3>Affiliates</h3>
|
||||
<p class="crumb" style="margin-top:4px;">Email, commission rate, earnings, and tracking link management.</p>
|
||||
</div>
|
||||
<div class="pill" id="affiliates-count">0 affiliates</div>
|
||||
</header>
|
||||
<div class="status-line" id="admin-status"></div>
|
||||
<div class="admin-table">
|
||||
<table style="width:100%; border-collapse:collapse; min-width:700px;">
|
||||
<thead style="position:sticky; top:0; background:var(--panel); z-index:1;">
|
||||
<tr
|
||||
style="text-align:left; font-size:13px; color: var(--muted); border-bottom:2px solid var(--border);">
|
||||
<th style="padding:12px 8px;">Email</th>
|
||||
<th style="padding:12px 8px;">Name</th>
|
||||
<th style="padding:12px 8px;">Commission</th>
|
||||
<th style="padding:12px 8px;">Total Earnings</th>
|
||||
<th style="padding:12px 8px;">Tracking Links</th>
|
||||
<th style="padding:12px 8px;">Created</th>
|
||||
<th style="padding:12px 8px;">Last Login</th>
|
||||
<th style="padding:12px 8px;">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="affiliates-table">
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<script src="/admin.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
307
chat/public/admin-contact-messages.html
Normal file
@@ -0,0 +1,307 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Contact Messages - Admin Panel</title>
|
||||
<link rel="stylesheet" href="/styles.css" />
|
||||
<style>
|
||||
body[data-page="contact-messages"] .admin-grid {
|
||||
grid-template-columns: none !important;
|
||||
gap: 12px !important;
|
||||
}
|
||||
body[data-page="contact-messages"] .admin-grid .admin-card {
|
||||
width: 100% !important;
|
||||
}
|
||||
.message-card {
|
||||
background: var(--panel);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 12px;
|
||||
padding: 16px;
|
||||
margin-bottom: 12px;
|
||||
transition: border-color 0.2s;
|
||||
}
|
||||
.message-card:hover {
|
||||
border-color: rgba(0, 66, 37, 0.3);
|
||||
}
|
||||
.message-card.unread {
|
||||
border-left: 4px solid var(--accent);
|
||||
}
|
||||
.message-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
.message-meta {
|
||||
flex: 1;
|
||||
}
|
||||
.message-name {
|
||||
font-weight: 700;
|
||||
font-size: 16px;
|
||||
color: var(--text);
|
||||
}
|
||||
.message-email {
|
||||
font-size: 13px;
|
||||
color: var(--muted);
|
||||
margin-top: 2px;
|
||||
}
|
||||
.message-subject {
|
||||
font-size: 14px;
|
||||
color: var(--text);
|
||||
margin-top: 4px;
|
||||
}
|
||||
.message-date {
|
||||
font-size: 12px;
|
||||
color: var(--muted);
|
||||
white-space: nowrap;
|
||||
}
|
||||
.message-body {
|
||||
background: rgba(255, 255, 255, 0.5);
|
||||
border-radius: 8px;
|
||||
padding: 12px;
|
||||
font-size: 14px;
|
||||
line-height: 1.6;
|
||||
color: var(--text);
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
.message-actions {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
margin-top: 12px;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
.message-status {
|
||||
display: inline-block;
|
||||
padding: 4px 8px;
|
||||
border-radius: 999px;
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
.message-status.unread {
|
||||
background: rgba(0, 128, 96, 0.1);
|
||||
color: var(--accent);
|
||||
}
|
||||
.message-status.read {
|
||||
background: rgba(0, 0, 0, 0.05);
|
||||
color: var(--muted);
|
||||
}
|
||||
.empty-state {
|
||||
text-align: center;
|
||||
padding: 60px 20px;
|
||||
color: var(--muted);
|
||||
}
|
||||
.empty-state svg {
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
margin-bottom: 16px;
|
||||
opacity: 0.5;
|
||||
}
|
||||
.search-box {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
.search-box input {
|
||||
width: 100%;
|
||||
padding: 12px 16px;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 10px;
|
||||
background: white;
|
||||
font-size: 14px;
|
||||
}
|
||||
.search-box input:focus {
|
||||
outline: none;
|
||||
border-color: var(--accent);
|
||||
}
|
||||
</style>
|
||||
<script src="/posthog.js"></script>
|
||||
</head>
|
||||
<body data-page="contact-messages">
|
||||
<div class="sidebar-overlay"></div>
|
||||
<div class="app-shell">
|
||||
<aside class="sidebar">
|
||||
<div class="brand">
|
||||
<div class="brand-mark">A</div>
|
||||
<div>
|
||||
<div class="brand-title">Admin</div>
|
||||
<div class="brand-sub">Site management</div>
|
||||
</div>
|
||||
<button id="close-sidebar" class="ghost" style="margin-left: auto; display: none;">×</button>
|
||||
</div>
|
||||
<div class="sidebar-section">
|
||||
<div class="section-heading">Navigation</div>
|
||||
<a class="ghost" href="/admin/build">Build models</a>
|
||||
<a class="ghost" href="/admin/plan">Plan models</a>
|
||||
<a class="ghost" href="/admin/plans">Plans</a>
|
||||
<a class="ghost" href="/admin/accounts">Accounts</a>
|
||||
<a class="ghost" href="/admin/affiliates">Affiliates</a>
|
||||
<a class="ghost" href="/admin/withdrawals">Withdrawals</a>
|
||||
<a class="ghost" href="/admin/tracking">Tracking</a>
|
||||
<a class="ghost" href="/admin/resources">Resources</a>
|
||||
<a class="ghost active" href="/admin/contact-messages">Contact Messages</a>
|
||||
<a class="ghost" href="/admin/login">Login</a>
|
||||
</div>
|
||||
</aside>
|
||||
<main class="main">
|
||||
<div class="admin-shell">
|
||||
<div class="topbar">
|
||||
<button id="menu-toggle">
|
||||
<span></span><span></span><span></span>
|
||||
</button>
|
||||
<div>
|
||||
<div class="pill">Admin</div>
|
||||
<div class="title" style="margin-top: 6px;">Contact Messages</div>
|
||||
<div class="crumb">View and manage contact form submissions</div>
|
||||
</div>
|
||||
<div class="admin-actions">
|
||||
<button id="admin-refresh" class="ghost">Refresh</button>
|
||||
<button id="admin-logout" class="primary">Logout</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="admin-grid">
|
||||
<div class="admin-card" style="grid-column: span 2;">
|
||||
<header>
|
||||
<h3>Messages</h3>
|
||||
<div class="pill" id="message-count">0</div>
|
||||
</header>
|
||||
<div class="search-box">
|
||||
<input type="text" id="search-input" placeholder="Search messages..." />
|
||||
</div>
|
||||
<div id="messages-list">
|
||||
<div class="empty-state">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"></path>
|
||||
</svg>
|
||||
<p>No contact messages yet</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
<script src="/admin.js"></script>
|
||||
<script>
|
||||
(async function() {
|
||||
const messagesList = document.getElementById('messages-list');
|
||||
const messageCount = document.getElementById('message-count');
|
||||
const searchInput = document.getElementById('search-input');
|
||||
let messages = [];
|
||||
|
||||
async function loadMessages() {
|
||||
try {
|
||||
const response = await fetch('/api/contact/messages');
|
||||
const data = await response.json();
|
||||
if (data.messages) {
|
||||
messages = data.messages;
|
||||
renderMessages(messages);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to load messages:', error);
|
||||
messagesList.innerHTML = '<div class="empty-state"><p>Failed to load messages</p></div>';
|
||||
}
|
||||
}
|
||||
|
||||
function formatDate(dateString) {
|
||||
const date = new Date(dateString);
|
||||
return date.toLocaleDateString('en-US', {
|
||||
year: 'numeric',
|
||||
month: 'short',
|
||||
day: 'numeric',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit'
|
||||
});
|
||||
}
|
||||
|
||||
function renderMessages(messageList) {
|
||||
messageCount.textContent = messageList.length;
|
||||
|
||||
if (messageList.length === 0) {
|
||||
messagesList.innerHTML = `
|
||||
<div class="empty-state">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"></path>
|
||||
</svg>
|
||||
<p>No contact messages found</p>
|
||||
</div>
|
||||
`;
|
||||
return;
|
||||
}
|
||||
|
||||
messagesList.innerHTML = messageList.map(msg => `
|
||||
<div class="message-card ${msg.read ? '' : 'unread'}" data-id="${msg.id}">
|
||||
<div class="message-header">
|
||||
<div class="message-meta">
|
||||
<div class="message-name">${escapeHtml(msg.name)}</div>
|
||||
<div class="message-email">${escapeHtml(msg.email)}</div>
|
||||
<div class="message-subject">${escapeHtml(msg.subject)}</div>
|
||||
</div>
|
||||
<div style="text-align: right;">
|
||||
<span class="message-status ${msg.read ? 'read' : 'unread'}">${msg.read ? 'Read' : 'Unread'}</span>
|
||||
<div class="message-date">${formatDate(msg.createdAt)}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="message-body">${escapeHtml(msg.message)}</div>
|
||||
<div class="message-actions">
|
||||
<button class="ghost mark-read-btn" data-id="${msg.id}" ${msg.read ? 'style="display:none;"' : ''}>Mark as Read</button>
|
||||
<button class="danger delete-btn" data-id="${msg.id}">Delete</button>
|
||||
</div>
|
||||
</div>
|
||||
`).join('');
|
||||
|
||||
document.querySelectorAll('.mark-read-btn').forEach(btn => {
|
||||
btn.addEventListener('click', async (e) => {
|
||||
const id = e.target.dataset.id;
|
||||
try {
|
||||
const response = await fetch(`/api/contact/messages/${id}/read`, { method: 'POST' });
|
||||
if (response.ok) {
|
||||
await loadMessages();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to mark as read:', error);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
document.querySelectorAll('.delete-btn').forEach(btn => {
|
||||
btn.addEventListener('click', async (e) => {
|
||||
const id = e.target.dataset.id;
|
||||
if (confirm('Are you sure you want to delete this message?')) {
|
||||
try {
|
||||
const response = await fetch(`/api/contact/messages/${id}`, { method: 'DELETE' });
|
||||
if (response.ok) {
|
||||
await loadMessages();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to delete message:', error);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function escapeHtml(text) {
|
||||
const div = document.createElement('div');
|
||||
div.textContent = text;
|
||||
return div.innerHTML;
|
||||
}
|
||||
|
||||
searchInput.addEventListener('input', (e) => {
|
||||
const query = e.target.value.toLowerCase();
|
||||
const filtered = messages.filter(msg =>
|
||||
msg.name.toLowerCase().includes(query) ||
|
||||
msg.email.toLowerCase().includes(query) ||
|
||||
msg.subject.toLowerCase().includes(query) ||
|
||||
msg.message.toLowerCase().includes(query)
|
||||
);
|
||||
renderMessages(filtered);
|
||||
});
|
||||
|
||||
document.getElementById('admin-refresh').addEventListener('click', loadMessages);
|
||||
|
||||
loadMessages();
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
70
chat/public/admin-login.html
Normal file
@@ -0,0 +1,70 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Admin Login</title>
|
||||
<link rel="stylesheet" href="/styles.css" />
|
||||
|
||||
<!-- PostHog Analytics -->
|
||||
<script src="/posthog.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div class="sidebar-overlay"></div>
|
||||
<div class="app-shell">
|
||||
<aside class="sidebar">
|
||||
<div class="brand">
|
||||
<div class="brand-mark">A</div>
|
||||
<div>
|
||||
<div class="brand-title">Admin</div>
|
||||
<div class="brand-sub">Site management</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="sidebar-section">
|
||||
<div class="section-heading">Navigation</div>
|
||||
<a class="ghost" href="/admin">Models</a>
|
||||
<a class="ghost" href="/admin/accounts">Accounts</a>
|
||||
<a class="ghost" href="/admin/login">Login</a>
|
||||
</div>
|
||||
</aside>
|
||||
<main class="main">
|
||||
<div class="admin-shell">
|
||||
<div class="admin-card" style="max-width: 520px; margin: 60px auto;">
|
||||
<header>
|
||||
<div>
|
||||
<div class="pill">Admin</div>
|
||||
<h3>Sign in to manage models</h3>
|
||||
</div>
|
||||
</header>
|
||||
<form id="admin-login-form" class="admin-form">
|
||||
<label>
|
||||
Username
|
||||
<input id="admin-username" type="text" autocomplete="username" required />
|
||||
</label>
|
||||
<label>
|
||||
Password
|
||||
<input id="admin-password" type="password" autocomplete="current-password" required />
|
||||
</label>
|
||||
<button type="submit" class="primary">Sign in</button>
|
||||
<div id="admin-login-status" class="status-line" style="min-height: 20px;"></div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
<script>
|
||||
(function(){
|
||||
try {
|
||||
const navLinks = document.querySelectorAll('.sidebar-section a');
|
||||
navLinks.forEach((a) => {
|
||||
if (a.getAttribute('href') === window.location.pathname) {
|
||||
a.classList.add('active');
|
||||
a.setAttribute('aria-current', 'page');
|
||||
}
|
||||
});
|
||||
} catch (err) {}
|
||||
})();
|
||||
</script>
|
||||
<script src="/admin-login.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
88
chat/public/admin-login.js
Normal file
@@ -0,0 +1,88 @@
|
||||
(() => {
|
||||
const form = document.getElementById('admin-login-form');
|
||||
const statusEl = document.getElementById('admin-login-status');
|
||||
const userEl = document.getElementById('admin-username');
|
||||
const passEl = document.getElementById('admin-password');
|
||||
|
||||
function setStatus(msg, isError = false) {
|
||||
if (!statusEl) return;
|
||||
statusEl.textContent = msg || '';
|
||||
statusEl.style.color = isError ? 'var(--danger)' : 'inherit';
|
||||
}
|
||||
|
||||
async function ensureSession() {
|
||||
try {
|
||||
// Include credentials explicitly so cookies are reliably sent/received across browsers
|
||||
const res = await fetch('/api/admin/me', { credentials: 'same-origin' });
|
||||
if (!res.ok) return;
|
||||
const data = await res.json();
|
||||
if (data && data.ok) {
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
const next = params.get('next');
|
||||
|
||||
// Only redirect if we have a next parameter and we're not already on that page
|
||||
if (next && typeof next === 'string' && next.startsWith('/') && window.location.pathname !== next) {
|
||||
window.location.href = next;
|
||||
} else if (!next) {
|
||||
// If no next parameter, go to admin dashboard
|
||||
window.location.href = '/admin';
|
||||
}
|
||||
}
|
||||
} catch (_) {
|
||||
/* ignore */
|
||||
}
|
||||
}
|
||||
|
||||
form.addEventListener('submit', async (e) => {
|
||||
e.preventDefault();
|
||||
setStatus('Signing in...');
|
||||
try {
|
||||
const payload = {
|
||||
username: userEl.value.trim(),
|
||||
password: passEl.value.trim(),
|
||||
};
|
||||
// Ensure credentials are included so Set-Cookie is accepted and future requests send cookies
|
||||
const res = await fetch('/api/admin/login', {
|
||||
method: 'POST',
|
||||
credentials: 'same-origin',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(payload),
|
||||
});
|
||||
const data = await res.json().catch(() => ({}));
|
||||
if (!res.ok) throw new Error(data.error || 'Login failed');
|
||||
setStatus('Success, redirecting...');
|
||||
|
||||
// Respect optional `next` parameter (e.g. /admin/login?next=/test-checkout)
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
const next = params.get('next');
|
||||
|
||||
// Poll /api/admin/me to ensure the session cookie is active before redirecting.
|
||||
// This avoids a race where the next page immediately checks /api/admin/me and gets 401.
|
||||
let sessionActive = false;
|
||||
for (let i = 0; i < 6; i++) {
|
||||
try {
|
||||
const meRes = await fetch('/api/admin/me', { credentials: 'same-origin' });
|
||||
if (meRes.ok) {
|
||||
sessionActive = true;
|
||||
break;
|
||||
}
|
||||
} catch (_) {
|
||||
// ignore
|
||||
}
|
||||
// small backoff
|
||||
await new Promise((r) => setTimeout(r, 150 * (i + 1)));
|
||||
}
|
||||
|
||||
// Redirect regardless (session will usually be active) but polling reduces redirect loops
|
||||
if (next && typeof next === 'string' && next.startsWith('/')) {
|
||||
window.location.href = next;
|
||||
} else {
|
||||
window.location.href = '/admin';
|
||||
}
|
||||
} catch (err) {
|
||||
setStatus(err.message, true);
|
||||
}
|
||||
});
|
||||
|
||||
ensureSession();
|
||||
})();
|
||||
132
chat/public/admin-plan.html
Normal file
@@ -0,0 +1,132 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Admin Panel – Planning</title>
|
||||
<link rel="stylesheet" href="/styles.css" />
|
||||
|
||||
<!-- PostHog Analytics -->
|
||||
<script src="/posthog.js"></script>
|
||||
</head>
|
||||
<body data-page="plan">
|
||||
<div class="sidebar-overlay"></div>
|
||||
<div class="app-shell">
|
||||
<aside class="sidebar">
|
||||
<div class="brand">
|
||||
<div class="brand-mark">A</div>
|
||||
<div>
|
||||
<div class="brand-title">Admin</div>
|
||||
<div class="brand-sub">Site management</div>
|
||||
</div>
|
||||
<button id="close-sidebar" class="ghost" style="margin-left: auto; display: none;">×</button>
|
||||
</div>
|
||||
<div class="sidebar-section">
|
||||
<div class="section-heading">Navigation</div>
|
||||
<a class="ghost" href="/admin/build">Build models</a>
|
||||
<a class="ghost" href="/admin/plan">Plan models</a>
|
||||
<a class="ghost" href="/admin/plans">Plans</a>
|
||||
<a class="ghost" href="/admin/accounts">Accounts</a>
|
||||
<a class="ghost" href="/admin/affiliates">Affiliates</a>
|
||||
<a class="ghost" href="/admin/withdrawals">Withdrawals</a>
|
||||
<a class="ghost" href="/admin/tracking">Tracking</a>
|
||||
<a class="ghost" href="/admin/resources">Resources</a>
|
||||
<a class="ghost" href="/admin/contact-messages">Contact Messages</a>
|
||||
<a class="ghost" href="/admin/login">Login</a>
|
||||
</div>
|
||||
</aside>
|
||||
<main class="main">
|
||||
<div class="admin-shell">
|
||||
<div class="topbar" style="margin-bottom: 12px;">
|
||||
<button id="menu-toggle">
|
||||
<span></span><span></span><span></span>
|
||||
</button>
|
||||
<div>
|
||||
<div class="pill">Admin</div>
|
||||
<div class="title" style="margin-top: 6px;">Planning Control</div>
|
||||
<div class="crumb">Fallback-ready planning across OpenRouter, Mistral, Google, Groq, and NVIDIA.</div>
|
||||
</div>
|
||||
<div class="admin-actions">
|
||||
<button id="admin-refresh" class="ghost">Refresh</button>
|
||||
<button id="admin-logout" class="primary">Logout</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="admin-card">
|
||||
<header>
|
||||
<h3>Planning Priority</h3>
|
||||
<div class="pill">Planning</div>
|
||||
</header>
|
||||
<p class="muted" style="margin-top:0;">One row per planning model. Highest priority runs first and automatically falls back on errors or rate limits.</p>
|
||||
<div id="plan-priority-list" class="admin-list"></div>
|
||||
<div class="admin-actions" style="margin-top: 12px;">
|
||||
<button type="button" id="add-plan-row" class="ghost">Add planning model</button>
|
||||
<div class="status-line" id="plan-chain-status"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="admin-card" style="margin-top: 16px;">
|
||||
<header>
|
||||
<h3>Rate Limits & Usage</h3>
|
||||
<div class="pill">Shared</div>
|
||||
</header>
|
||||
<p style="margin-top:0; color: var(--muted);">Limits here apply to both planning and build traffic. Set provider/model RPM/TPM caps and monitor live usage.</p>
|
||||
<form id="provider-limit-form" class="admin-form">
|
||||
<label>
|
||||
Provider
|
||||
<select id="limit-provider">
|
||||
<option value="openrouter">OpenRouter</option>
|
||||
<option value="mistral">Mistral</option>
|
||||
<option value="google">Google</option>
|
||||
<option value="groq">Groq</option>
|
||||
<option value="nvidia">NVIDIA</option>
|
||||
<option value="opencode">OpenCode</option>
|
||||
</select>
|
||||
</label>
|
||||
<label>
|
||||
Scope
|
||||
<select id="limit-scope">
|
||||
<option value="provider">Per Provider</option>
|
||||
<option value="model">Per Model</option>
|
||||
</select>
|
||||
</label>
|
||||
<label>
|
||||
Model (for per-model limits)
|
||||
<select id="limit-model">
|
||||
<option value="">Any model</option>
|
||||
</select>
|
||||
<input id="limit-model-input" type="text" placeholder="Type a model id (e.g. mistral-large-latest)" style="display:none; margin-top:8px;" list="available-model-datalist" />
|
||||
</label>
|
||||
<label>
|
||||
Tokens per minute
|
||||
<input id="limit-tpm" type="number" min="0" step="1" placeholder="0 = unlimited" />
|
||||
</label>
|
||||
<label>
|
||||
Tokens per day
|
||||
<input id="limit-tpd" type="number" min="0" step="1" placeholder="0 = unlimited" />
|
||||
</label>
|
||||
<label>
|
||||
Requests per minute
|
||||
<input id="limit-rpm" type="number" min="0" step="1" placeholder="0 = unlimited" />
|
||||
</label>
|
||||
<label>
|
||||
Requests per day
|
||||
<input id="limit-rpd" type="number" min="0" step="1" placeholder="0 = unlimited" />
|
||||
</label>
|
||||
<label>
|
||||
Opencode backup model
|
||||
<input id="limit-backup" type="text" placeholder="Backup model when all providers fail" />
|
||||
</label>
|
||||
<div class="admin-actions">
|
||||
<button type="submit" class="primary">Save limits</button>
|
||||
</div>
|
||||
<div class="status-line" id="provider-limit-status"></div>
|
||||
</form>
|
||||
<div id="provider-usage" class="admin-list"></div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
<script src="/admin.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
98
chat/public/admin-plans.html
Normal file
@@ -0,0 +1,98 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>Admin Panel – Plans</title>
|
||||
<link rel="stylesheet" href="/styles.css" />
|
||||
|
||||
<!-- PostHog Analytics -->
|
||||
<script src="/posthog.js"></script>
|
||||
</head>
|
||||
<body data-page="plans">
|
||||
<div class="sidebar-overlay"></div>
|
||||
<div class="app-shell">
|
||||
<aside class="sidebar">
|
||||
<div class="brand">
|
||||
<div class="brand-mark">A</div>
|
||||
<div>
|
||||
<div class="brand-title">Admin</div>
|
||||
<div class="brand-sub">Site management</div>
|
||||
</div>
|
||||
<button id="close-sidebar" class="ghost" style="margin-left: auto; display: none;">×</button>
|
||||
</div>
|
||||
<div class="sidebar-section">
|
||||
<div class="section-heading">Navigation</div>
|
||||
<a class="ghost" href="/admin/build">Build models</a>
|
||||
<a class="ghost" href="/admin/plan">Plan models</a>
|
||||
<a class="ghost" href="/admin/plans">Plans</a>
|
||||
<a class="ghost" href="/admin/accounts">Accounts</a>
|
||||
<a class="ghost" href="/admin/affiliates">Affiliates</a>
|
||||
<a class="ghost" href="/admin/withdrawals">Withdrawals</a>
|
||||
<a class="ghost" href="/admin/tracking">Tracking</a>
|
||||
<a class="ghost" href="/admin/resources">Resources</a>
|
||||
<a class="ghost" href="/admin/contact-messages">Contact Messages</a>
|
||||
<a class="ghost" href="/admin/login">Login</a>
|
||||
</div>
|
||||
</aside>
|
||||
<main class="main">
|
||||
<div class="admin-shell">
|
||||
<div class="topbar" style="margin-bottom: 12px;">
|
||||
<button id="menu-toggle">
|
||||
<span></span><span></span><span></span>
|
||||
</button>
|
||||
<div>
|
||||
<div class="pill">Admin</div>
|
||||
<div class="title" style="margin-top: 6px;">Plan Token Limits</div>
|
||||
<div class="crumb">View and edit token allocations per plan and tier.</div>
|
||||
</div>
|
||||
<div class="admin-actions">
|
||||
<button id="admin-refresh" class="ghost">Refresh</button>
|
||||
<button id="admin-logout" class="primary">Logout</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="admin-card">
|
||||
<header>
|
||||
<h3>Plan token allocations</h3>
|
||||
<div class="pill">Tokens</div>
|
||||
</header>
|
||||
<p class="muted" style="margin-top:0;">Edit total tokens available per plan and per tier. Changes take effect immediately for new token calculations.</p>
|
||||
<div id="plan-tokens-table" class="admin-list"></div>
|
||||
<div class="admin-actions" style="margin-top:12px;">
|
||||
<button id="save-plan-tokens" class="primary">Save changes</button>
|
||||
<div class="status-line" id="plan-tokens-status"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="admin-card" style="margin-top: 16px;">
|
||||
<header>
|
||||
<h3>Token usage rates (overage)</h3>
|
||||
<div class="pill">Per 1M tokens</div>
|
||||
</header>
|
||||
<p class="muted" style="margin-top:0;">Set the exact rate charged per 1,000,000 overage tokens. Rates are in minor units (cents/pence).</p>
|
||||
<div class="admin-list">
|
||||
<div class="admin-row">
|
||||
<div style="min-width: 140px;"><strong>USD</strong></div>
|
||||
<input id="token-rate-usd" type="number" min="0" step="1" placeholder="250" style="max-width: 200px;" />
|
||||
</div>
|
||||
<div class="admin-row">
|
||||
<div style="min-width: 140px;"><strong>GBP</strong></div>
|
||||
<input id="token-rate-gbp" type="number" min="0" step="1" placeholder="200" style="max-width: 200px;" />
|
||||
</div>
|
||||
<div class="admin-row">
|
||||
<div style="min-width: 140px;"><strong>EUR</strong></div>
|
||||
<input id="token-rate-eur" type="number" min="0" step="1" placeholder="250" style="max-width: 200px;" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="admin-actions" style="margin-top:12px;">
|
||||
<button id="save-token-rates" class="primary">Save rates</button>
|
||||
<div class="status-line" id="token-rates-status"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
<script src="/admin.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
962
chat/public/admin-resources.html
Normal file
@@ -0,0 +1,962 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Admin - Resource Usage</title>
|
||||
<link rel="stylesheet" href="/styles.css" />
|
||||
<style>
|
||||
.stats-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
|
||||
gap: 16px;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
.stat-card {
|
||||
background: var(--surface);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 8px;
|
||||
padding: 16px;
|
||||
}
|
||||
.stat-value {
|
||||
font-size: 28px;
|
||||
font-weight: bold;
|
||||
color: var(--text);
|
||||
margin: 8px 0;
|
||||
}
|
||||
.stat-value.warning { color: var(--warning); }
|
||||
.stat-value.danger { color: var(--danger); }
|
||||
.stat-label {
|
||||
color: var(--muted);
|
||||
font-size: 13px;
|
||||
}
|
||||
.section-card {
|
||||
background: var(--surface);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 8px;
|
||||
padding: 20px;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
.section-title {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
margin-bottom: 16px;
|
||||
color: var(--text);
|
||||
}
|
||||
.data-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
font-size: 13px;
|
||||
}
|
||||
.data-table th {
|
||||
background: var(--background);
|
||||
padding: 10px 12px;
|
||||
text-align: left;
|
||||
font-weight: 600;
|
||||
color: var(--muted);
|
||||
border-bottom: 1px solid var(--border);
|
||||
white-space: nowrap;
|
||||
}
|
||||
.data-table td {
|
||||
padding: 10px 12px;
|
||||
border-bottom: 1px solid var(--border);
|
||||
color: var(--text);
|
||||
max-width: 300px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.data-table tr:last-child td {
|
||||
border-bottom: none;
|
||||
}
|
||||
.data-table tr:hover {
|
||||
background: var(--background);
|
||||
}
|
||||
.memory-bar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
.memory-bar-track {
|
||||
flex: 1;
|
||||
height: 8px;
|
||||
background: var(--background);
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
}
|
||||
.memory-bar-fill {
|
||||
height: 100%;
|
||||
border-radius: 4px;
|
||||
transition: width 0.3s ease;
|
||||
}
|
||||
.memory-bar-fill.low { background: var(--success); }
|
||||
.memory-bar-fill.medium { background: var(--warning); }
|
||||
.memory-bar-fill.high { background: var(--danger); }
|
||||
.badge {
|
||||
display: inline-block;
|
||||
padding: 2px 8px;
|
||||
border-radius: 4px;
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
.badge.running { background: rgba(46, 204, 113, 0.15); color: var(--success); }
|
||||
.badge.queued { background: rgba(241, 196, 15, 0.15); color: var(--warning); }
|
||||
.badge.done { background: rgba(52, 152, 219, 0.15); color: var(--info); }
|
||||
.badge.error { background: rgba(231, 76, 60, 0.15); color: var(--danger); }
|
||||
.empty-state {
|
||||
text-align: center;
|
||||
padding: 48px 24px;
|
||||
color: var(--muted);
|
||||
}
|
||||
.pill {
|
||||
display: inline-block;
|
||||
padding: 2px 8px;
|
||||
border-radius: 4px;
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
background: var(--background);
|
||||
color: var(--muted);
|
||||
}
|
||||
.pill.active { background: rgba(46, 204, 113, 0.15); color: var(--success); }
|
||||
.code {
|
||||
font-family: 'SF Mono', Monaco, 'Courier New', monospace;
|
||||
font-size: 12px;
|
||||
background: var(--background);
|
||||
padding: 2px 6px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
.collapsible-header {
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 8px 0;
|
||||
}
|
||||
.collapsible-header:hover {
|
||||
opacity: 0.8;
|
||||
}
|
||||
.collapsible-content {
|
||||
display: none;
|
||||
padding: 12px 0 0 0;
|
||||
}
|
||||
.collapsible-content.open {
|
||||
display: block;
|
||||
}
|
||||
.collapsible-arrow {
|
||||
transition: transform 0.2s ease;
|
||||
}
|
||||
.collapsible-header.open .collapsible-arrow {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
.nested-table {
|
||||
margin-left: 24px;
|
||||
width: calc(100% - 24px);
|
||||
}
|
||||
.session-row {
|
||||
cursor: pointer;
|
||||
}
|
||||
.session-row:hover {
|
||||
background: var(--background);
|
||||
}
|
||||
.memory-breakdown {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||
gap: 16px;
|
||||
margin-top: 16px;
|
||||
}
|
||||
.breakdown-item {
|
||||
background: var(--background);
|
||||
border-radius: 8px;
|
||||
padding: 12px;
|
||||
}
|
||||
.breakdown-value {
|
||||
font-size: 20px;
|
||||
font-weight: bold;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
.breakdown-label {
|
||||
font-size: 12px;
|
||||
color: var(--muted);
|
||||
}
|
||||
.refresh-indicator {
|
||||
display: inline-block;
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: 50%;
|
||||
margin-right: 8px;
|
||||
animation: pulse 2s infinite;
|
||||
}
|
||||
.refresh-indicator.active {
|
||||
background: var(--success);
|
||||
}
|
||||
@keyframes pulse {
|
||||
0%, 100% { opacity: 1; }
|
||||
50% { opacity: 0.5; }
|
||||
}
|
||||
.two-col {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(400px, 1fr));
|
||||
gap: 24px;
|
||||
}
|
||||
</style>
|
||||
<script src="/admin.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div class="sidebar-overlay"></div>
|
||||
<div class="app-shell">
|
||||
<aside class="sidebar">
|
||||
<div class="brand">
|
||||
<div class="brand-mark">A</div>
|
||||
<div>
|
||||
<div class="brand-title">Admin</div>
|
||||
<div class="brand-sub">Site management</div>
|
||||
</div>
|
||||
<button id="close-sidebar" class="ghost" style="margin-left: auto; display: none;">×</button>
|
||||
</div>
|
||||
<div class="sidebar-section">
|
||||
<div class="section-heading">Navigation</div>
|
||||
<a class="ghost" href="/admin/build">Build models</a>
|
||||
<a class="ghost" href="/admin/plan">Plan models</a>
|
||||
<a class="ghost" href="/admin/plans">Plans</a>
|
||||
<a class="ghost" href="/admin/accounts">Accounts</a>
|
||||
<a class="ghost" href="/admin/affiliates">Affiliates</a>
|
||||
<a class="ghost" href="/admin/withdrawals">Withdrawals</a>
|
||||
<a class="ghost" href="/admin/tracking">Tracking</a>
|
||||
<a class="ghost active" href="/admin/resources">Resources</a>
|
||||
<a class="ghost" href="/admin/contact-messages">Contact Messages</a>
|
||||
<a class="ghost" href="/admin/login">Login</a>
|
||||
</div>
|
||||
</aside>
|
||||
<main class="main">
|
||||
<div class="admin-shell">
|
||||
<div class="topbar" style="margin-bottom: 24px;">
|
||||
<button id="menu-toggle">
|
||||
<span></span><span></span><span></span>
|
||||
</button>
|
||||
<div>
|
||||
<div class="pill">Admin</div>
|
||||
<div class="title" style="margin-top: 6px;">Resource Usage</div>
|
||||
<div class="crumb">Memory and CPU allocation breakdown by session and message.</div>
|
||||
</div>
|
||||
<div class="admin-actions">
|
||||
<button id="auto-refresh" class="ghost">Auto Refresh: ON</button>
|
||||
<button id="admin-refresh" class="ghost">Refresh</button>
|
||||
<button id="admin-logout" class="primary">Logout</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- System Overview -->
|
||||
<div class="section-card">
|
||||
<div class="section-title">
|
||||
<span class="refresh-indicator active" id="refresh-indicator"></span>
|
||||
System Overview
|
||||
</div>
|
||||
<div class="stats-grid" id="system-stats">
|
||||
<div class="stat-card" style="grid-column: 1 / -1; text-align: center; padding: 32px;">
|
||||
<div style="color: var(--muted);">Loading system overview...</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Memory Breakdown -->
|
||||
<div class="section-card">
|
||||
<div class="section-title">Memory Breakdown</div>
|
||||
<div class="stats-grid" id="memory-stats">
|
||||
<div class="stat-card" style="grid-column: 1 / -1; text-align: center; padding: 32px;">
|
||||
<div style="color: var(--muted);">Loading memory stats...</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="memory-bar" style="margin-top: 16px;">
|
||||
<span style="min-width: 80px;">RSS:</span>
|
||||
<div class="memory-bar-track">
|
||||
<div class="memory-bar-fill" id="memory-bar-rss"></div>
|
||||
</div>
|
||||
<span id="memory-bar-rss-text" class="code">0 MB / 0 MB</span>
|
||||
</div>
|
||||
<div class="memory-bar" style="margin-top: 8px;">
|
||||
<span style="min-width: 80px;">Heap:</span>
|
||||
<div class="memory-bar-track">
|
||||
<div class="memory-bar-fill" id="memory-bar-heap"></div>
|
||||
</div>
|
||||
<span id="memory-bar-heap-text" class="code">0 MB / 0 MB</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- CPU & Load -->
|
||||
<div class="section-card">
|
||||
<div class="section-title">CPU & System Load</div>
|
||||
<div class="stats-grid" id="cpu-stats">
|
||||
<div class="stat-card" style="grid-column: 1 / -1; text-align: center; padding: 32px;">
|
||||
<div style="color: var(--muted);">Loading CPU stats...</div>
|
||||
</div>
|
||||
</div>
|
||||
<div style="margin-top: 12px; font-size: 13px; color: var(--muted);">
|
||||
Load Average (1m / 5m / 15m): <span id="load-avg" class="code">- / - / -</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Two Column: Sessions & Processes -->
|
||||
<div class="two-col">
|
||||
<!-- Active Sessions -->
|
||||
<div class="section-card">
|
||||
<div class="section-title">Sessions by Memory Usage</div>
|
||||
<div id="sessions-table-container">
|
||||
<table class="data-table" id="sessions-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Session</th>
|
||||
<th>Messages</th>
|
||||
<th>Running</th>
|
||||
<th>Memory</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="sessions-tbody">
|
||||
<tr><td colspan="4" class="empty-state">Loading...</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Running Processes -->
|
||||
<div class="section-card">
|
||||
<div class="section-title">Running Processes</div>
|
||||
<div id="processes-table-container">
|
||||
<table class="data-table" id="processes-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Message</th>
|
||||
<th>Session</th>
|
||||
<th>Age</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="processes-tbody">
|
||||
<tr><td colspan="3" class="empty-state">No running processes</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- OpenCode & Streams -->
|
||||
<div class="two-col">
|
||||
<!-- OpenCode Instances -->
|
||||
<div class="section-card">
|
||||
<div class="section-title">OpenCode Process Manager</div>
|
||||
<div id="opencode-stats">
|
||||
<div class="breakdown-item" style="grid-column: 1 / -1; text-align: center; padding: 16px;">
|
||||
<div style="color: var(--muted);">Loading OpenCode stats...</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Active Streams -->
|
||||
<div class="section-card">
|
||||
<div class="section-title">Active SSE Streams</div>
|
||||
<div id="streams-stats">
|
||||
<div class="breakdown-item" style="grid-column: 1 / -1; text-align: center; padding: 16px;">
|
||||
<div style="color: var(--muted);">Loading streams stats...</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="streams-table-container" style="margin-top: 12px;">
|
||||
<table class="data-table" id="streams-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Message</th>
|
||||
<th>Session</th>
|
||||
<th>Streams</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="streams-tbody">
|
||||
<tr><td colspan="3" class="empty-state">No active streams</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Child Processes -->
|
||||
<div class="section-card">
|
||||
<div class="section-title">Child Processes</div>
|
||||
<div id="child-processes-table-container">
|
||||
<table class="data-table" id="child-processes-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>PID</th>
|
||||
<th>Session</th>
|
||||
<th>Message</th>
|
||||
<th>Age</th>
|
||||
<th>Started</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="child-processes-tbody">
|
||||
<tr><td colspan="5" class="empty-state">Loading...</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Session Details (Collapsible) -->
|
||||
<div class="section-card" id="session-details-section" style="display: none;">
|
||||
<div class="section-title">Session Details</div>
|
||||
<div id="selected-session-info" style="margin-bottom: 16px; padding: 12px; background: var(--background); border-radius: 8px;">
|
||||
<!-- Filled by JS -->
|
||||
</div>
|
||||
<div class="collapsible-header" onclick="toggleMessages()">
|
||||
<span>Messages in Session</span>
|
||||
<span class="collapsible-arrow">▶</span>
|
||||
</div>
|
||||
<div class="collapsible-content" id="messages-content">
|
||||
<table class="data-table" id="messages-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>Role</th>
|
||||
<th>Status</th>
|
||||
<th>Model</th>
|
||||
<th>Content</th>
|
||||
<th>Reply</th>
|
||||
<th>Memory</th>
|
||||
<th>Created</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="messages-tbody">
|
||||
<!-- Filled by JS -->
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Maps & Data Structures -->
|
||||
<div class="section-card">
|
||||
<div class="section-title">Internal Data Structures</div>
|
||||
<div class="stats-grid" id="maps-stats">
|
||||
<div class="breakdown-item" style="grid-column: 1 / -1; text-align: center; padding: 16px;">
|
||||
<div style="color: var(--muted);">Loading maps stats...</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
let resourceData = null;
|
||||
let autoRefresh = true;
|
||||
let refreshInterval = null;
|
||||
let loadError = null;
|
||||
|
||||
const byId = (id) => document.getElementById(id);
|
||||
|
||||
function showError(message) {
|
||||
loadError = message;
|
||||
const errorHtml = `
|
||||
<div class="stat-card" style="grid-column: 1 / -1; text-align: center; padding: 32px; border-color: var(--danger);">
|
||||
<div style="color: var(--danger); font-weight: 600; margin-bottom: 8px;">Error Loading Data</div>
|
||||
<div style="color: var(--muted); font-size: 13px;">${escapeHtml(message)}</div>
|
||||
<div style="margin-top: 16px;">
|
||||
<button onclick="loadResources()" class="ghost">Retry</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
byId('system-stats').innerHTML = errorHtml;
|
||||
byId('memory-stats').innerHTML = errorHtml;
|
||||
byId('cpu-stats').innerHTML = errorHtml;
|
||||
byId('sessions-tbody').innerHTML = `<tr><td colspan="4" class="empty-state" style="color: var(--danger);">${escapeHtml(message)}</td></tr>`;
|
||||
byId('processes-tbody').innerHTML = `<tr><td colspan="3" class="empty-state" style="color: var(--danger);">${escapeHtml(message)}</td></tr>`;
|
||||
byId('child-processes-tbody').innerHTML = `<tr><td colspan="5" class="empty-state" style="color: var(--danger);">${escapeHtml(message)}</td></tr>`;
|
||||
byId('streams-tbody').innerHTML = `<tr><td colspan="3" class="empty-state" style="color: var(--danger);">${escapeHtml(message)}</td></tr>`;
|
||||
}
|
||||
|
||||
function formatBytes(bytes) {
|
||||
if (bytes < 1024) return bytes + ' B';
|
||||
if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + ' KB';
|
||||
if (bytes < 1024 * 1024 * 1024) return (bytes / (1024 * 1024)).toFixed(1) + ' MB';
|
||||
return (bytes / (1024 * 1024 * 1024)).toFixed(2) + ' GB';
|
||||
}
|
||||
|
||||
function formatNumber(value) {
|
||||
return (Number(value) || 0).toLocaleString();
|
||||
}
|
||||
|
||||
function formatDuration(ms) {
|
||||
if (ms < 1000) return ms + 'ms';
|
||||
if (ms < 60000) return (ms / 1000).toFixed(1) + 's';
|
||||
if (ms < 3600000) return (ms / 60000).toFixed(1) + 'm';
|
||||
if (ms < 86400000) return (ms / 3600000).toFixed(1) + 'h';
|
||||
return (ms / 86400000).toFixed(1) + 'd';
|
||||
}
|
||||
|
||||
function formatUptime(seconds) {
|
||||
const days = Math.floor(seconds / 86400);
|
||||
const hours = Math.floor((seconds % 86400) / 3600);
|
||||
const mins = Math.floor((seconds % 3600) / 60);
|
||||
const secs = Math.floor(seconds % 60);
|
||||
if (days > 0) return `${days}d ${hours}h ${mins}m`;
|
||||
if (hours > 0) return `${hours}h ${mins}m ${secs}s`;
|
||||
if (mins > 0) return `${mins}m ${secs}s`;
|
||||
return `${secs}s`;
|
||||
}
|
||||
|
||||
function setText(id, value) {
|
||||
const el = byId(id);
|
||||
if (!el) return;
|
||||
el.textContent = value;
|
||||
}
|
||||
|
||||
function getMemoryBarColor(percent) {
|
||||
if (percent < 50) return 'low';
|
||||
if (percent < 80) return 'medium';
|
||||
return 'high';
|
||||
}
|
||||
|
||||
function updateMemoryBars() {
|
||||
if (!resourceData) return;
|
||||
|
||||
const sys = resourceData.system;
|
||||
const limits = sys.limits;
|
||||
const memory = sys.memory;
|
||||
|
||||
// RSS bar
|
||||
const rssPercent = Math.min(100, (memory.raw.rss / limits.memoryBytes) * 100);
|
||||
byId('memory-bar-rss').style.width = rssPercent + '%';
|
||||
byId('memory-bar-rss').className = 'memory-bar-fill ' + getMemoryBarColor(rssPercent);
|
||||
byId('memory-bar-rss-text').textContent = `${memory.rss} / ${limits.memoryMb} (${rssPercent.toFixed(1)}%)`;
|
||||
|
||||
// Heap bar
|
||||
const heapPercent = Math.min(100, (memory.raw.heapUsed / limits.memoryBytes) * 100);
|
||||
byId('memory-bar-heap').style.width = heapPercent + '%';
|
||||
byId('memory-bar-heap').className = 'memory-bar-fill ' + getMemoryBarColor(heapPercent);
|
||||
byId('memory-bar-heap-text').textContent = `${memory.heapUsed} / ${limits.memoryMb} (${heapPercent.toFixed(1)}%)`;
|
||||
|
||||
// Load average
|
||||
const cpu = sys.cpu;
|
||||
setText('load-avg', `${cpu.loadAvg1m} / ${cpu.loadAvg5m} / ${cpu.loadAvg15m}`);
|
||||
}
|
||||
|
||||
function renderSystemStats() {
|
||||
if (!resourceData) return;
|
||||
|
||||
const sys = resourceData.system;
|
||||
const totals = resourceData.totals;
|
||||
|
||||
byId('system-stats').innerHTML = `
|
||||
<div class="stat-card">
|
||||
<div class="stat-label">Total Sessions</div>
|
||||
<div class="stat-value">${formatNumber(totals.sessions)}</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-label">Total Messages</div>
|
||||
<div class="stat-value">${formatNumber(totals.totalMessages)}</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-label">Running Messages</div>
|
||||
<div class="stat-value" style="${totals.runningMessages > 0 ? 'color: var(--success);' : ''}">${formatNumber(totals.runningMessages)}</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-label">Queued Messages</div>
|
||||
<div class="stat-value" style="${totals.queuedMessages > 0 ? 'color: var(--warning);' : ''}">${formatNumber(totals.queuedMessages)}</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-label">Error Messages</div>
|
||||
<div class="stat-value" style="${totals.errorMessages > 0 ? 'color: var(--danger);' : ''}">${formatNumber(totals.errorMessages)}</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-label">Process Uptime</div>
|
||||
<div class="stat-value">${sys.process.uptimeFormatted}</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-label">OpenCode Instances</div>
|
||||
<div class="stat-value" style="${resourceData.opencode.runningInstances > 0 ? 'color: var(--success);' : ''}">${formatNumber(resourceData.opencode.runningInstances)}</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
byId('memory-stats').innerHTML = `
|
||||
<div class="stat-card">
|
||||
<div class="stat-label">RSS Memory</div>
|
||||
<div class="stat-value">${sys.memory.rss}</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-label">Heap Used</div>
|
||||
<div class="stat-value">${sys.memory.heapUsed}</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-label">Heap Total</div>
|
||||
<div class="stat-value">${sys.memory.heapTotal}</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-label">External</div>
|
||||
<div class="stat-value">${sys.memory.external}</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-label">Est. Session Memory</div>
|
||||
<div class="stat-value">${totals.totalEstimatedMemoryMb}</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-label">Memory Limit</div>
|
||||
<div class="stat-value">${sys.limits.memoryMb}</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
byId('cpu-stats').innerHTML = `
|
||||
<div class="stat-card">
|
||||
<div class="stat-label">CPU User</div>
|
||||
<div class="stat-value">${sys.cpu.userPercent}</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-label">CPU System</div>
|
||||
<div class="stat-value">${sys.cpu.systemPercent}</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-label">Load 1m</div>
|
||||
<div class="stat-value">${sys.cpu.loadAvg1m}</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-label">CPU Cores</div>
|
||||
<div class="stat-value">${sys.limits.cpuCores}</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
updateMemoryBars();
|
||||
}
|
||||
|
||||
function renderSessions() {
|
||||
if (!resourceData) return;
|
||||
|
||||
const sessions = resourceData.sessions;
|
||||
const tbody = byId('sessions-tbody');
|
||||
|
||||
if (!sessions || sessions.length === 0) {
|
||||
tbody.innerHTML = '<tr><td colspan="4" class="empty-state">No active sessions</td></tr>';
|
||||
return;
|
||||
}
|
||||
|
||||
tbody.innerHTML = sessions.slice(0, 20).map(session => `
|
||||
<tr class="session-row" onclick="showSessionDetails('${session.id}')">
|
||||
<td>
|
||||
<div style="font-weight: 600;">${escapeHtml(session.title || 'Untitled')}</div>
|
||||
<div class="pill">${session.cli}</div>
|
||||
${session.appId ? `<div class="pill" style="margin-left: 4px;">${escapeHtml(session.appId)}</div>` : ''}
|
||||
<div style="font-size: 11px; color: var(--muted); margin-top: 4px;">${session.id.slice(0, 8)}...</div>
|
||||
</td>
|
||||
<td>
|
||||
<span class="code">${session.messageCount}</span>
|
||||
${session.errorMessages > 0 ? `<span class="badge error">${session.errorMessages} errors</span>` : ''}
|
||||
</td>
|
||||
<td>
|
||||
${session.runningMessages > 0 ? `<span class="badge running">${session.runningMessages} running</span>` : '-'}
|
||||
${session.queuedMessages > 0 ? `<span class="badge queued" style="margin-left: 4px;">${session.queuedMessages} queued</span>` : ''}
|
||||
</td>
|
||||
<td>
|
||||
<span class="code">${session.estimatedSessionMemoryKb}</span>
|
||||
<div style="font-size: 11px; color: var(--muted);">${session.totalMessageMemoryKb} messages</div>
|
||||
</td>
|
||||
</tr>
|
||||
`).join('');
|
||||
}
|
||||
|
||||
function renderProcesses() {
|
||||
if (!resourceData) return;
|
||||
|
||||
const processes = resourceData.runningProcesses;
|
||||
const tbody = byId('processes-tbody');
|
||||
|
||||
if (!processes || processes.length === 0) {
|
||||
tbody.innerHTML = '<tr><td colspan="3" class="empty-state">No running processes</td></tr>';
|
||||
return;
|
||||
}
|
||||
|
||||
tbody.innerHTML = processes.map(proc => `
|
||||
<tr>
|
||||
<td>
|
||||
<span class="code">${proc.messageId.slice(0, 8)}...</span>
|
||||
<div style="font-size: 11px; color: var(--muted); margin-top: 4px;">${escapeHtml(proc.messagePreview || '').slice(0, 50)}</div>
|
||||
</td>
|
||||
<td><span class="code">${proc.sessionId ? proc.sessionId.slice(0, 8) + '...' : '-'}</span></td>
|
||||
<td><span class="pill active">${proc.age}</span></td>
|
||||
</tr>
|
||||
`).join('');
|
||||
}
|
||||
|
||||
function renderChildProcesses() {
|
||||
if (!resourceData) return;
|
||||
|
||||
const childProcs = resourceData.childProcesses;
|
||||
const tbody = byId('child-processes-tbody');
|
||||
|
||||
if (!childProcs || childProcs.length === 0) {
|
||||
tbody.innerHTML = '<tr><td colspan="5" class="empty-state">No child processes</td></tr>';
|
||||
return;
|
||||
}
|
||||
|
||||
tbody.innerHTML = childProcs.map(proc => `
|
||||
<tr>
|
||||
<td><span class="code">${proc.pid}</span></td>
|
||||
<td><span class="code">${proc.sessionId ? proc.sessionId.slice(0, 8) + '...' : '-'}</span></td>
|
||||
<td><span class="code">${proc.messageId ? proc.messageId.slice(0, 8) + '...' : '-'}</span></td>
|
||||
<td><span class="pill active">${proc.age}</span></td>
|
||||
<td style="font-size: 12px; color: var(--muted);">${new Date(proc.startTime).toLocaleString()}</td>
|
||||
</tr>
|
||||
`).join('');
|
||||
}
|
||||
|
||||
function renderStreams() {
|
||||
if (!resourceData) return;
|
||||
|
||||
const streams = resourceData.activeStreams;
|
||||
const tbody = byId('streams-tbody');
|
||||
|
||||
// OpenCode stats
|
||||
const opencode = resourceData.opencode;
|
||||
byId('opencode-stats').innerHTML = `
|
||||
<div class="breakdown-item">
|
||||
<div class="breakdown-value">${opencode.mode}</div>
|
||||
<div class="breakdown-label">Mode</div>
|
||||
</div>
|
||||
<div class="breakdown-item">
|
||||
<div class="breakdown-value" style="${opencode.isReady ? 'color: var(--success);' : 'color: var(--warning);'}">${opencode.isReady ? 'Ready' : 'Not Ready'}</div>
|
||||
<div class="breakdown-label">Status</div>
|
||||
</div>
|
||||
<div class="breakdown-item">
|
||||
<div class="breakdown-value">${opencode.pendingRequests}</div>
|
||||
<div class="breakdown-label">Pending Requests</div>
|
||||
</div>
|
||||
<div class="breakdown-item">
|
||||
<div class="breakdown-value">${opencode.sessionWorkspaces}</div>
|
||||
<div class="breakdown-label">Session Workspaces</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
// Streams stats
|
||||
byId('streams-stats').innerHTML = `
|
||||
<div class="breakdown-item">
|
||||
<div class="breakdown-value">${streams.length}</div>
|
||||
<div class="breakdown-label">Active Stream Groups</div>
|
||||
</div>
|
||||
<div class="breakdown-item">
|
||||
<div class="breakdown-value">${streams.reduce((sum, s) => sum + s.streamCount, 0)}</div>
|
||||
<div class="breakdown-label">Total Stream Connections</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
if (!streams || streams.length === 0) {
|
||||
tbody.innerHTML = '<tr><td colspan="3" class="empty-state">No active streams</td></tr>';
|
||||
return;
|
||||
}
|
||||
|
||||
tbody.innerHTML = streams.map(stream => `
|
||||
<tr>
|
||||
<td><span class="code">${stream.messageId.slice(0, 8)}...</span></td>
|
||||
<td><span class="code">${stream.sessionId ? stream.sessionId.slice(0, 8) + '...' : '-'}</span></td>
|
||||
<td><span class="pill">${stream.streamCount} connections</span></td>
|
||||
</tr>
|
||||
`).join('');
|
||||
}
|
||||
|
||||
function renderMaps() {
|
||||
if (!resourceData) return;
|
||||
|
||||
const maps = resourceData.maps;
|
||||
byId('maps-stats').innerHTML = `
|
||||
<div class="breakdown-item">
|
||||
<div class="breakdown-value">${formatNumber(maps.sessionQueues)}</div>
|
||||
<div class="breakdown-label">Session Queues</div>
|
||||
</div>
|
||||
<div class="breakdown-item">
|
||||
<div class="breakdown-value">${formatNumber(maps.activeStreams)}</div>
|
||||
<div class="breakdown-label">Active Stream Maps</div>
|
||||
</div>
|
||||
<div class="breakdown-item">
|
||||
<div class="breakdown-value">${formatNumber(maps.runningProcesses)}</div>
|
||||
<div class="breakdown-label">Running Process Maps</div>
|
||||
</div>
|
||||
<div class="breakdown-item">
|
||||
<div class="breakdown-value">${formatNumber(maps.childProcesses)}</div>
|
||||
<div class="breakdown-label">Child Process Maps</div>
|
||||
</div>
|
||||
<div class="breakdown-item">
|
||||
<div class="breakdown-value">${formatNumber(maps.oauthStates)}</div>
|
||||
<div class="breakdown-label">OAuth States</div>
|
||||
</div>
|
||||
<div class="breakdown-item">
|
||||
<div class="breakdown-value">${formatNumber(maps.loginAttempts)}</div>
|
||||
<div class="breakdown-label">Login Attempts</div>
|
||||
</div>
|
||||
<div class="breakdown-item">
|
||||
<div class="breakdown-value">${formatNumber(maps.apiRateLimit)}</div>
|
||||
<div class="breakdown-label">API Rate Limits</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
function showSessionDetails(sessionId) {
|
||||
if (!resourceData) return;
|
||||
|
||||
const session = resourceData.sessions.find(s => s.id === sessionId);
|
||||
if (!session) return;
|
||||
|
||||
const section = byId('session-details-section');
|
||||
section.style.display = 'block';
|
||||
|
||||
// Session info
|
||||
byId('selected-session-info').innerHTML = `
|
||||
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 16px;">
|
||||
<div>
|
||||
<div style="font-size: 12px; color: var(--muted);">Session ID</div>
|
||||
<div class="code">${session.id}</div>
|
||||
</div>
|
||||
<div>
|
||||
<div style="font-size: 12px; color: var(--muted);">User ID</div>
|
||||
<div class="code">${session.userId}</div>
|
||||
</div>
|
||||
<div>
|
||||
<div style="font-size: 12px; color: var(--muted);">Title</div>
|
||||
<div>${escapeHtml(session.title || 'Untitled')}</div>
|
||||
</div>
|
||||
<div>
|
||||
<div style="font-size: 12px; color: var(--muted);">App</div>
|
||||
<div>${session.appId ? escapeHtml(session.appId) : '-'}</div>
|
||||
</div>
|
||||
<div>
|
||||
<div style="font-size: 12px; color: var(--muted);">Model</div>
|
||||
<div class="pill">${session.model || session.cli}</div>
|
||||
</div>
|
||||
<div>
|
||||
<div style="font-size: 12px; color: var(--muted);">Age</div>
|
||||
<div>${session.age}</div>
|
||||
</div>
|
||||
<div>
|
||||
<div style="font-size: 12px; color: var(--muted);">Created</div>
|
||||
<div style="font-size: 12px;">${new Date(session.createdAt).toLocaleString()}</div>
|
||||
</div>
|
||||
<div>
|
||||
<div style="font-size: 12px; color: var(--muted);">Workspace</div>
|
||||
<div class="code" style="font-size: 11px;">${session.workspaceDir || '-'}</div>
|
||||
</div>
|
||||
<div>
|
||||
<div style="font-size: 12px; color: var(--muted);">OpenCode Session</div>
|
||||
<div class="code" style="font-size: 11px;">${session.opencodeSessionId || '-'}</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
// Messages table
|
||||
const messages = session.messages || [];
|
||||
const tbody = byId('messages-tbody');
|
||||
|
||||
if (messages.length === 0) {
|
||||
tbody.innerHTML = '<tr><td colspan="8" class="empty-state">No messages in this session</td></tr>';
|
||||
} else {
|
||||
tbody.innerHTML = messages.map(msg => `
|
||||
<tr>
|
||||
<td><span class="code">${msg.id.slice(0, 8)}...</span></td>
|
||||
<td><span class="pill">${msg.role}</span></td>
|
||||
<td><span class="badge ${msg.status}">${msg.status}</span></td>
|
||||
<td><span class="code" style="font-size: 11px;">${escapeHtml(msg.model || '')}</span></td>
|
||||
<td style="max-width: 150px;">
|
||||
<div style="overflow: hidden; text-overflow: ellipsis; white-space: nowrap;" title="${escapeHtml(msg.content || '')}">
|
||||
${escapeHtml(msg.content || '-').slice(0, 50)}
|
||||
</div>
|
||||
</td>
|
||||
<td style="max-width: 150px;">
|
||||
<div style="overflow: hidden; text-overflow: ellipsis; white-space: nowrap;" title="${escapeHtml(msg.reply || '')}">
|
||||
${escapeHtml(msg.reply || '-').slice(0, 50)}
|
||||
</div>
|
||||
</td>
|
||||
<td><span class="code">${msg.estimatedMemoryKb}</span></td>
|
||||
<td style="font-size: 11px; color: var(--muted);">${new Date(msg.createdAt).toLocaleString()}</td>
|
||||
</tr>
|
||||
`).join('');
|
||||
}
|
||||
|
||||
// Scroll to section
|
||||
section.scrollIntoView({ behavior: 'smooth' });
|
||||
}
|
||||
|
||||
function toggleMessages() {
|
||||
const content = byId('messages-content');
|
||||
const header = content.previousElementSibling;
|
||||
content.classList.toggle('open');
|
||||
header.classList.toggle('open');
|
||||
}
|
||||
|
||||
function escapeHtml(str) {
|
||||
if (!str) return '';
|
||||
return String(str)
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"')
|
||||
.replace(/'/g, ''');
|
||||
}
|
||||
|
||||
async function loadResources() {
|
||||
try {
|
||||
// Ensure credentials are included so admin session cookie is sent
|
||||
const response = await fetch('/api/admin/resources', { credentials: 'same-origin' });
|
||||
if (!response.ok) {
|
||||
if (response.status === 401) {
|
||||
window.location.href = '/admin/login';
|
||||
return;
|
||||
}
|
||||
const errorData = await response.json().catch(() => ({}));
|
||||
throw new Error(errorData.error || `HTTP ${response.status}: ${response.statusText}`);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
resourceData = data;
|
||||
loadError = null;
|
||||
|
||||
renderSystemStats();
|
||||
renderSessions();
|
||||
renderProcesses();
|
||||
renderChildProcesses();
|
||||
renderStreams();
|
||||
renderMaps();
|
||||
|
||||
// Update timestamp
|
||||
if (data.timestamp) {
|
||||
setText('refresh-indicator', '');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error loading resources:', error);
|
||||
const errorMessage = error.message || 'Failed to load resources';
|
||||
showError(errorMessage);
|
||||
}
|
||||
}
|
||||
|
||||
function toggleAutoRefresh() {
|
||||
autoRefresh = !autoRefresh;
|
||||
byId('auto-refresh').textContent = autoRefresh ? 'Auto Refresh: ON' : 'Auto Refresh: OFF';
|
||||
byId('auto-refresh').classList.toggle('active', autoRefresh);
|
||||
|
||||
if (autoRefresh) {
|
||||
refreshInterval = setInterval(loadResources, 5000);
|
||||
} else {
|
||||
clearInterval(refreshInterval);
|
||||
}
|
||||
}
|
||||
|
||||
// Event listeners
|
||||
byId('admin-refresh').addEventListener('click', () => {
|
||||
loadResources();
|
||||
});
|
||||
|
||||
byId('auto-refresh').addEventListener('click', toggleAutoRefresh);
|
||||
|
||||
byId('admin-logout').addEventListener('click', async () => {
|
||||
try {
|
||||
await fetch('/api/admin/logout', { method: 'POST', credentials: 'same-origin' });
|
||||
window.location.href = '/admin/login';
|
||||
} catch (error) {
|
||||
console.error('Logout failed:', error);
|
||||
}
|
||||
});
|
||||
|
||||
// Initial load
|
||||
loadResources();
|
||||
refreshInterval = setInterval(loadResources, 5000);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
990
chat/public/admin-tracking.html
Normal file
@@ -0,0 +1,990 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Admin - Visitor Tracking</title>
|
||||
<link rel="stylesheet" href="/styles.css" />
|
||||
<style>
|
||||
.stats-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||
gap: 16px;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
.stat-card {
|
||||
background: var(--surface);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 8px;
|
||||
padding: 16px;
|
||||
}
|
||||
.stat-value {
|
||||
font-size: 32px;
|
||||
font-weight: bold;
|
||||
color: var(--text);
|
||||
margin: 8px 0;
|
||||
}
|
||||
.stat-label {
|
||||
color: var(--muted);
|
||||
font-size: 14px;
|
||||
}
|
||||
.chart-container {
|
||||
background: var(--surface);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 8px;
|
||||
padding: 20px;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
.chart-title {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
.bar-chart {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
.bar-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
.bar-label {
|
||||
min-width: 200px;
|
||||
font-size: 14px;
|
||||
color: var(--text);
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.bar-wrapper {
|
||||
flex: 1;
|
||||
height: 24px;
|
||||
background: var(--background);
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
}
|
||||
.bar-fill {
|
||||
height: 100%;
|
||||
background: linear-gradient(90deg, #667eea 0%, #764ba2 100%);
|
||||
transition: width 0.3s ease;
|
||||
}
|
||||
.bar-count {
|
||||
min-width: 60px;
|
||||
text-align: right;
|
||||
font-weight: 600;
|
||||
color: var(--text);
|
||||
}
|
||||
.table-container {
|
||||
background: var(--surface);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
.data-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
.data-table th {
|
||||
background: var(--background);
|
||||
padding: 12px;
|
||||
text-align: left;
|
||||
font-weight: 600;
|
||||
color: var(--muted);
|
||||
border-bottom: 1px solid var(--border);
|
||||
}
|
||||
.data-table td {
|
||||
padding: 12px;
|
||||
border-bottom: 1px solid var(--border);
|
||||
color: var(--text);
|
||||
}
|
||||
.data-table tr:last-child td {
|
||||
border-bottom: none;
|
||||
}
|
||||
.data-table tr:hover {
|
||||
background: var(--background);
|
||||
}
|
||||
.time-cell {
|
||||
color: var(--muted);
|
||||
font-size: 13px;
|
||||
}
|
||||
.path-cell {
|
||||
font-family: monospace;
|
||||
font-size: 13px;
|
||||
}
|
||||
.empty-state {
|
||||
text-align: center;
|
||||
padding: 48px 24px;
|
||||
color: var(--muted);
|
||||
}
|
||||
.line-chart {
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
height: 200px;
|
||||
gap: 4px;
|
||||
padding: 20px 0;
|
||||
}
|
||||
.line-bar {
|
||||
flex: 1;
|
||||
background: linear-gradient(180deg, #667eea 0%, #764ba2 100%);
|
||||
border-radius: 4px 4px 0 0;
|
||||
min-width: 8px;
|
||||
position: relative;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
.line-bar:hover {
|
||||
opacity: 0.8;
|
||||
}
|
||||
.line-bar-label {
|
||||
position: absolute;
|
||||
bottom: -25px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%) rotate(-45deg);
|
||||
font-size: 11px;
|
||||
color: var(--muted);
|
||||
white-space: nowrap;
|
||||
transform-origin: center;
|
||||
}
|
||||
.line-bar-value {
|
||||
position: absolute;
|
||||
top: -25px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
color: var(--text);
|
||||
}
|
||||
</style>
|
||||
|
||||
<!-- PostHog Analytics -->
|
||||
<script src="/posthog.js"></script>
|
||||
<script src="/admin.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div class="sidebar-overlay"></div>
|
||||
<div class="app-shell">
|
||||
<aside class="sidebar">
|
||||
<div class="brand">
|
||||
<div class="brand-mark">A</div>
|
||||
<div>
|
||||
<div class="brand-title">Admin</div>
|
||||
<div class="brand-sub">Site management</div>
|
||||
</div>
|
||||
<button id="close-sidebar" class="ghost" style="margin-left: auto; display: none;">×</button>
|
||||
</div>
|
||||
<div class="sidebar-section">
|
||||
<div class="section-heading">Navigation</div>
|
||||
<a class="ghost" href="/admin/build">Build models</a>
|
||||
<a class="ghost" href="/admin/plan">Plan models</a>
|
||||
<a class="ghost" href="/admin/plans">Plans</a>
|
||||
<a class="ghost" href="/admin/accounts">Accounts</a>
|
||||
<a class="ghost" href="/admin/affiliates">Affiliates</a>
|
||||
<a class="ghost" href="/admin/withdrawals">Withdrawals</a>
|
||||
<a class="ghost" href="/admin/tracking">Tracking</a>
|
||||
<a class="ghost" href="/admin/resources">Resources</a>
|
||||
<a class="ghost" href="/admin/contact-messages">Contact Messages</a>
|
||||
<a class="ghost" href="/admin/login">Login</a>
|
||||
</div>
|
||||
</aside>
|
||||
<main class="main">
|
||||
<div class="admin-shell">
|
||||
<div class="topbar" style="margin-bottom: 24px;">
|
||||
<button id="menu-toggle">
|
||||
<span></span><span></span><span></span>
|
||||
</button>
|
||||
<div>
|
||||
<div class="pill">Admin</div>
|
||||
<div class="title" style="margin-top: 6px;">Visitor Tracking</div>
|
||||
<div class="crumb">Analytics and visitor statistics for your application.</div>
|
||||
</div>
|
||||
<div class="admin-actions">
|
||||
<button id="admin-refresh" class="ghost">Refresh</button>
|
||||
<button id="admin-logout" class="primary">Logout</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Enhanced Analytics Overview -->
|
||||
<div class="stats-grid">
|
||||
<div class="stat-card">
|
||||
<div class="stat-label">DAU (Daily Active)</div>
|
||||
<div class="stat-value" id="dau">-</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-label">WAU (Weekly Active)</div>
|
||||
<div class="stat-value" id="wau">-</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-label">MAU (Monthly Active)</div>
|
||||
<div class="stat-value" id="mau">-</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-label">Avg Session Duration</div>
|
||||
<div class="stat-value" id="avg-session-duration">-</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Business Metrics Overview -->
|
||||
<div class="stats-grid">
|
||||
<div class="stat-card">
|
||||
<div class="stat-label">MRR (Monthly Recurring)</div>
|
||||
<div class="stat-value" id="mrr">$0</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-label">LTV (Lifetime Value)</div>
|
||||
<div class="stat-value" id="ltv">$0</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-label">Churn Rate</div>
|
||||
<div class="stat-value" id="churn-rate">0%</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-label">ARPU</div>
|
||||
<div class="stat-value" id="arpu">$0</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Product Usage Metrics -->
|
||||
<div class="stats-grid">
|
||||
<div class="stat-card">
|
||||
<div class="stat-label">Project Completion Rate</div>
|
||||
<div class="stat-value" id="project-completion-rate">0%</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-label">Return User Rate</div>
|
||||
<div class="stat-value" id="return-user-rate">0%</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-label">Total Sessions</div>
|
||||
<div class="stat-value" id="total-sessions">0</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-label">Total Projects</div>
|
||||
<div class="stat-value" id="total-projects">0</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Technical Metrics Overview -->
|
||||
<div class="stats-grid">
|
||||
<div class="stat-card">
|
||||
<div class="stat-label">Avg Queue Time</div>
|
||||
<div class="stat-value" id="avg-queue-time">0ms</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-label">Total Exports</div>
|
||||
<div class="stat-value" id="total-exports">0</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-label">Total Errors</div>
|
||||
<div class="stat-value" id="total-errors">0</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-label">System Uptime</div>
|
||||
<div class="stat-value" id="system-uptime">0h</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Feature Usage Chart -->
|
||||
<div class="chart-container">
|
||||
<div class="chart-title">Feature Usage (Most Popular)</div>
|
||||
<div class="bar-chart" id="feature-usage-chart"></div>
|
||||
</div>
|
||||
|
||||
<!-- Model Usage Chart -->
|
||||
<div class="chart-container">
|
||||
<div class="chart-title">AI Model Usage</div>
|
||||
<div class="bar-chart" id="model-usage-chart"></div>
|
||||
</div>
|
||||
|
||||
<!-- Error Rates Chart -->
|
||||
<div class="chart-container">
|
||||
<div class="chart-title">Error Rates by Type</div>
|
||||
<div class="bar-chart" id="error-rates-chart"></div>
|
||||
</div>
|
||||
|
||||
<!-- Plan Upgrade Patterns Chart -->
|
||||
<div class="chart-container">
|
||||
<div class="chart-title">Plan Upgrade Patterns</div>
|
||||
<div class="bar-chart" id="plan-upgrades-chart"></div>
|
||||
</div>
|
||||
|
||||
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(400px, 1fr)); gap: 24px;">
|
||||
<!-- Retention Cohorts Table -->
|
||||
<div class="chart-container">
|
||||
<div class="chart-title">Retention Cohorts</div>
|
||||
<div class="table-container">
|
||||
<table class="data-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Cohort Month</th>
|
||||
<th>Size</th>
|
||||
<th>1 Week</th>
|
||||
<th>1 Month</th>
|
||||
<th>3 Month</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="retention-cohorts-table">
|
||||
<tr>
|
||||
<td colspan="5" class="empty-state">Loading...</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Conversion Funnels -->
|
||||
<div class="chart-container">
|
||||
<div class="chart-title">Conversion Funnels</div>
|
||||
<div id="conversion-funnels-chart"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Resource Utilization Chart -->
|
||||
<div class="chart-container">
|
||||
<div class="chart-title">Resource Utilization (Last 24 Hours)</div>
|
||||
<div class="line-chart" id="resource-utilization-chart"></div>
|
||||
</div>
|
||||
|
||||
<!-- AI Response Times Chart -->
|
||||
<div class="chart-container">
|
||||
<div class="chart-title">AI Response Times (Last 100 Requests)</div>
|
||||
<div class="line-chart" id="ai-response-times-chart"></div>
|
||||
</div>
|
||||
|
||||
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(400px, 1fr)); gap: 24px;">
|
||||
<!-- Top Referrers -->
|
||||
<div class="chart-container">
|
||||
<div class="chart-title">Top Referrers</div>
|
||||
<div class="bar-chart" id="referrers-chart"></div>
|
||||
</div>
|
||||
|
||||
<!-- Top Referrers to Upgrade -->
|
||||
<div class="chart-container">
|
||||
<div class="chart-title">Top Referrers to Upgrade Page</div>
|
||||
<div class="bar-chart" id="upgrade-referrers-chart"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(400px, 1fr)); gap: 24px;">
|
||||
<!-- Top Pages -->
|
||||
<div class="chart-container">
|
||||
<div class="chart-title">Most Visited Pages</div>
|
||||
<div class="bar-chart" id="pages-chart"></div>
|
||||
</div>
|
||||
|
||||
<!-- Conversion Sources -->
|
||||
<div class="chart-container">
|
||||
<div class="chart-title">Signup Conversion Sources</div>
|
||||
<div class="bar-chart" id="conversion-sources-chart"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Upgrade Popup Sources -->
|
||||
<div class="chart-container">
|
||||
<div class="chart-title">Upgrade Popup Sources (Where users clicked upgrade)</div>
|
||||
<div class="bar-chart" id="upgrade-sources-chart"></div>
|
||||
</div>
|
||||
|
||||
<!-- Recent Visits Table -->
|
||||
<div class="table-container">
|
||||
<table class="data-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Time</th>
|
||||
<th>Path</th>
|
||||
<th>Referrer</th>
|
||||
<th>IP Address</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="recent-visits-table">
|
||||
<tr>
|
||||
<td colspan="4" class="empty-state">Loading...</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
let trackingData = null;
|
||||
|
||||
const byId = (id) => document.getElementById(id);
|
||||
|
||||
function setText(id, value) {
|
||||
const el = byId(id);
|
||||
if (!el) return;
|
||||
el.textContent = value;
|
||||
}
|
||||
|
||||
function formatNumber(value) {
|
||||
return (Number(value) || 0).toLocaleString();
|
||||
}
|
||||
|
||||
function formatMoney(value) {
|
||||
const num = Number(value) || 0;
|
||||
return `$${num.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })}`;
|
||||
}
|
||||
|
||||
function formatPercent(value, digits = 0) {
|
||||
const num = Number(value) || 0;
|
||||
return `${num.toFixed(digits)}%`;
|
||||
}
|
||||
|
||||
function formatDuration(seconds) {
|
||||
const num = Number(seconds) || 0;
|
||||
if (num < 60) return `${Math.round(num)}s`;
|
||||
const minutes = Math.floor(num / 60);
|
||||
const remainingSeconds = Math.round(num % 60);
|
||||
return `${minutes}m ${remainingSeconds}s`;
|
||||
}
|
||||
|
||||
function formatUptime(seconds) {
|
||||
const num = Number(seconds) || 0;
|
||||
const hours = Math.floor(num / 3600);
|
||||
const days = Math.floor(hours / 24);
|
||||
if (days > 0) return `${days}d ${hours % 24}h`;
|
||||
if (hours > 0) return `${hours}h ${Math.floor((num % 3600) / 60)}m`;
|
||||
return `${Math.floor(num / 60)}m`;
|
||||
}
|
||||
|
||||
async function loadTrackingData() {
|
||||
try {
|
||||
// Ensure credentials are included so admin session cookie is sent
|
||||
const response = await fetch('/api/admin/tracking', { credentials: 'same-origin' });
|
||||
if (!response.ok) {
|
||||
if (response.status === 401) {
|
||||
window.location.href = '/admin/login';
|
||||
return;
|
||||
}
|
||||
throw new Error('Failed to load tracking data');
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
trackingData = (data && data.stats) ? data.stats : null;
|
||||
|
||||
renderStats();
|
||||
renderFeatureUsageChart();
|
||||
renderModelUsageChart();
|
||||
renderErrorRatesChart();
|
||||
renderPlanUpgradesChart();
|
||||
renderRetentionCohorts();
|
||||
renderConversionFunnels();
|
||||
renderResourceUtilization();
|
||||
renderAIResponseTimes();
|
||||
renderReferrersChart();
|
||||
renderUpgradeReferrersChart();
|
||||
renderPagesChart();
|
||||
renderConversionSourcesChart();
|
||||
renderUpgradeSourcesChart();
|
||||
renderRecentVisits();
|
||||
} catch (error) {
|
||||
console.error('Error loading tracking data:', error);
|
||||
}
|
||||
}
|
||||
|
||||
function renderStats() {
|
||||
if (!trackingData) return;
|
||||
|
||||
const engagement = trackingData.userEngagement || {};
|
||||
setText('dau', formatNumber(engagement.dau));
|
||||
setText('wau', formatNumber(engagement.wau));
|
||||
setText('mau', formatNumber(engagement.mau));
|
||||
setText('avg-session-duration', formatDuration(engagement.averageSessionDuration));
|
||||
|
||||
const business = trackingData.businessMetrics || {};
|
||||
setText('mrr', formatMoney(business.mrr));
|
||||
setText('ltv', formatMoney(business.ltv));
|
||||
setText('churn-rate', formatPercent(business.churnRate, 1));
|
||||
setText('arpu', formatMoney(business.averageRevenuePerUser));
|
||||
|
||||
setText('project-completion-rate', formatPercent(engagement.projectCompletionRate));
|
||||
setText('return-user-rate', formatPercent(engagement.returnUserRate));
|
||||
|
||||
const sessions = trackingData.sessionInsights || {};
|
||||
setText('total-sessions', formatNumber(sessions.totalSessions));
|
||||
setText('total-projects', formatNumber(sessions.totalProjectsCreated));
|
||||
setText('total-exports', formatNumber(sessions.totalExports));
|
||||
setText('total-errors', formatNumber(sessions.totalErrors));
|
||||
|
||||
const tech = trackingData.technicalMetrics || {};
|
||||
setText('avg-queue-time', `${formatNumber(tech.averageQueueTime)}ms`);
|
||||
setText('system-uptime', formatUptime(tech.systemHealth && tech.systemHealth.uptime));
|
||||
}
|
||||
|
||||
function renderBarChart({
|
||||
containerId,
|
||||
entries,
|
||||
maxItems = 10,
|
||||
emptyText = 'No data available',
|
||||
barGradient = 'linear-gradient(90deg, #667eea 0%, #764ba2 100%)',
|
||||
labelFormatter = (label) => label,
|
||||
countFormatter = (count) => String(count),
|
||||
}) {
|
||||
const chartEl = byId(containerId);
|
||||
if (!chartEl) return;
|
||||
|
||||
chartEl.innerHTML = '';
|
||||
|
||||
const sliced = entries.slice(0, maxItems);
|
||||
if (sliced.length === 0) {
|
||||
chartEl.innerHTML = `<div class="empty-state">${emptyText}</div>`;
|
||||
return;
|
||||
}
|
||||
|
||||
const maxCount = Math.max(...sliced.map(([, count]) => Number(count) || 0), 1);
|
||||
|
||||
sliced.forEach(([labelRaw, countRaw]) => {
|
||||
const count = Number(countRaw) || 0;
|
||||
const item = document.createElement('div');
|
||||
item.className = 'bar-item';
|
||||
|
||||
const label = document.createElement('div');
|
||||
label.className = 'bar-label';
|
||||
label.textContent = labelFormatter(labelRaw);
|
||||
|
||||
const wrapper = document.createElement('div');
|
||||
wrapper.className = 'bar-wrapper';
|
||||
|
||||
const fill = document.createElement('div');
|
||||
fill.className = 'bar-fill';
|
||||
fill.style.background = barGradient;
|
||||
fill.style.width = `${(count / maxCount) * 100}%`;
|
||||
|
||||
const countEl = document.createElement('div');
|
||||
countEl.className = 'bar-count';
|
||||
countEl.textContent = countFormatter(count);
|
||||
|
||||
wrapper.appendChild(fill);
|
||||
item.appendChild(label);
|
||||
item.appendChild(wrapper);
|
||||
item.appendChild(countEl);
|
||||
|
||||
chartEl.appendChild(item);
|
||||
});
|
||||
}
|
||||
|
||||
function renderFeatureUsageChart() {
|
||||
if (!trackingData || !trackingData.featureUsage) return;
|
||||
|
||||
renderBarChart({
|
||||
containerId: 'feature-usage-chart',
|
||||
entries: Object.entries(trackingData.featureUsage).sort((a, b) => (b[1] || 0) - (a[1] || 0)),
|
||||
emptyText: 'No feature usage data available',
|
||||
barGradient: 'linear-gradient(90deg, #667eea 0%, #764ba2 100%)',
|
||||
labelFormatter: (feature) => String(feature).replace(/_/g, ' ').replace(/\b\w/g, (l) => l.toUpperCase()),
|
||||
});
|
||||
}
|
||||
|
||||
function renderModelUsageChart() {
|
||||
if (!trackingData || !trackingData.modelUsage) return;
|
||||
|
||||
renderBarChart({
|
||||
containerId: 'model-usage-chart',
|
||||
entries: Object.entries(trackingData.modelUsage).sort((a, b) => (b[1] || 0) - (a[1] || 0)),
|
||||
emptyText: 'No model usage data available',
|
||||
barGradient: 'linear-gradient(90deg, #008060 0%, #004c3f 100%)',
|
||||
labelFormatter: (model) => String(model).split('/').pop(),
|
||||
});
|
||||
}
|
||||
|
||||
function renderErrorRatesChart() {
|
||||
if (!trackingData || !trackingData.errorRates) return;
|
||||
|
||||
renderBarChart({
|
||||
containerId: 'error-rates-chart',
|
||||
entries: Object.entries(trackingData.errorRates).sort((a, b) => (b[1] || 0) - (a[1] || 0)),
|
||||
emptyText: 'No error data available',
|
||||
barGradient: 'linear-gradient(90deg, #ef4444 0%, #dc2626 100%)',
|
||||
labelFormatter: (error) => String(error).replace(/_/g, ' ').replace(/\b\w/g, (l) => l.toUpperCase()),
|
||||
});
|
||||
}
|
||||
|
||||
function renderPlanUpgradesChart() {
|
||||
if (!trackingData || !trackingData.planUpgradePatterns) return;
|
||||
|
||||
const upgrades = trackingData.planUpgradePatterns;
|
||||
const upgradeEntries = [];
|
||||
|
||||
Object.entries(upgrades).forEach(([fromPlan, toPlans]) => {
|
||||
Object.entries(toPlans || {}).forEach(([toPlan, count]) => {
|
||||
upgradeEntries.push([`${fromPlan} → ${toPlan}`, Number(count) || 0]);
|
||||
});
|
||||
});
|
||||
|
||||
upgradeEntries.sort((a, b) => (b[1] || 0) - (a[1] || 0));
|
||||
|
||||
renderBarChart({
|
||||
containerId: 'plan-upgrades-chart',
|
||||
entries: upgradeEntries,
|
||||
emptyText: 'No upgrade data available',
|
||||
barGradient: 'linear-gradient(90deg, #3b82f6 0%, #2563eb 100%)',
|
||||
});
|
||||
}
|
||||
|
||||
function renderRetentionCohorts() {
|
||||
if (!trackingData || !trackingData.retentionCohorts) return;
|
||||
|
||||
const tableEl = byId('retention-cohorts-table');
|
||||
if (!tableEl) return;
|
||||
|
||||
tableEl.innerHTML = '';
|
||||
|
||||
const cohorts = Object.entries(trackingData.retentionCohorts)
|
||||
.sort((a, b) => b[0].localeCompare(a[0]))
|
||||
.slice(0, 12);
|
||||
|
||||
if (cohorts.length === 0) {
|
||||
tableEl.innerHTML = '<tr><td colspan="5" class="empty-state">No cohort data available</td></tr>';
|
||||
return;
|
||||
}
|
||||
|
||||
cohorts.forEach(([month, cohort]) => {
|
||||
const row = document.createElement('tr');
|
||||
const tdMonth = document.createElement('td');
|
||||
tdMonth.textContent = month;
|
||||
|
||||
const tdSize = document.createElement('td');
|
||||
tdSize.textContent = formatNumber(cohort && cohort.cohortSize);
|
||||
|
||||
const retention = (cohort && cohort.retention) || {};
|
||||
const td1w = document.createElement('td');
|
||||
td1w.textContent = formatPercent(retention['1week'] || 0, 1);
|
||||
|
||||
const td1m = document.createElement('td');
|
||||
td1m.textContent = formatPercent(retention['1month'] || 0, 1);
|
||||
|
||||
const td3m = document.createElement('td');
|
||||
td3m.textContent = formatPercent(retention['3month'] || 0, 1);
|
||||
|
||||
row.appendChild(tdMonth);
|
||||
row.appendChild(tdSize);
|
||||
row.appendChild(td1w);
|
||||
row.appendChild(td1m);
|
||||
row.appendChild(td3m);
|
||||
tableEl.appendChild(row);
|
||||
});
|
||||
}
|
||||
|
||||
function renderConversionFunnels() {
|
||||
if (!trackingData || !trackingData.conversionFunnels) return;
|
||||
|
||||
const chartEl = byId('conversion-funnels-chart');
|
||||
if (!chartEl) return;
|
||||
|
||||
chartEl.innerHTML = '';
|
||||
|
||||
const funnels = trackingData.conversionFunnels;
|
||||
const funnelNames = Object.keys(funnels || {});
|
||||
|
||||
if (funnelNames.length === 0) {
|
||||
chartEl.innerHTML = '<div class="empty-state">No funnel data available</div>';
|
||||
return;
|
||||
}
|
||||
|
||||
funnelNames.forEach((funnelName) => {
|
||||
const funnel = funnels[funnelName] || {};
|
||||
const steps = Object.keys(funnel).sort();
|
||||
|
||||
if (steps.length === 0) return;
|
||||
|
||||
const funnelDiv = document.createElement('div');
|
||||
funnelDiv.className = 'chart-container';
|
||||
funnelDiv.style.marginBottom = '20px';
|
||||
|
||||
const title = document.createElement('div');
|
||||
title.className = 'chart-title';
|
||||
title.textContent = String(funnelName).replace(/_/g, ' ').replace(/\b\w/g, (l) => l.toUpperCase());
|
||||
|
||||
const stepsList = document.createElement('div');
|
||||
stepsList.className = 'bar-chart';
|
||||
|
||||
const maxCount = Math.max(
|
||||
...steps.map((step) => Number((funnel[step] && funnel[step].count) || 0)),
|
||||
1
|
||||
);
|
||||
|
||||
steps.forEach((step) => {
|
||||
const stepData = funnel[step] || {};
|
||||
const count = Number(stepData.count) || 0;
|
||||
|
||||
const item = document.createElement('div');
|
||||
item.className = 'bar-item';
|
||||
|
||||
const label = document.createElement('div');
|
||||
label.className = 'bar-label';
|
||||
label.textContent = String(step).replace(/_/g, ' ').replace(/\b\w/g, (l) => l.toUpperCase());
|
||||
|
||||
const wrapper = document.createElement('div');
|
||||
wrapper.className = 'bar-wrapper';
|
||||
|
||||
const fill = document.createElement('div');
|
||||
fill.className = 'bar-fill';
|
||||
fill.style.background = 'linear-gradient(90deg, #10b981 0%, #059669 100%)';
|
||||
fill.style.width = `${(count / maxCount) * 100}%`;
|
||||
|
||||
const countEl = document.createElement('div');
|
||||
countEl.className = 'bar-count';
|
||||
countEl.textContent = formatNumber(count);
|
||||
|
||||
wrapper.appendChild(fill);
|
||||
item.appendChild(label);
|
||||
item.appendChild(wrapper);
|
||||
item.appendChild(countEl);
|
||||
|
||||
stepsList.appendChild(item);
|
||||
});
|
||||
|
||||
funnelDiv.appendChild(title);
|
||||
funnelDiv.appendChild(stepsList);
|
||||
chartEl.appendChild(funnelDiv);
|
||||
});
|
||||
}
|
||||
|
||||
function getResourceUsageSeries() {
|
||||
if (!trackingData || !trackingData.technicalMetrics) return [];
|
||||
|
||||
const tech = trackingData.technicalMetrics;
|
||||
|
||||
if (Array.isArray(tech.resourceUsage)) {
|
||||
return tech.resourceUsage;
|
||||
}
|
||||
|
||||
const raw = tech.resourceUtilization;
|
||||
if (!raw) return [];
|
||||
|
||||
if (Array.isArray(raw)) return raw;
|
||||
|
||||
if (typeof raw === 'object') {
|
||||
return Object.entries(raw)
|
||||
.map(([timestamp, data]) => ({ timestamp: Number(timestamp), ...(data || {}) }))
|
||||
.filter((d) => Number.isFinite(d.timestamp))
|
||||
.sort((a, b) => a.timestamp - b.timestamp);
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
function renderResourceUtilization() {
|
||||
const chartEl = byId('resource-utilization-chart');
|
||||
if (!chartEl) return;
|
||||
|
||||
chartEl.innerHTML = '';
|
||||
|
||||
const all = getResourceUsageSeries();
|
||||
const cutoff = Date.now() - 24 * 60 * 60 * 1000;
|
||||
const data = all.filter((d) => (Number(d.timestamp) || 0) >= cutoff).slice(-288); // ~24h @ 5m intervals
|
||||
|
||||
if (data.length === 0) {
|
||||
chartEl.innerHTML = '<div class="empty-state">No resource data available</div>';
|
||||
return;
|
||||
}
|
||||
|
||||
const maxMemory = Math.max(...data.map((d) => Number(d.memory) || 0), 1);
|
||||
const labelEvery = Math.max(1, Math.floor(data.length / 8));
|
||||
|
||||
data.forEach((d, idx) => {
|
||||
const bar = document.createElement('div');
|
||||
bar.className = 'line-bar';
|
||||
|
||||
const height = ((Number(d.memory) || 0) / maxMemory) * 100;
|
||||
bar.style.height = `${height}%`;
|
||||
|
||||
const ts = Number(d.timestamp) || 0;
|
||||
const memMb = ((Number(d.memory) || 0) / (1024 * 1024)).toFixed(1);
|
||||
const cpu = Number(d.cpu) || 0;
|
||||
bar.title = `${new Date(ts).toLocaleString()}: ${memMb}MB, load: ${cpu.toFixed(2)}`;
|
||||
|
||||
const label = document.createElement('div');
|
||||
label.className = 'line-bar-label';
|
||||
label.textContent = idx % labelEvery === 0 ? new Date(ts).toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit' }) : '';
|
||||
bar.appendChild(label);
|
||||
|
||||
chartEl.appendChild(bar);
|
||||
});
|
||||
}
|
||||
|
||||
function renderAIResponseTimes() {
|
||||
const chartEl = byId('ai-response-times-chart');
|
||||
if (!chartEl) return;
|
||||
if (!trackingData || !trackingData.technicalMetrics || !Array.isArray(trackingData.technicalMetrics.aiResponseTimes)) return;
|
||||
|
||||
chartEl.innerHTML = '';
|
||||
|
||||
const responseTimes = trackingData.technicalMetrics.aiResponseTimes.slice(-100);
|
||||
if (responseTimes.length === 0) {
|
||||
chartEl.innerHTML = '<div class="empty-state">No response time data available</div>';
|
||||
return;
|
||||
}
|
||||
|
||||
const maxTime = Math.max(...responseTimes.map((r) => Number(r.responseTime) || 0), 1);
|
||||
|
||||
responseTimes.forEach((data, index) => {
|
||||
const bar = document.createElement('div');
|
||||
bar.className = 'line-bar';
|
||||
|
||||
const height = ((Number(data.responseTime) || 0) / maxTime) * 100;
|
||||
bar.style.height = `${height}%`;
|
||||
bar.style.background = data.success
|
||||
? 'linear-gradient(180deg, #10b981 0%, #059669 100%)'
|
||||
: 'linear-gradient(180deg, #ef4444 0%, #dc2626 100%)';
|
||||
|
||||
bar.title = `${data.provider}: ${data.responseTime}ms ${data.success ? '(success)' : '(error)'}`;
|
||||
|
||||
const label = document.createElement('div');
|
||||
label.className = 'line-bar-label';
|
||||
label.textContent = index % 10 === 0 ? `${Math.round(Number(data.responseTime) || 0)}ms` : '';
|
||||
bar.appendChild(label);
|
||||
|
||||
chartEl.appendChild(bar);
|
||||
});
|
||||
}
|
||||
|
||||
function renderReferrersChart() {
|
||||
if (!trackingData) return;
|
||||
|
||||
const referrers = Array.isArray(trackingData.topReferrers) ? trackingData.topReferrers.slice(0, 10) : [];
|
||||
renderBarChart({
|
||||
containerId: 'referrers-chart',
|
||||
entries: referrers.map((r) => [r.domain, r.count]),
|
||||
emptyText: 'No referrer data available',
|
||||
barGradient: 'linear-gradient(90deg, #667eea 0%, #764ba2 100%)',
|
||||
countFormatter: (count) => formatNumber(count),
|
||||
});
|
||||
}
|
||||
|
||||
function renderUpgradeReferrersChart() {
|
||||
if (!trackingData) return;
|
||||
|
||||
const referrers = Array.isArray(trackingData.referrersToUpgrade) ? trackingData.referrersToUpgrade : [];
|
||||
renderBarChart({
|
||||
containerId: 'upgrade-referrers-chart',
|
||||
entries: referrers.map((r) => [r.domain, r.count]),
|
||||
maxItems: 10,
|
||||
emptyText: 'No data available',
|
||||
barGradient: 'linear-gradient(90deg, #008060 0%, #004c3f 100%)',
|
||||
countFormatter: (count) => formatNumber(count),
|
||||
});
|
||||
}
|
||||
|
||||
function renderPagesChart() {
|
||||
if (!trackingData) return;
|
||||
|
||||
const pages = Array.isArray(trackingData.topPages) ? trackingData.topPages.slice(0, 10) : [];
|
||||
renderBarChart({
|
||||
containerId: 'pages-chart',
|
||||
entries: pages.map((p) => [p.path, p.count]),
|
||||
emptyText: 'No page data available',
|
||||
barGradient: 'linear-gradient(90deg, #667eea 0%, #764ba2 100%)',
|
||||
countFormatter: (count) => formatNumber(count),
|
||||
});
|
||||
}
|
||||
|
||||
function renderConversionSourcesChart() {
|
||||
if (!trackingData || !trackingData.conversionSources) return;
|
||||
|
||||
const sources = trackingData.conversionSources.signup || {};
|
||||
const entries = Object.entries(sources).sort((a, b) => (b[1] || 0) - (a[1] || 0));
|
||||
|
||||
renderBarChart({
|
||||
containerId: 'conversion-sources-chart',
|
||||
entries,
|
||||
emptyText: 'No data available',
|
||||
barGradient: 'linear-gradient(90deg, #3b82f6 0%, #2563eb 100%)',
|
||||
labelFormatter: (source) => {
|
||||
const s = String(source);
|
||||
return s.charAt(0).toUpperCase() + s.slice(1);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function renderUpgradeSourcesChart() {
|
||||
if (!trackingData || !trackingData.upgradeSources) return;
|
||||
|
||||
const sources = trackingData.upgradeSources || {};
|
||||
const entries = Object.entries(sources).sort((a, b) => (b[1] || 0) - (a[1] || 0));
|
||||
const sourceLabels = {
|
||||
apps_page: 'Apps Page',
|
||||
builder_model: 'Builder Model Selection',
|
||||
usage_limit: 'Usage Limit Reached',
|
||||
other: 'Other',
|
||||
};
|
||||
|
||||
renderBarChart({
|
||||
containerId: 'upgrade-sources-chart',
|
||||
entries,
|
||||
emptyText: 'No upgrade popup data available',
|
||||
barGradient: 'linear-gradient(90deg, #f59e0b 0%, #d97706 100%)',
|
||||
labelFormatter: (source) => sourceLabels[source] || String(source).replace(/_/g, ' ').replace(/\b\w/g, (l) => l.toUpperCase()),
|
||||
});
|
||||
}
|
||||
|
||||
function renderRecentVisits() {
|
||||
if (!trackingData) return;
|
||||
|
||||
const tableEl = byId('recent-visits-table');
|
||||
if (!tableEl) return;
|
||||
|
||||
tableEl.innerHTML = '';
|
||||
|
||||
const visits = Array.isArray(trackingData.recentVisits) ? trackingData.recentVisits.slice(0, 50) : [];
|
||||
if (visits.length === 0) {
|
||||
tableEl.innerHTML = '<tr><td colspan="4" class="empty-state">No recent visits</td></tr>';
|
||||
return;
|
||||
}
|
||||
|
||||
visits.forEach((visit) => {
|
||||
const row = document.createElement('tr');
|
||||
|
||||
const timeCell = document.createElement('td');
|
||||
timeCell.className = 'time-cell';
|
||||
timeCell.textContent = new Date(visit.timestamp).toLocaleString();
|
||||
|
||||
const pathCell = document.createElement('td');
|
||||
pathCell.className = 'path-cell';
|
||||
pathCell.textContent = visit.path;
|
||||
|
||||
const referrerCell = document.createElement('td');
|
||||
referrerCell.textContent = visit.referrer === 'direct' ? 'Direct' : visit.referrer;
|
||||
referrerCell.style.maxWidth = '200px';
|
||||
referrerCell.style.overflow = 'hidden';
|
||||
referrerCell.style.textOverflow = 'ellipsis';
|
||||
referrerCell.style.whiteSpace = 'nowrap';
|
||||
referrerCell.title = visit.referrer;
|
||||
|
||||
const ipCell = document.createElement('td');
|
||||
ipCell.textContent = visit.ip;
|
||||
ipCell.style.fontFamily = 'monospace';
|
||||
ipCell.style.fontSize = '13px';
|
||||
|
||||
row.appendChild(timeCell);
|
||||
row.appendChild(pathCell);
|
||||
row.appendChild(referrerCell);
|
||||
row.appendChild(ipCell);
|
||||
tableEl.appendChild(row);
|
||||
});
|
||||
}
|
||||
|
||||
// Admin controls
|
||||
byId('admin-refresh')?.addEventListener('click', () => {
|
||||
loadTrackingData();
|
||||
});
|
||||
|
||||
byId('admin-logout')?.addEventListener('click', async () => {
|
||||
try {
|
||||
await fetch('/api/admin/logout', { method: 'POST', credentials: 'same-origin' });
|
||||
window.location.href = '/admin/login';
|
||||
} catch (error) {
|
||||
console.error('Logout error:', error);
|
||||
window.location.href = '/admin/login';
|
||||
}
|
||||
});
|
||||
|
||||
// Load data on page load
|
||||
loadTrackingData();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
93
chat/public/admin-withdrawals.html
Normal file
@@ -0,0 +1,93 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Admin - Affiliate Withdrawals</title>
|
||||
<link rel="stylesheet" href="/styles.css" />
|
||||
|
||||
<!-- PostHog Analytics -->
|
||||
<script src="/posthog.js"></script>
|
||||
</head>
|
||||
|
||||
<body data-page="withdrawals">
|
||||
<div class="sidebar-overlay"></div>
|
||||
<div class="app-shell">
|
||||
<aside class="sidebar">
|
||||
<div class="brand">
|
||||
<div class="brand-mark">A</div>
|
||||
<div>
|
||||
<div class="brand-title">Admin</div>
|
||||
<div class="brand-sub">Site management</div>
|
||||
</div>
|
||||
<button id="close-sidebar" class="ghost" style="margin-left: auto; display: none;">×</button>
|
||||
</div>
|
||||
<div class="sidebar-section">
|
||||
<div class="section-heading">Navigation</div>
|
||||
<a class="ghost" href="/admin/build">Build models</a>
|
||||
<a class="ghost" href="/admin/plan">Plan models</a>
|
||||
<a class="ghost" href="/admin/plans">Plans</a>
|
||||
<a class="ghost" href="/admin/accounts">Accounts</a>
|
||||
<a class="ghost" href="/admin/affiliates">Affiliates</a>
|
||||
<a class="ghost" href="/admin/withdrawals">Withdrawals</a>
|
||||
<a class="ghost" href="/admin/tracking">Tracking</a>
|
||||
<a class="ghost" href="/admin/resources">Resources</a>
|
||||
<a class="ghost" href="/admin/contact-messages">Contact Messages</a>
|
||||
<a class="ghost" href="/admin/login">Login</a>
|
||||
</div>
|
||||
</aside>
|
||||
<main class="main">
|
||||
<div class="admin-shell">
|
||||
<div class="topbar" style="margin-bottom: 12px;">
|
||||
<button id="menu-toggle">
|
||||
<span></span><span></span><span></span>
|
||||
</button>
|
||||
<div>
|
||||
<div class="pill">Admin</div>
|
||||
<div class="title" style="margin-top: 6px;">Affiliate Withdrawals</div>
|
||||
<div class="crumb">View and manage affiliate withdrawal requests.</div>
|
||||
</div>
|
||||
<div class="admin-actions">
|
||||
<a class="ghost" href="/admin">Back to models</a>
|
||||
<button id="admin-refresh" class="ghost">Refresh</button>
|
||||
<button id="admin-logout" class="primary">Logout</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="admin-card" style="overflow:auto;">
|
||||
<header style="align-items:center;">
|
||||
<div>
|
||||
<h3>Withdrawal Requests</h3>
|
||||
<p class="crumb" style="margin-top:4px;">Manage PayPal payouts and request status.</p>
|
||||
</div>
|
||||
<div class="pill" id="withdrawals-count">0 requests</div>
|
||||
</header>
|
||||
<div class="status-line" id="admin-status"></div>
|
||||
<div class="admin-table">
|
||||
<table style="width:100%; border-collapse:collapse; min-width:900px;">
|
||||
<thead style="position:sticky; top:0; background:var(--panel); z-index:1;">
|
||||
<tr
|
||||
style="text-align:left; font-size:13px; color: var(--muted); border-bottom:2px solid var(--border);">
|
||||
<th style="padding:12px 8px;">Date</th>
|
||||
<th style="padding:12px 8px;">Affiliate</th>
|
||||
<th style="padding:12px 8px;">PayPal Email</th>
|
||||
<th style="padding:12px 8px;">Amount</th>
|
||||
<th style="padding:12px 8px;">Currency</th>
|
||||
<th style="padding:12px 8px;">Status</th>
|
||||
<th style="padding:12px 8px;">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="withdrawals-table">
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<script src="/admin.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
242
chat/public/admin.html
Normal file
@@ -0,0 +1,242 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Admin Panel</title>
|
||||
<link rel="stylesheet" href="/styles.css" />
|
||||
<style>
|
||||
/* Build page uses a single-column admin layout for clarity */
|
||||
body[data-page="build"] .admin-grid {
|
||||
grid-template-columns: none !important;
|
||||
gap: 12px !important;
|
||||
}
|
||||
body[data-page="build"] .admin-grid .admin-card {
|
||||
width: 100% !important;
|
||||
}
|
||||
/* Slightly tighten cards on the build page */
|
||||
body[data-page="build"] .admin-card { padding: 12px; }
|
||||
</style>
|
||||
|
||||
<!-- PostHog Analytics -->
|
||||
<script src="/posthog.js"></script>
|
||||
</head>
|
||||
<body data-page="build">
|
||||
<div class="sidebar-overlay"></div>
|
||||
<div class="app-shell">
|
||||
<aside class="sidebar">
|
||||
<div class="brand">
|
||||
<div class="brand-mark">A</div>
|
||||
<div>
|
||||
<div class="brand-title">Admin</div>
|
||||
<div class="brand-sub">Site management</div>
|
||||
</div>
|
||||
<button id="close-sidebar" class="ghost" style="margin-left: auto; display: none;">×</button>
|
||||
</div>
|
||||
<div class="sidebar-section">
|
||||
<div class="section-heading">Navigation</div>
|
||||
<a class="ghost" href="/admin/build">Build models</a>
|
||||
<a class="ghost" href="/admin/plan">Plan models</a>
|
||||
<a class="ghost" href="/admin/plans">Plans</a>
|
||||
<a class="ghost" href="/admin/accounts">Accounts</a>
|
||||
<a class="ghost" href="/admin/affiliates">Affiliates</a>
|
||||
<a class="ghost" href="/admin/withdrawals">Withdrawals</a>
|
||||
<a class="ghost" href="/admin/tracking">Tracking</a>
|
||||
<a class="ghost" href="/admin/resources">Resources</a>
|
||||
<a class="ghost" href="/admin/contact-messages">Contact Messages</a>
|
||||
<a class="ghost" href="/admin/login">Login</a>
|
||||
</div>
|
||||
</aside>
|
||||
<main class="main">
|
||||
<div class="admin-shell">
|
||||
<div class="topbar" style="margin-bottom: 12px;">
|
||||
<button id="menu-toggle">
|
||||
<span></span><span></span><span></span>
|
||||
</button>
|
||||
<div>
|
||||
<div class="pill">Admin</div>
|
||||
<div class="title" style="margin-top: 6px;">Model Control</div>
|
||||
<div class="crumb">Only the configured admin can sign in here.</div>
|
||||
</div>
|
||||
<div class="admin-actions">
|
||||
<button id="admin-refresh" class="ghost">Refresh</button>
|
||||
<button id="admin-logout" class="primary">Logout</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="admin-grid">
|
||||
<div class="admin-card">
|
||||
<header>
|
||||
<h3>Add / Update Model</h3>
|
||||
<div class="pill">Step 1</div>
|
||||
</header>
|
||||
<form id="model-form" class="admin-form">
|
||||
<label>
|
||||
Choose a model from OpenCode
|
||||
<select id="available-models"></select>
|
||||
</label>
|
||||
<label>
|
||||
Display name shown to users
|
||||
<input id="display-label" type="text" placeholder="Friendly label (e.g. Fast GPT-4)" required />
|
||||
</label>
|
||||
<label>
|
||||
Model tier (for plan limits)
|
||||
<select id="model-tier">
|
||||
<option value="free">Free (1x multiplier)</option>
|
||||
<option value="plus">Plus (2x multiplier)</option>
|
||||
<option value="pro">Pro (3x multiplier)</option>
|
||||
</select>
|
||||
</label>
|
||||
<label>
|
||||
Icon (files in /assets)
|
||||
<select id="icon-select">
|
||||
<option value="">No icon</option>
|
||||
</select>
|
||||
</label>
|
||||
<label>
|
||||
Provider priority (comma separated provider:model)
|
||||
<input id="provider-order" type="text" placeholder="openrouter:anthropic/claude-3.5-sonnet, mistral:mistral-large-latest" />
|
||||
</label>
|
||||
<label style="display: flex; align-items: center; gap: 8px; margin-top: 8px;">
|
||||
<input id="supports-media" type="checkbox" style="width: auto;" />
|
||||
<span>Supports image uploads</span>
|
||||
</label>
|
||||
<div class="admin-actions">
|
||||
<button type="submit" class="primary">Save model</button>
|
||||
<button type="button" id="reload-available" class="ghost">Reload available models</button>
|
||||
</div>
|
||||
<div class="status-line" id="admin-status"></div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="admin-card">
|
||||
<header>
|
||||
<h3>System Actions</h3>
|
||||
<div class="pill">Admin</div>
|
||||
</header>
|
||||
<p style="margin-top:0; color: var(--muted);">Emergency controls for system management.</p>
|
||||
<div class="admin-actions" style="flex-direction: column; align-items: flex-start;">
|
||||
<button id="cancel-all-messages" class="danger">Cancel All Running & Queued Messages</button>
|
||||
<div class="status-line" id="cancel-messages-status"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="admin-card">
|
||||
<header>
|
||||
<h3>Icon Library</h3>
|
||||
<div class="pill">Step 0</div>
|
||||
</header>
|
||||
<p style="margin-top:0; color: var(--muted);">Upload icon files to <strong>/chat/public/assets</strong> and pick them here. PNG, JPG, SVG, and WEBP are supported.</p>
|
||||
<div id="icon-list" class="admin-list"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="admin-card" style="margin-top: 16px;">
|
||||
<header>
|
||||
<h3>OpenCode Ultimate Backup Model</h3>
|
||||
<div class="pill">Fallback</div>
|
||||
</header>
|
||||
<p style="margin-top:0; color: var(--muted);">Configure the ultimate fallback model that will be used when all providers fail. This is the last-resort backup for reliability.</p>
|
||||
<form id="opencode-backup-form" class="admin-form">
|
||||
<label>
|
||||
OpenCode backup model
|
||||
<select id="opencode-backup"></select>
|
||||
</label>
|
||||
<div class="admin-actions">
|
||||
<button type="submit" class="primary">Save backup model</button>
|
||||
</div>
|
||||
<div class="status-line" id="opencode-backup-status"></div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="admin-card" style="margin-top: 16px;">
|
||||
<header>
|
||||
<h3>Auto Model for Hobby/Free Plan</h3>
|
||||
<div class="pill">Free Plan</div>
|
||||
</header>
|
||||
<p style="margin-top:0; color: var(--muted);">Select which model Hobby and Free plan users will automatically use. Paid plan users can select their own models.</p>
|
||||
<form id="auto-model-form" class="admin-form">
|
||||
<label>
|
||||
Model for hobby/free users
|
||||
<select id="auto-model-select">
|
||||
<option value="">Auto (use first configured model)</option>
|
||||
</select>
|
||||
</label>
|
||||
<div class="admin-actions">
|
||||
<button type="submit" class="primary">Save auto model</button>
|
||||
</div>
|
||||
<div class="status-line" id="auto-model-status"></div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="admin-grid" style="margin-top: 16px;">
|
||||
<div class="admin-card">
|
||||
<header>
|
||||
<h3>Provider Limits & Usage</h3>
|
||||
<div class="pill">Rate limits</div>
|
||||
</header>
|
||||
<p style="margin-top:0; color: var(--muted);">Configure token/request limits per provider or per model and monitor current usage.</p>
|
||||
<form id="provider-limit-form" class="admin-form">
|
||||
<label>
|
||||
Provider
|
||||
<select id="limit-provider">
|
||||
<option value="openrouter">OpenRouter</option>
|
||||
<option value="mistral">Mistral</option>
|
||||
<option value="google">Google</option>
|
||||
<option value="groq">Groq</option>
|
||||
<option value="nvidia">NVIDIA</option>
|
||||
<option value="opencode">OpenCode</option>
|
||||
</select>
|
||||
</label>
|
||||
<label>
|
||||
Scope
|
||||
<select id="limit-scope">
|
||||
<option value="provider">Per Provider</option>
|
||||
<option value="model">Per Model</option>
|
||||
</select>
|
||||
</label>
|
||||
<label>
|
||||
Model (for per-model limits)
|
||||
<select id="limit-model">
|
||||
<option value="">Any model</option>
|
||||
</select>
|
||||
</label>
|
||||
<label>
|
||||
Tokens per minute
|
||||
<input id="limit-tpm" type="number" min="0" step="1" placeholder="0 = unlimited" />
|
||||
</label>
|
||||
<label>
|
||||
Tokens per day
|
||||
<input id="limit-tpd" type="number" min="0" step="1" placeholder="0 = unlimited" />
|
||||
</label>
|
||||
<label>
|
||||
Requests per minute
|
||||
<input id="limit-rpm" type="number" min="0" step="1" placeholder="0 = unlimited" />
|
||||
</label>
|
||||
<label>
|
||||
Requests per day
|
||||
<input id="limit-rpd" type="number" min="0" step="1" placeholder="0 = unlimited" />
|
||||
</label>
|
||||
<div class="admin-actions">
|
||||
<button type="submit" class="primary">Save limits</button>
|
||||
</div>
|
||||
<div class="status-line" id="provider-limit-status"></div>
|
||||
</form>
|
||||
<div id="provider-usage" class="admin-list"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="admin-card" style="margin-top: 16px;">
|
||||
<header>
|
||||
<h3>Models available to users</h3>
|
||||
<div class="pill" id="configured-count">0</div>
|
||||
</header>
|
||||
<p class="muted" style="margin-top:0;">One row per model. Arrange provider order to control automatic fallback when a provider errors or hits a rate limit.</p>
|
||||
<div id="configured-list" class="admin-list"></div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
<script src="/admin.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
2216
chat/public/admin.js
Normal file
187
chat/public/affiliate-dashboard.html
Normal file
@@ -0,0 +1,187 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Affiliate Dashboard | Plugin Compass</title>
|
||||
<link rel="icon" type="image/png" href="/assets/Plugin.png">
|
||||
<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">
|
||||
<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=Inter:wght@400;600&display=swap" rel="stylesheet">
|
||||
<script>
|
||||
tailwind.config = { theme: { extend: { fontFamily: { sans: ['Inter', 'sans-serif'] } } } };
|
||||
</script>
|
||||
|
||||
<!-- PostHog Analytics -->
|
||||
<script src="/posthog.js"></script>
|
||||
</head>
|
||||
<body class="min-h-screen bg-amber-50 text-gray-900">
|
||||
<nav class="sticky top-0 z-30 bg-amber-50/95 backdrop-blur border-b border-amber-200">
|
||||
<div class="max-w-6xl mx-auto px-4 sm:px-6 lg:px-8 h-14 flex items-center justify-between">
|
||||
<a href="/" class="flex items-center gap-2 font-semibold">
|
||||
<img src="/assets/Plugin.png" alt="Plugin Compass" class="w-8 h-8 rounded-lg">
|
||||
<span>Plugin<span class="text-green-700">Compass</span></span>
|
||||
</a>
|
||||
<div class="flex items-center gap-3 text-sm">
|
||||
<a href="/affiliate" class="text-gray-700 hover:text-gray-900 hidden sm:inline">Showcase</a>
|
||||
<button id="logout" class="px-3 py-2 rounded-lg border border-gray-300 hover:bg-white">Logout</button>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<main class="max-w-6xl mx-auto px-4 sm:px-6 lg:px-8 py-10">
|
||||
<header class="mb-8">
|
||||
<p class="text-xs font-semibold uppercase text-green-700 mb-1">Affiliate dashboard</p>
|
||||
<h1 class="text-3xl font-bold">Earnings & tracking links</h1>
|
||||
<p class="text-gray-600">Create campaign links and monitor the 7.5% commissions attributed to you.</p>
|
||||
</header>
|
||||
|
||||
<section class="grid grid-cols-1 md:grid-cols-3 gap-6 mb-8">
|
||||
<div class="bg-white border border-amber-200 rounded-2xl p-6 shadow-sm">
|
||||
<p class="text-sm text-gray-600">Total earnings</p>
|
||||
<div class="text-3xl font-bold mt-2" id="total-earnings">$0.00</div>
|
||||
<p class="text-xs text-gray-500 mt-1">7.5% of Business & Enterprise billings</p>
|
||||
<button id="request-withdrawal" class="mt-3 w-full px-4 py-2 rounded-lg bg-green-700 text-white text-sm font-semibold hover:bg-green-600">Request Withdrawal</button>
|
||||
</div>
|
||||
<div class="bg-white border border-amber-200 rounded-2xl p-6 shadow-sm md:col-span-2">
|
||||
<div class="flex items-center justify-between mb-3">
|
||||
<div>
|
||||
<p class="text-sm text-gray-600">Primary tracking link</p>
|
||||
<p class="font-semibold text-gray-900" id="primary-link">—</p>
|
||||
</div>
|
||||
<button id="copy-link" class="px-3 py-2 rounded-lg bg-green-700 text-white text-sm hover:bg-green-600">Copy</button>
|
||||
</div>
|
||||
<p class="text-xs text-gray-500">Share this on your pricing pages, emails, or social posts.</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="bg-white border border-amber-200 rounded-2xl p-6 shadow-sm mb-8">
|
||||
<div class="flex flex-wrap items-center justify-between gap-3 mb-4">
|
||||
<div>
|
||||
<h2 class="text-lg font-semibold">Tracking links</h2>
|
||||
<p class="text-sm text-gray-600">Create unique links for campaigns and channels.</p>
|
||||
</div>
|
||||
<button id="new-link" class="px-4 py-2 rounded-lg bg-green-700 text-white text-sm font-semibold hover:bg-green-600">Create link</button>
|
||||
</div>
|
||||
<div id="links" class="space-y-3 text-sm text-gray-800"></div>
|
||||
</section>
|
||||
|
||||
<section class="bg-white border border-amber-200 rounded-2xl p-6 shadow-sm">
|
||||
<div class="flex items-center justify-between mb-3">
|
||||
<h2 class="text-lg font-semibold">Recent earnings</h2>
|
||||
<a href="/affiliate-transactions" class="text-xs text-green-700 hover:underline font-semibold">View all transactions</a>
|
||||
</div>
|
||||
<div id="earnings" class="space-y-3 text-sm text-gray-800"></div>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<script>
|
||||
const linksEl = document.getElementById('links');
|
||||
const earningsEl = document.getElementById('earnings');
|
||||
const primaryLinkEl = document.getElementById('primary-link');
|
||||
const totalEl = document.getElementById('total-earnings');
|
||||
const copyBtn = document.getElementById('copy-link');
|
||||
const newLinkBtn = document.getElementById('new-link');
|
||||
const logoutBtn = document.getElementById('logout');
|
||||
let sampleLink = '';
|
||||
|
||||
async function loadAffiliate() {
|
||||
const res = await fetch('/api/affiliates/me');
|
||||
if (res.status === 401) {
|
||||
window.location.href = '/affiliate-login';
|
||||
return;
|
||||
}
|
||||
const json = await res.json().catch(() => ({}));
|
||||
const affiliate = json.affiliate || {};
|
||||
sampleLink = json.sampleLink || '';
|
||||
primaryLinkEl.textContent = sampleLink || '—';
|
||||
totalEl.textContent = `$${Number(affiliate?.earnings?.total || 0).toFixed(2)}`;
|
||||
renderLinks(affiliate.trackingLinks || []);
|
||||
renderEarnings((affiliate.earnings && affiliate.earnings.records) || []);
|
||||
}
|
||||
|
||||
function renderLinks(links) {
|
||||
if (!links.length) {
|
||||
linksEl.innerHTML = '<p class="text-gray-500 text-sm">No links yet. Create your first one.</p>';
|
||||
return;
|
||||
}
|
||||
const origin = window.location.origin;
|
||||
linksEl.innerHTML = links.map((l) => {
|
||||
const path = l.targetPath || '/pricing';
|
||||
const url = `${origin}${path}${path.includes('?') ? '&' : '?'}aff=${l.code}`;
|
||||
return `<div class="flex flex-col md:flex-row md:items-center md:justify-between gap-2 border border-amber-200 rounded-xl p-3">
|
||||
<div>
|
||||
<p class="font-semibold">${l.label || 'Tracking link'}</p>
|
||||
<p class="text-gray-600">${url}</p>
|
||||
</div>
|
||||
<button class="px-3 py-2 rounded-lg border border-gray-300 text-sm hover:bg-amber-50" data-copy="${url}">Copy</button>
|
||||
</div>`;
|
||||
}).join('');
|
||||
linksEl.querySelectorAll('button[data-copy]').forEach((btn) => {
|
||||
btn.addEventListener('click', () => {
|
||||
navigator.clipboard.writeText(btn.dataset.copy);
|
||||
btn.textContent = 'Copied';
|
||||
setTimeout(() => (btn.textContent = 'Copy'), 1200);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function renderEarnings(records) {
|
||||
if (!records.length) {
|
||||
earningsEl.innerHTML = '<p class="text-gray-500 text-sm">No earnings yet. Share your links to start earning 7.5%.</p>';
|
||||
return;
|
||||
}
|
||||
earningsEl.innerHTML = records.slice().reverse().map((r) => `
|
||||
<div class="flex items-center justify-between border border-amber-200 rounded-xl px-3 py-2">
|
||||
<div>
|
||||
<p class="font-semibold capitalize">${r.plan} plan</p>
|
||||
<p class="text-gray-600 text-xs">User ${r.userId || ''}</p>
|
||||
</div>
|
||||
<div class="text-right">
|
||||
<p class="font-bold">$${Number(r.amount || 0).toFixed(2)}</p>
|
||||
<p class="text-gray-500 text-xs">${new Date(r.createdAt).toLocaleDateString()}</p>
|
||||
</div>
|
||||
</div>
|
||||
`).join('');
|
||||
}
|
||||
|
||||
copyBtn.addEventListener('click', () => {
|
||||
if (!sampleLink) return;
|
||||
navigator.clipboard.writeText(sampleLink);
|
||||
copyBtn.textContent = 'Copied';
|
||||
setTimeout(() => (copyBtn.textContent = 'Copy'), 1200);
|
||||
});
|
||||
|
||||
newLinkBtn.addEventListener('click', async () => {
|
||||
const label = prompt('Label for this link (campaign, channel, etc.)') || 'New link';
|
||||
const targetPath = prompt('Target path (e.g. /pricing, /features, /)', '/pricing') || '/pricing';
|
||||
const res = await fetch('/api/affiliates/links', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ label, targetPath })
|
||||
});
|
||||
const json = await res.json().catch(() => ({}));
|
||||
if (res.ok) {
|
||||
renderLinks(json.links || []);
|
||||
} else {
|
||||
alert(json.error || 'Unable to create link');
|
||||
}
|
||||
});
|
||||
|
||||
const withdrawalBtn = document.getElementById('request-withdrawal');
|
||||
withdrawalBtn.addEventListener('click', () => {
|
||||
window.location.href = '/affiliate-withdrawal';
|
||||
});
|
||||
|
||||
logoutBtn.addEventListener('click', async () => {
|
||||
await fetch('/api/affiliates/logout', { method: 'POST' });
|
||||
localStorage.removeItem('plugin_compass_onboarding_completed');
|
||||
window.location.href = '/affiliate-login';
|
||||
});
|
||||
|
||||
loadAffiliate();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
115
chat/public/affiliate-login.html
Normal file
@@ -0,0 +1,115 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Affiliate Login | Plugin Compass</title>
|
||||
<link rel="icon" type="image/png" href="/assets/Plugin.png">
|
||||
<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">
|
||||
<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=Inter:wght@400;600&display=swap" rel="stylesheet">
|
||||
<script>
|
||||
tailwind.config = { theme: { extend: { fontFamily: { sans: ['Inter', 'sans-serif'] } } } };
|
||||
</script>
|
||||
|
||||
<!-- PostHog Analytics -->
|
||||
<script src="/posthog.js"></script>
|
||||
</head>
|
||||
<body class="bg-amber-50 text-gray-900 font-sans antialiased min-h-screen flex flex-col">
|
||||
<div class="flex-1 flex items-center justify-center px-4 py-8">
|
||||
<div class="w-full max-w-md">
|
||||
<a href="/" class="flex items-center gap-2 mb-6 text-gray-700 hover:text-gray-900">
|
||||
<img src="/assets/Plugin.png" alt="Plugin Compass" class="w-8 h-8 rounded-lg">
|
||||
<span class="font-bold">Plugin<span class="text-green-700">Compass</span></span>
|
||||
</a>
|
||||
<div class="bg-white rounded-2xl shadow-sm border border-amber-200 p-8">
|
||||
<h1 class="text-2xl font-bold mb-2">Affiliate Login</h1>
|
||||
<p class="text-sm text-gray-600 mb-6">Access your dashboard to create tracking links and track earnings.</p>
|
||||
<form id="login-form" class="space-y-4">
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-1">Email</label>
|
||||
<input type="email" name="email" required class="w-full rounded-lg border border-gray-300 px-3 py-2 focus:outline-none focus:ring-2 focus:ring-green-600">
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-1">Password</label>
|
||||
<input type="password" name="password" required class="w-full rounded-lg border border-gray-300 px-3 py-2 focus:outline-none focus:ring-2 focus:ring-green-600">
|
||||
</div>
|
||||
<div id="error" class="text-sm text-red-600 hidden"></div>
|
||||
<button type="submit" class="w-full py-3 rounded-lg bg-green-700 text-white font-semibold hover:bg-green-600">Login</button>
|
||||
</form>
|
||||
<p class="text-sm text-gray-600 mt-4">New partner? <a class="text-green-700 font-semibold" href="/affiliate-signup">Join the program</a></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<footer class="bg-white border-t border-green-200 pt-16 pb-8">
|
||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div class="grid grid-cols-2 md:grid-cols-4 gap-12 mb-16">
|
||||
<div class="col-span-2 md:col-span-1">
|
||||
<div class="flex items-center gap-2 mb-6">
|
||||
<img src="/assets/Plugin.png" alt="Plugin Compass" class="w-8 h-8">
|
||||
<span class="font-bold text-xl tracking-tight text-gray-800">Plugin<span
|
||||
class="text-green-700">Compass</span></span>
|
||||
</div>
|
||||
<p class="text-gray-600 text-sm leading-relaxed">
|
||||
The smart way for WordPress site owners to replace expensive plugin subscriptions with custom
|
||||
solutions. Save thousands monthly.
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<h4 class="font-bold text-gray-900 mb-6">Product</h4>
|
||||
<ul class="space-y-4 text-sm">
|
||||
<li><a href="/features" class="text-gray-600 hover:text-green-700">Features</a></li>
|
||||
<li><a href="/pricing" class="text-gray-600 hover:text-green-700">Pricing</a></li>
|
||||
<li><a href="#" class="text-gray-600 hover:text-green-700">Templates</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div>
|
||||
<h4 class="font-bold text-gray-900 mb-6">Resources</h4>
|
||||
<ul class="space-y-4 text-sm">
|
||||
<li><a href="/docs" class="text-gray-600 hover:text-green-700">Documentation</a></li>
|
||||
<li><a href="/faq" class="text-gray-600 hover:text-green-700">FAQ</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div>
|
||||
<h4 class="font-bold text-gray-900 mb-6">Legal</h4>
|
||||
<ul class="space-y-4 text-sm">
|
||||
<li><a href="/privacy.html" class="text-gray-600 hover:text-green-700">Privacy Policy</a></li>
|
||||
<li><a href="/terms" class="text-gray-600 hover:text-green-700">Terms of Service</a></li>
|
||||
<li><a href="/contact" class="text-gray-600 hover:text-green-700">Contact Us</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="border-t border-gray-100 pt-8 flex justify-center">
|
||||
<p class="text-gray-500 text-xs text-center">© 2026 Plugin Compass. All rights reserved.</p>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
<script>
|
||||
const form = document.getElementById('login-form');
|
||||
const errorBox = document.getElementById('error');
|
||||
form.addEventListener('submit', async (e) => {
|
||||
e.preventDefault();
|
||||
errorBox.classList.add('hidden');
|
||||
const data = Object.fromEntries(new FormData(form).entries());
|
||||
const res = await fetch('/api/affiliates/login', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(data)
|
||||
});
|
||||
const json = await res.json().catch(() => ({}));
|
||||
if (!res.ok) {
|
||||
if (json.verificationRequired) {
|
||||
window.location.href = '/affiliate-verification-sent';
|
||||
return;
|
||||
}
|
||||
errorBox.textContent = json.error || 'Unable to login';
|
||||
errorBox.classList.remove('hidden');
|
||||
return;
|
||||
}
|
||||
window.location.href = '/affiliate-dashboard';
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
120
chat/public/affiliate-signup.html
Normal file
@@ -0,0 +1,120 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Affiliate Signup | Plugin Compass</title>
|
||||
<link rel="icon" type="image/png" href="/assets/Plugin.png">
|
||||
<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">
|
||||
<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=Inter:wght@400;600&display=swap" rel="stylesheet">
|
||||
<script>
|
||||
tailwind.config = { theme: { extend: { fontFamily: { sans: ['Inter', 'sans-serif'] } } } };
|
||||
</script>
|
||||
|
||||
<!-- PostHog Analytics -->
|
||||
<script src="/posthog.js"></script>
|
||||
</head>
|
||||
<body class="bg-amber-50 text-gray-900 font-sans antialiased min-h-screen flex flex-col">
|
||||
<div class="flex-1 flex items-center justify-center px-4 py-8">
|
||||
<div class="w-full max-w-md">
|
||||
<a href="/" class="flex items-center gap-2 mb-6 text-gray-700 hover:text-gray-900">
|
||||
<img src="/assets/Plugin.png" alt="Plugin Compass" class="w-8 h-8 rounded-lg">
|
||||
<span class="font-bold">Plugin<span class="text-green-700">Compass</span></span>
|
||||
</a>
|
||||
<div class="bg-white rounded-2xl shadow-sm border border-amber-200 p-8">
|
||||
<p class="text-xs font-semibold uppercase text-green-700 mb-2">Earn 7.5% recurring</p>
|
||||
<h1 class="text-2xl font-bold mb-2">Join the Affiliate Program</h1>
|
||||
<p class="text-sm text-gray-600 mb-6">Create your account to generate tracking links and track payouts.</p>
|
||||
<form id="signup-form" class="space-y-4">
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-1">Name</label>
|
||||
<input type="text" name="name" required class="w-full rounded-lg border border-gray-300 px-3 py-2 focus:outline-none focus:ring-2 focus:ring-green-600">
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-1">Email</label>
|
||||
<input type="email" name="email" required class="w-full rounded-lg border border-gray-300 px-3 py-2 focus:outline-none focus:ring-2 focus:ring-green-600">
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-1">Password</label>
|
||||
<input type="password" name="password" minlength="6" required class="w-full rounded-lg border border-gray-300 px-3 py-2 focus:outline-none focus:ring-2 focus:ring-green-600">
|
||||
</div>
|
||||
<div id="error" class="text-sm text-red-600 hidden"></div>
|
||||
<button type="submit" class="w-full py-3 rounded-lg bg-green-700 text-white font-semibold hover:bg-green-600">Create account</button>
|
||||
</form>
|
||||
<p class="text-sm text-gray-600 mt-4">Already an affiliate? <a class="text-green-700 font-semibold" href="/affiliate-login">Login</a></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<footer class="bg-white border-t border-green-200 pt-16 pb-8">
|
||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div class="grid grid-cols-2 md:grid-cols-4 gap-12 mb-16">
|
||||
<div class="col-span-2 md:col-span-1">
|
||||
<div class="flex items-center gap-2 mb-6">
|
||||
<img src="/assets/Plugin.png" alt="Plugin Compass" class="w-8 h-8">
|
||||
<span class="font-bold text-xl tracking-tight text-gray-800">Plugin<span
|
||||
class="text-green-700">Compass</span></span>
|
||||
</div>
|
||||
<p class="text-gray-600 text-sm leading-relaxed">
|
||||
The smart way for WordPress site owners to replace expensive plugin subscriptions with custom
|
||||
solutions. Save thousands monthly.
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<h4 class="font-bold text-gray-900 mb-6">Product</h4>
|
||||
<ul class="space-y-4 text-sm">
|
||||
<li><a href="/features" class="text-gray-600 hover:text-green-700">Features</a></li>
|
||||
<li><a href="/pricing" class="text-gray-600 hover:text-green-700">Pricing</a></li>
|
||||
<li><a href="#" class="text-gray-600 hover:text-green-700">Templates</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div>
|
||||
<h4 class="font-bold text-gray-900 mb-6">Resources</h4>
|
||||
<ul class="space-y-4 text-sm">
|
||||
<li><a href="/docs" class="text-gray-600 hover:text-green-700">Documentation</a></li>
|
||||
<li><a href="/faq" class="text-gray-600 hover:text-green-700">FAQ</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div>
|
||||
<h4 class="font-bold text-gray-900 mb-6">Legal</h4>
|
||||
<ul class="space-y-4 text-sm">
|
||||
<li><a href="/privacy.html" class="text-gray-600 hover:text-green-700">Privacy Policy</a></li>
|
||||
<li><a href="/terms" class="text-gray-600 hover:text-green-700">Terms of Service</a></li>
|
||||
<li><a href="/contact" class="text-gray-600 hover:text-green-700">Contact Us</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="border-t border-gray-100 pt-8 flex justify-center">
|
||||
<p class="text-gray-500 text-xs text-center">© 2026 Plugin Compass. All rights reserved.</p>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
<script>
|
||||
const form = document.getElementById('signup-form');
|
||||
const errorBox = document.getElementById('error');
|
||||
form.addEventListener('submit', async (e) => {
|
||||
e.preventDefault();
|
||||
errorBox.classList.add('hidden');
|
||||
const data = Object.fromEntries(new FormData(form).entries());
|
||||
const res = await fetch('/api/affiliates/signup', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(data)
|
||||
});
|
||||
const json = await res.json().catch(() => ({}));
|
||||
if (!res.ok) {
|
||||
errorBox.textContent = json.error || 'Unable to create account';
|
||||
errorBox.classList.remove('hidden');
|
||||
return;
|
||||
}
|
||||
if (json.verificationRequired) {
|
||||
window.location.href = '/affiliate-verification-sent';
|
||||
} else {
|
||||
window.location.href = '/affiliate-dashboard';
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
103
chat/public/affiliate-transactions.html
Normal file
@@ -0,0 +1,103 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Affiliate Transactions | Plugin Compass</title>
|
||||
<link rel="icon" type="image/png" href="/assets/Plugin.png">
|
||||
<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">
|
||||
<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=Inter:wght@400;600&display=swap" rel="stylesheet">
|
||||
<script>
|
||||
tailwind.config = { theme: { extend: { fontFamily: { sans: ['Inter', 'sans-serif'] } } } };
|
||||
</script>
|
||||
<!-- PostHog Analytics -->
|
||||
<script src="/posthog.js"></script>
|
||||
</head>
|
||||
<body class="min-h-screen bg-amber-50 text-gray-900">
|
||||
<nav class="sticky top-0 z-30 bg-amber-50/95 backdrop-blur border-b border-amber-200">
|
||||
<div class="max-w-6xl mx-auto px-4 sm:px-6 lg:px-8 h-14 flex items-center justify-between">
|
||||
<a href="/" class="flex items-center gap-2 font-semibold">
|
||||
<img src="/assets/Plugin.png" alt="Plugin Compass" class="w-8 h-8 rounded-lg">
|
||||
<span>Plugin<span class="text-green-700">Compass</span></span>
|
||||
</a>
|
||||
<div class="flex items-center gap-3 text-sm">
|
||||
<a href="/affiliate-dashboard" class="text-gray-700 hover:text-gray-900">Dashboard</a>
|
||||
<button id="logout" class="px-3 py-2 rounded-lg border border-gray-300 hover:bg-white">Logout</button>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<main class="max-w-6xl mx-auto px-4 sm:px-6 lg:px-8 py-10">
|
||||
<header class="mb-8 flex items-center justify-between">
|
||||
<div>
|
||||
<p class="text-xs font-semibold uppercase text-green-700 mb-1">Affiliate Program</p>
|
||||
<h1 class="text-3xl font-bold">Transaction History</h1>
|
||||
<p class="text-gray-600">A detailed record of all your attributed commissions.</p>
|
||||
</div>
|
||||
<a href="/affiliate-dashboard" class="px-4 py-2 rounded-lg border border-gray-300 bg-white text-sm font-semibold hover:bg-amber-50">Back to dashboard</a>
|
||||
</header>
|
||||
|
||||
<section class="bg-white border border-amber-200 rounded-2xl shadow-sm overflow-hidden">
|
||||
<div class="overflow-x-auto">
|
||||
<table class="w-full text-left text-sm">
|
||||
<thead class="bg-amber-50/50 border-b border-amber-200 text-gray-600 font-semibold">
|
||||
<tr>
|
||||
<th class="px-6 py-4">Date</th>
|
||||
<th class="px-6 py-4">User ID</th>
|
||||
<th class="px-6 py-4">Plan</th>
|
||||
<th class="px-6 py-4">Commission</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="transactions-body" class="divide-y divide-amber-100">
|
||||
<tr>
|
||||
<td colspan="4" class="px-6 py-10 text-center text-gray-500">Loading transactions...</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<script>
|
||||
const transactionsBody = document.getElementById('transactions-body');
|
||||
const logoutBtn = document.getElementById('logout');
|
||||
|
||||
async function loadTransactions() {
|
||||
const res = await fetch('/api/affiliates/transactions');
|
||||
if (res.status === 401) {
|
||||
window.location.href = '/affiliate-login';
|
||||
return;
|
||||
}
|
||||
const json = await res.json().catch(() => ({}));
|
||||
const transactions = json.transactions || [];
|
||||
renderTransactions(transactions);
|
||||
}
|
||||
|
||||
function renderTransactions(records) {
|
||||
if (!records.length) {
|
||||
transactionsBody.innerHTML = '<tr><td colspan="4" class="px-6 py-10 text-center text-gray-500">No transactions yet. Share your links to start earning.</td></tr>';
|
||||
return;
|
||||
}
|
||||
transactionsBody.innerHTML = records.map((r) => `
|
||||
<tr class="hover:bg-amber-50/50 transition-colors">
|
||||
<td class="px-6 py-4 text-gray-600">${new Date(r.createdAt).toLocaleDateString()} ${new Date(r.createdAt).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}</td>
|
||||
<td class="px-6 py-4 font-medium text-gray-900">${r.userId || '—'}</td>
|
||||
<td class="px-6 py-4 capitalize"><span class="px-2 py-1 rounded-full bg-green-100 text-green-700 text-xs font-semibold">${r.plan}</span></td>
|
||||
<td class="px-6 py-4 font-bold text-gray-900">$${Number(r.amount || 0).toFixed(2)}</td>
|
||||
</tr>
|
||||
`).join('');
|
||||
}
|
||||
|
||||
logoutBtn.addEventListener('click', async () => {
|
||||
await fetch('/api/affiliates/logout', { method: 'POST' });
|
||||
localStorage.removeItem('plugin_compass_onboarding_completed');
|
||||
window.location.href = '/affiliate-login';
|
||||
});
|
||||
|
||||
loadTransactions();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
236
chat/public/affiliate-verification-sent.html
Normal file
@@ -0,0 +1,236 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Registration Successful | Plugin Compass</title>
|
||||
<link rel="icon" type="image/png" href="/assets/Plugin.png">
|
||||
<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">
|
||||
<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=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
|
||||
|
||||
<script>
|
||||
tailwind.config = {
|
||||
theme: {
|
||||
extend: {
|
||||
fontFamily: {
|
||||
sans: ['Inter', 'sans-serif'],
|
||||
},
|
||||
colors: {
|
||||
brand: {
|
||||
50: '#eef2ff',
|
||||
100: '#e0e7ff',
|
||||
200: '#c7d2fe',
|
||||
300: '#a5b4fc',
|
||||
400: '#818cf8',
|
||||
500: '#6366f1',
|
||||
600: '#4f46e5',
|
||||
700: '#4338ca',
|
||||
800: '#3730a3',
|
||||
900: '#312e81',
|
||||
950: '#1e1b4b',
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style>
|
||||
::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
}
|
||||
::-webkit-scrollbar-track {
|
||||
background: #fdf6ed;
|
||||
}
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: #d1d5db;
|
||||
border-radius: 4px;
|
||||
}
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background: #9ca3af;
|
||||
}
|
||||
|
||||
.glass-nav {
|
||||
background: rgba(251, 246, 239, 0.9);
|
||||
backdrop-filter: blur(12px);
|
||||
-webkit-backdrop-filter: blur(12px);
|
||||
border-bottom: 1px solid rgba(0, 66, 37, 0.1);
|
||||
}
|
||||
|
||||
.glass-panel {
|
||||
background: rgba(255, 255, 255, 0.8);
|
||||
backdrop-filter: blur(12px);
|
||||
-webkit-backdrop-filter: blur(12px);
|
||||
border: 1px solid rgba(0, 66, 37, 0.1);
|
||||
}
|
||||
</style>
|
||||
|
||||
<!-- PostHog Analytics -->
|
||||
<script src="/posthog.js"></script>
|
||||
</head>
|
||||
<body class="bg-amber-50 text-gray-900 font-sans antialiased min-h-screen flex flex-col">
|
||||
|
||||
<!-- Navigation -->
|
||||
<nav class="fixed w-full z-50 glass-nav transition-all duration-300" id="navbar">
|
||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div class="flex justify-between items-center h-20">
|
||||
<!-- Logo -->
|
||||
<a href="/" class="flex-shrink-0 flex items-center gap-2 cursor-pointer">
|
||||
<img src="/assets/Plugin.png" alt="Plugin Compass" class="w-8 h-8">
|
||||
<span class="font-bold text-xl tracking-tight text-gray-800">Plugin<span class="text-green-700">Compass</span></span>
|
||||
</a>
|
||||
|
||||
<!-- Desktop Menu -->
|
||||
<div class="hidden md:flex items-center space-x-8">
|
||||
<a href="/features"
|
||||
class="text-gray-700 hover:text-gray-900 transition-colors text-sm font-medium">Features</a>
|
||||
<a href="/pricing"
|
||||
class="text-gray-700 hover:text-gray-900 transition-colors text-sm font-medium">Pricing</a>
|
||||
<a href="/docs"
|
||||
class="text-gray-700 hover:text-gray-900 transition-colors text-sm font-medium">Docs</a>
|
||||
</div>
|
||||
|
||||
<!-- CTA Buttons -->
|
||||
<div class="hidden md:flex items-center gap-4">
|
||||
<a href="/affiliate-login" class="text-gray-700 hover:text-gray-900 font-medium text-sm transition-colors">Sign In</a>
|
||||
<a href="/affiliate" class="bg-green-700 hover:bg-green-600 text-white px-5 py-2.5 rounded-full font-medium text-sm transition-all shadow-lg shadow-green-700/20 hover:shadow-green-700/40 transform hover:-translate-y-0.5">
|
||||
Join Program
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<!-- Mobile Menu Button -->
|
||||
<div class="md:hidden flex items-center">
|
||||
<button id="mobile-menu-btn" class="text-gray-700 hover:text-gray-900 focus:outline-none">
|
||||
<i class="fa-solid fa-bars text-xl"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Mobile Menu Panel -->
|
||||
<div id="mobile-menu" class="hidden md:hidden bg-amber-50 border-b border-amber-200">
|
||||
<div class="px-4 pt-2 pb-6 space-y-1">
|
||||
<a href="/features"
|
||||
class="block px-3 py-3 text-base font-medium text-gray-700 hover:text-gray-900 hover:bg-amber-100 rounded-md">Features</a>
|
||||
<a href="/pricing"
|
||||
class="block px-3 py-3 text-base font-medium text-gray-700 hover:text-gray-900 hover:bg-amber-100 rounded-md">Pricing</a>
|
||||
<a href="/docs"
|
||||
class="block px-3 py-3 text-base font-medium text-gray-700 hover:text-gray-900 hover:bg-amber-100 rounded-md">Docs</a>
|
||||
<div class="pt-4 flex flex-col gap-3">
|
||||
<a href="/affiliate-login" class="w-full text-center py-2 text-gray-700 font-medium">Sign In</a>
|
||||
<a href="/affiliate" class="w-full bg-green-700 text-white text-center py-3 rounded-lg font-medium">Join Program</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<main class="flex-grow flex items-center justify-center px-4 pt-32 pb-12">
|
||||
<div class="max-w-lg w-full">
|
||||
<div class="glass-panel p-10 rounded-3xl shadow-2xl shadow-green-900/10 text-center">
|
||||
<div class="w-20 h-20 bg-green-100 rounded-full flex items-center justify-center text-green-700 mx-auto mb-8">
|
||||
<i class="fa-solid fa-envelope-open-text text-3xl"></i>
|
||||
</div>
|
||||
|
||||
<h1 class="text-3xl font-bold text-gray-900 mb-4">Check your email</h1>
|
||||
<p class="text-lg text-gray-600 mb-8">
|
||||
We've sent a verification link to your email address. Please click the link to verify your affiliate account and start earning commissions.
|
||||
</p>
|
||||
|
||||
<div class="bg-amber-100/50 rounded-2xl p-6 mb-8 border border-green-700/10 text-sm text-gray-700 text-left">
|
||||
<h3 class="font-bold text-gray-900 mb-2 flex items-center gap-2">
|
||||
<i class="fa-solid fa-circle-info text-green-700"></i>
|
||||
Didn't receive the email?
|
||||
</h3>
|
||||
<ul class="list-disc pl-5 space-y-1">
|
||||
<li>Check your spam or junk folder</li>
|
||||
<li>Verify that your email address was entered correctly</li>
|
||||
<li>Wait a few minutes as delivery can sometimes be delayed</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-4">
|
||||
<a href="/affiliate-login" class="w-full bg-green-700 hover:bg-green-600 text-white font-bold py-4 rounded-xl transition-all shadow-lg shadow-green-700/20 transform hover:-translate-y-0.5">
|
||||
Back to Login
|
||||
</a>
|
||||
<a href="/" class="text-green-700 hover:underline font-medium">
|
||||
Return to Homepage
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<!-- Footer -->
|
||||
<footer class="bg-white border-t border-green-200 pt-16 pb-8">
|
||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div class="grid grid-cols-2 md:grid-cols-4 gap-12 mb-16">
|
||||
<div class="col-span-2 md:col-span-1">
|
||||
<div class="flex items-center gap-2 mb-6">
|
||||
<img src="/assets/Plugin.png" alt="Plugin Compass" class="w-8 h-8">
|
||||
<span class="font-bold text-xl tracking-tight text-gray-800">Plugin<span
|
||||
class="text-green-700">Compass</span></span>
|
||||
</div>
|
||||
<p class="text-gray-600 text-sm leading-relaxed">
|
||||
The smart way for WordPress site owners to replace expensive plugin subscriptions with custom
|
||||
solutions. Save thousands monthly.
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<h4 class="font-bold text-gray-900 mb-6">Product</h4>
|
||||
<ul class="space-y-4 text-sm">
|
||||
<li><a href="/features" class="text-gray-600 hover:text-green-700">Features</a></li>
|
||||
<li><a href="/pricing" class="text-gray-600 hover:text-green-700">Pricing</a></li>
|
||||
<li><a href="#" class="text-gray-600 hover:text-green-700">Templates</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div>
|
||||
<h4 class="font-bold text-gray-900 mb-6">Resources</h4>
|
||||
<ul class="space-y-4 text-sm">
|
||||
<li><a href="/docs" class="text-gray-600 hover:text-green-700">Documentation</a></li>
|
||||
<li><a href="/faq" class="text-gray-600 hover:text-green-700">FAQ</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div>
|
||||
<h4 class="font-bold text-gray-900 mb-6">Legal</h4>
|
||||
<ul class="space-y-4 text-sm">
|
||||
<li><a href="/privacy"
|
||||
class="text-gray-600 hover:text-green-700">Privacy Policy</a></li>
|
||||
<li><a href="/terms"
|
||||
class="text-gray-600 hover:text-green-700">Terms of Service</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="border-t border-gray-100 pt-8 flex justify-center">
|
||||
<p class="text-gray-500 text-xs text-center">© 2026 Plugin Compass. All rights reserved.</p>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<script>
|
||||
// Navigation functionality
|
||||
const mobileMenuBtn = document.getElementById('mobile-menu-btn');
|
||||
const mobileMenu = document.getElementById('mobile-menu');
|
||||
|
||||
if (mobileMenuBtn && mobileMenu) {
|
||||
mobileMenuBtn.addEventListener('click', () => {
|
||||
mobileMenu.classList.toggle('hidden');
|
||||
});
|
||||
}
|
||||
|
||||
// Navbar scroll effect
|
||||
window.addEventListener('scroll', () => {
|
||||
const navbar = document.getElementById('navbar');
|
||||
if (!navbar) return;
|
||||
if (window.scrollY > 20) {
|
||||
navbar.classList.add('shadow-md', 'h-16');
|
||||
navbar.classList.remove('h-20');
|
||||
} else {
|
||||
navbar.classList.remove('shadow-md', 'h-16');
|
||||
navbar.classList.add('h-20');
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
95
chat/public/affiliate-verify-email.html
Normal file
@@ -0,0 +1,95 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Verify Email | Plugin Compass Affiliate Program</title>
|
||||
<link rel="icon" type="image/png" href="/assets/Plugin.png">
|
||||
<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">
|
||||
<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=Inter:wght@400;600&display=swap" rel="stylesheet">
|
||||
<script>
|
||||
tailwind.config = { theme: { extend: { fontFamily: { sans: ['Inter', 'sans-serif'] } } } };
|
||||
</script>
|
||||
|
||||
<!-- PostHog Analytics -->
|
||||
<script src="/posthog.js"></script>
|
||||
</head>
|
||||
<body class="bg-amber-50 text-gray-900 font-sans antialiased min-h-screen flex flex-col">
|
||||
<nav class="w-full">
|
||||
<div class="max-w-4xl mx-auto px-4 py-6 flex items-center justify-between">
|
||||
<a href="/" class="flex items-center gap-2 font-bold text-lg text-gray-800">
|
||||
<img src="/assets/Plugin.png" alt="Plugin Compass" class="w-8 h-8">
|
||||
Plugin Compass
|
||||
</a>
|
||||
<a href="/affiliate-login" class="text-sm text-green-700 hover:underline font-semibold">Back to affiliate login</a>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<main class="flex-grow flex items-center justify-center px-4 py-12">
|
||||
<div class="max-w-xl w-full bg-white/80 border border-gray-200 rounded-2xl shadow-xl shadow-green-900/5 p-8">
|
||||
<div class="text-center">
|
||||
<div class="w-14 h-14 rounded-full bg-green-700 text-white flex items-center justify-center mx-auto mb-4">
|
||||
<i class="fa-solid fa-envelope-circle-check text-2xl"></i>
|
||||
</div>
|
||||
<h1 class="text-2xl font-bold mb-2">Verify your email</h1>
|
||||
<p class="text-gray-600 mb-6">We sent you a verification link for your affiliate account. Click it to start earning commissions.</p>
|
||||
<div id="verify-status" class="text-sm text-gray-700 bg-amber-50 border border-amber-100 rounded-lg px-4 py-3"></div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<footer class="py-6 text-center text-gray-500 text-xs">
|
||||
<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>
|
||||
|
||||
<script>
|
||||
(async () => {
|
||||
const statusEl = document.getElementById('verify-status');
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
const token = params.get('token');
|
||||
|
||||
function setStatus(message, isError = false) {
|
||||
statusEl.textContent = message || '';
|
||||
statusEl.classList.toggle('text-red-700', isError);
|
||||
statusEl.classList.toggle('border-red-200', isError);
|
||||
statusEl.classList.toggle('bg-red-50', isError);
|
||||
statusEl.classList.toggle('text-green-700', !isError);
|
||||
statusEl.classList.toggle('border-green-100', !isError);
|
||||
statusEl.classList.toggle('bg-green-50', !isError);
|
||||
}
|
||||
|
||||
if (!token) {
|
||||
setStatus('Missing verification token. Please open the link from your email again.', true);
|
||||
return;
|
||||
}
|
||||
|
||||
setStatus('Verifying your email...', false);
|
||||
try {
|
||||
const resp = await fetch(`/api/affiliates/verify-email?token=${encodeURIComponent(token)}`);
|
||||
const data = await resp.json().catch(() => ({}));
|
||||
if (!resp.ok) {
|
||||
setStatus(data.error || 'Verification failed. Please request a new link.', true);
|
||||
return;
|
||||
}
|
||||
setStatus(data.message || 'Email verified! Redirecting...', false);
|
||||
|
||||
// Redirect to dashboard
|
||||
const redirectUrl = data.redirect || '/affiliate-dashboard';
|
||||
setTimeout(() => {
|
||||
window.location.href = redirectUrl;
|
||||
}, 1500);
|
||||
} catch (err) {
|
||||
setStatus('Verification failed. Please try again.', true);
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
172
chat/public/affiliate-withdrawal.html
Normal file
@@ -0,0 +1,172 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Request Withdrawal | Plugin Compass</title>
|
||||
<link rel="icon" type="image/png" href="/assets/Plugin.png">
|
||||
<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">
|
||||
<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=Inter:wght@400;600&display=swap" rel="stylesheet">
|
||||
<script>
|
||||
tailwind.config = { theme: { extend: { fontFamily: { sans: ['Inter', 'sans-serif'] } } } };
|
||||
</script>
|
||||
|
||||
<!-- PostHog Analytics -->
|
||||
<script src="/posthog.js"></script>
|
||||
</head>
|
||||
<body class="min-h-screen bg-amber-50 text-gray-900">
|
||||
<nav class="sticky top-0 z-30 bg-amber-50/95 backdrop-blur border-b border-amber-200">
|
||||
<div class="max-w-6xl mx-auto px-4 sm:px-6 lg:px-8 h-14 flex items-center justify-between">
|
||||
<a href="/affiliate-dashboard" class="flex items-center gap-2 font-semibold">
|
||||
<img src="/assets/Plugin.png" alt="Plugin Compass" class="w-8 h-8 rounded-lg">
|
||||
<span>Plugin<span class="text-green-700">Compass</span></span>
|
||||
</a>
|
||||
<div class="flex items-center gap-3 text-sm">
|
||||
<a href="/affiliate-dashboard" class="text-gray-700 hover:text-gray-900 hidden sm:inline">Dashboard</a>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<main class="max-w-2xl mx-auto px-4 sm:px-6 lg:px-8 py-10">
|
||||
<header class="mb-8">
|
||||
<a href="/affiliate-dashboard" class="inline-flex items-center text-sm text-green-700 hover:underline mb-4">
|
||||
<i class="fas fa-arrow-left mr-2"></i> Back to Dashboard
|
||||
</a>
|
||||
<h1 class="text-3xl font-bold">Request Withdrawal</h1>
|
||||
<p class="text-gray-600 mt-2">Submit a withdrawal request to receive your earnings.</p>
|
||||
</header>
|
||||
|
||||
<div class="bg-white border border-amber-200 rounded-2xl p-6 shadow-sm">
|
||||
<div class="mb-6 p-4 bg-amber-50 border border-amber-200 rounded-xl">
|
||||
<p class="text-sm text-gray-700">Available Balance</p>
|
||||
<p class="text-3xl font-bold mt-1" id="available-balance">$0.00</p>
|
||||
</div>
|
||||
|
||||
<form id="withdrawal-form" class="space-y-6">
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-900 mb-2">PayPal Email Address</label>
|
||||
<input type="email" id="paypal-email" name="paypal-email" required
|
||||
class="w-full px-4 py-3 border border-gray-300 rounded-xl focus:ring-2 focus:ring-green-700 focus:border-transparent outline-none"
|
||||
placeholder="your@email.com">
|
||||
<p class="text-xs text-gray-500 mt-1">Enter the PayPal email where you want to receive your payout.</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-900 mb-2">Preferred Currency</label>
|
||||
<select id="currency" name="currency" required
|
||||
class="w-full px-4 py-3 border border-gray-300 rounded-xl focus:ring-2 focus:ring-green-700 focus:border-transparent outline-none bg-white">
|
||||
<option value="USD">USD - US Dollar</option>
|
||||
<option value="EUR">EUR - Euro</option>
|
||||
<option value="GBP">GBP - British Pound</option>
|
||||
<option value="CAD">CAD - Canadian Dollar</option>
|
||||
<option value="AUD">AUD - Australian Dollar</option>
|
||||
<option value="JPY">JPY - Japanese Yen</option>
|
||||
<option value="CHF">CHF - Swiss Franc</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-900 mb-2">Amount to Withdraw</label>
|
||||
<input type="number" id="amount" name="amount" required min="0" step="0.01"
|
||||
class="w-full px-4 py-3 border border-gray-300 rounded-xl focus:ring-2 focus:ring-green-700 focus:border-transparent outline-none"
|
||||
placeholder="0.00">
|
||||
<p class="text-xs text-gray-500 mt-1">Enter the amount you want to withdraw.</p>
|
||||
</div>
|
||||
|
||||
<div class="pt-4 border-t border-gray-200">
|
||||
<button type="submit" id="submit-btn"
|
||||
class="w-full px-6 py-3 rounded-xl bg-green-700 text-white font-semibold hover:bg-green-600 transition-colors">
|
||||
Submit Withdrawal Request
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div id="status-message" class="hidden p-4 rounded-xl text-sm"></div>
|
||||
</form>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<script>
|
||||
const form = document.getElementById('withdrawal-form');
|
||||
const amountInput = document.getElementById('amount');
|
||||
const statusMessage = document.getElementById('status-message');
|
||||
const submitBtn = document.getElementById('submit-btn');
|
||||
const balanceEl = document.getElementById('available-balance');
|
||||
let availableBalance = 0;
|
||||
|
||||
async function loadAffiliate() {
|
||||
const res = await fetch('/api/affiliates/me');
|
||||
if (res.status === 401) {
|
||||
window.location.href = '/affiliate-login';
|
||||
return;
|
||||
}
|
||||
const json = await res.json().catch(() => ({}));
|
||||
const affiliate = json.affiliate || {};
|
||||
availableBalance = Number(affiliate?.earnings?.total || 0);
|
||||
balanceEl.textContent = `$${availableBalance.toFixed(2)}`;
|
||||
amountInput.max = availableBalance;
|
||||
}
|
||||
|
||||
function showMessage(message, isError = false) {
|
||||
statusMessage.classList.remove('hidden', 'bg-green-50', 'text-green-800', 'border-green-200', 'bg-red-50', 'text-red-800', 'border-red-200');
|
||||
statusMessage.classList.add(
|
||||
isError ? 'bg-red-50 text-red-800 border border-red-200' : 'bg-green-50 text-green-800 border border-green-200'
|
||||
);
|
||||
statusMessage.textContent = message;
|
||||
}
|
||||
|
||||
form.addEventListener('submit', async (e) => {
|
||||
e.preventDefault();
|
||||
const paypalEmail = document.getElementById('paypal-email').value.trim();
|
||||
const currency = document.getElementById('currency').value;
|
||||
const amount = parseFloat(amountInput.value);
|
||||
|
||||
if (!paypalEmail || !paypalEmail.includes('@')) {
|
||||
showMessage('Please enter a valid PayPal email address.', true);
|
||||
return;
|
||||
}
|
||||
|
||||
if (amount <= 0) {
|
||||
showMessage('Please enter a valid amount.', true);
|
||||
return;
|
||||
}
|
||||
|
||||
if (amount > availableBalance) {
|
||||
showMessage('Amount exceeds available balance.', true);
|
||||
return;
|
||||
}
|
||||
|
||||
submitBtn.disabled = true;
|
||||
submitBtn.textContent = 'Submitting...';
|
||||
|
||||
try {
|
||||
const res = await fetch('/api/affiliates/withdrawals', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ paypalEmail, currency, amount })
|
||||
});
|
||||
const json = await res.json().catch(() => ({}));
|
||||
|
||||
if (res.ok) {
|
||||
showMessage('Withdrawal request submitted successfully!');
|
||||
form.reset();
|
||||
setTimeout(() => {
|
||||
window.location.href = '/affiliate-dashboard';
|
||||
}, 2000);
|
||||
} else {
|
||||
showMessage(json.error || 'Failed to submit withdrawal request.', true);
|
||||
}
|
||||
} catch (err) {
|
||||
showMessage('An error occurred. Please try again.', true);
|
||||
} finally {
|
||||
submitBtn.disabled = false;
|
||||
submitBtn.textContent = 'Submit Withdrawal Request';
|
||||
}
|
||||
});
|
||||
|
||||
loadAffiliate();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
258
chat/public/affiliate.html
Normal file
@@ -0,0 +1,258 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" class="scroll-smooth">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Affiliate Program | Plugin Compass</title>
|
||||
<link rel="icon" type="image/png" href="/assets/Plugin.png">
|
||||
<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">
|
||||
<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=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
|
||||
<script>
|
||||
tailwind.config = {
|
||||
theme: {
|
||||
extend: {
|
||||
fontFamily: { sans: ['Inter', 'sans-serif'] },
|
||||
colors: {
|
||||
brand: {
|
||||
50: '#eef2ff',
|
||||
500: '#6366f1',
|
||||
600: '#4f46e5',
|
||||
700: '#4338ca'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- PostHog Analytics -->
|
||||
<script src="/posthog.js"></script>
|
||||
</head>
|
||||
|
||||
<body class="bg-amber-50 text-gray-900 font-sans antialiased">
|
||||
<nav class="sticky top-0 z-30 bg-amber-50/95 backdrop-blur border-b border-amber-200">
|
||||
<div class="max-w-6xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div class="flex items-center justify-between h-16">
|
||||
<a href="/" class="flex items-center gap-2 font-bold">
|
||||
<img src="/assets/Plugin.png" alt="Plugin Compass" class="w-8 h-8 rounded-lg">
|
||||
<span class="text-gray-900">Plugin<span class="text-green-700">Compass</span></span>
|
||||
</a>
|
||||
<div class="hidden sm:flex items-center gap-4 text-sm">
|
||||
<a class="text-gray-700 hover:text-gray-900" href="/pricing">Pricing</a>
|
||||
<a class="text-gray-700 hover:text-gray-900" href="/features">Features</a>
|
||||
<a class="text-gray-700 hover:text-gray-900" href="/affiliate-dashboard">Affiliate Dashboard</a>
|
||||
<a class="px-4 py-2 rounded-lg border border-green-700 text-green-700 font-semibold hover:bg-green-50"
|
||||
href="/affiliate-login">Affiliate Login</a>
|
||||
<a class="px-4 py-2 rounded-lg bg-green-700 text-white font-semibold shadow hover:bg-green-600"
|
||||
href="/affiliate-signup">Join program</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<header class="max-w-5xl mx-auto px-4 sm:px-6 lg:px-8 pt-14 pb-12 text-center">
|
||||
<p
|
||||
class="inline-flex items-center gap-2 px-3 py-1 rounded-full bg-green-100 text-green-800 text-xs font-semibold mb-4">
|
||||
<i class="fa-solid fa-gem"></i> Earn 7.5% on every paid plan
|
||||
</p>
|
||||
<h1 class="text-4xl md:text-5xl font-bold text-gray-900 leading-tight mb-4">Partner with Plugin Compass and grow
|
||||
recurring revenue</h1>
|
||||
<p class="text-lg md:text-xl text-gray-700 max-w-3xl mx-auto mb-8">Share the AI builder that replaces expensive
|
||||
plugins with custom solutions. Every customer you send earns you <strong>7.5% commission</strong> on their paid
|
||||
plan — with transparent tracking inside your dashboard.</p>
|
||||
<div class="flex flex-wrap justify-center gap-4">
|
||||
<a href="/affiliate-signup"
|
||||
class="px-6 py-3 rounded-full bg-green-700 text-white font-semibold shadow-lg hover:bg-green-600">Become an
|
||||
affiliate</a>
|
||||
<a href="/affiliate-login"
|
||||
class="px-6 py-3 rounded-full border border-green-700 text-green-700 font-semibold hover:bg-green-50">Login to
|
||||
dashboard</a>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main class="max-w-6xl mx-auto px-4 sm:px-6 lg:px-8 pb-16">
|
||||
<section class="grid grid-cols-1 md:grid-cols-3 gap-6 mb-12">
|
||||
<div class="bg-white rounded-2xl shadow-sm border border-amber-200 p-6">
|
||||
<div class="w-10 h-10 rounded-lg bg-green-100 text-green-800 flex items-center justify-center mb-4">
|
||||
<i class="fa-solid fa-hand-holding-dollar"></i>
|
||||
</div>
|
||||
<h3 class="text-lg font-semibold mb-2">Recurring 7.5% payouts</h3>
|
||||
<p class="text-gray-700 text-sm">Earn 7.5% on every paid billing cycle (Business & Enterprise plans). Payout
|
||||
records are visible in your dashboard.</p>
|
||||
</div>
|
||||
<div class="bg-white rounded-2xl shadow-sm border border-amber-200 p-6">
|
||||
<div class="w-10 h-10 rounded-lg bg-green-100 text-green-800 flex items-center justify-center mb-4">
|
||||
<i class="fa-solid fa-link"></i>
|
||||
</div>
|
||||
<h3 class="text-lg font-semibold mb-2">Flexible tracking links</h3>
|
||||
<p class="text-gray-700 text-sm">Generate unlimited tracking links for campaigns. We keep attribution on signup
|
||||
and when customers upgrade.</p>
|
||||
</div>
|
||||
<div class="bg-white rounded-2xl shadow-sm border border-amber-200 p-6">
|
||||
<div class="w-10 h-10 rounded-lg bg-green-100 text-green-800 flex items-center justify-center mb-4">
|
||||
<i class="fa-solid fa-gauge-high"></i>
|
||||
</div>
|
||||
<h3 class="text-lg font-semibold mb-2">Clear dashboards</h3>
|
||||
<p class="text-gray-700 text-sm">Track earnings, see attributed plans, and copy ready-to-share pricing links
|
||||
from one place.</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="bg-white rounded-3xl shadow-sm border border-amber-200 p-8 mb-12">
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-6 items-center">
|
||||
<div>
|
||||
<p class="text-xs font-semibold uppercase text-green-700 mb-2">Why customers convert</p>
|
||||
<h2 class="text-2xl font-bold mb-3">Plan benefits you can pitch</h2>
|
||||
<ul class="space-y-3 text-gray-700">
|
||||
<li class="flex gap-3"><i class="fa-solid fa-circle-check text-green-600 mt-1"></i><span>Business plan —
|
||||
unlimited custom app builds, priority queueing, and premium templates.</span></li>
|
||||
<li class="flex gap-3"><i class="fa-solid fa-circle-check text-green-600 mt-1"></i><span>Enterprise plan —
|
||||
unlimited apps, fastest generation speed, and dedicated support for agencies.</span></li>
|
||||
<li class="flex gap-3"><i class="fa-solid fa-circle-check text-green-600 mt-1"></i><span>All plans replace
|
||||
expensive plugin stacks with AI-built, fully owned code.</span></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="bg-amber-50 rounded-2xl p-6 border border-amber-200">
|
||||
<h3 class="text-lg font-semibold mb-3">How payouts work</h3>
|
||||
<ol class="space-y-2 text-gray-700 text-sm">
|
||||
<li class="flex gap-3"><span class="font-bold text-green-700">1.</span> Create a tracking link in your
|
||||
dashboard.</li>
|
||||
<li class="flex gap-3"><span class="font-bold text-green-700">2.</span> Visitors who sign up carry your code
|
||||
into checkout.</li>
|
||||
<li class="flex gap-3"><span class="font-bold text-green-700">3.</span> When they activate Business or
|
||||
Enterprise, you earn 7.5%.</li>
|
||||
</ol>
|
||||
<div class="mt-4 p-4 bg-white rounded-xl border border-amber-200 text-sm">
|
||||
<p class="font-semibold text-gray-900 mb-1">Example earnings</p>
|
||||
<p class="text-gray-700">You earn 7.5% of each paid billing cycle. Share your tracking link to start earning
|
||||
recurring payouts.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="text-center bg-green-700 text-white rounded-3xl p-10 shadow-lg">
|
||||
<h3 class="text-2xl font-bold mb-3">Ready to partner?</h3>
|
||||
<p class="text-lg text-green-50 mb-6">Sign up in seconds, generate your first tracking link, and start earning
|
||||
recurring 7.5% commissions.</p>
|
||||
<div class="flex flex-wrap justify-center gap-3">
|
||||
<a href="/affiliate-signup" class="px-6 py-3 rounded-full bg-white text-green-800 font-semibold shadow">Join the
|
||||
program</a>
|
||||
<a href="/affiliate-dashboard"
|
||||
class="px-6 py-3 rounded-full border border-white/70 text-white font-semibold hover:bg-white/10">View
|
||||
dashboard</a>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
<footer class="bg-white border-t border-green-200 pt-16 pb-8">
|
||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div class="grid grid-cols-2 lg:grid-cols-5 gap-12 mb-16">
|
||||
<div class="col-span-2 lg:col-span-1">
|
||||
<div class="flex items-center gap-2 mb-6">
|
||||
<img src="/assets/Plugin.png" alt="Plugin Compass" class="w-8 h-8">
|
||||
<span class="font-bold text-xl tracking-tight text-gray-800">Plugin<span
|
||||
class="text-green-700">Compass</span></span>
|
||||
</div>
|
||||
<p class="text-gray-600 text-sm leading-relaxed">
|
||||
The smart way for WordPress site owners to replace expensive plugin subscriptions with custom
|
||||
solutions. Save thousands monthly.
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<h4 class="font-bold text-gray-900 mb-6">Product</h4>
|
||||
<ul class="space-y-4 text-sm">
|
||||
<li><a href="/features" class="text-gray-600 hover:text-green-700">Features</a></li>
|
||||
<li><a href="/pricing" class="text-gray-600 hover:text-green-700">Pricing</a></li>
|
||||
<li><a href="#" class="text-gray-600 hover:text-green-700">Templates</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div>
|
||||
<h4 class="font-bold text-gray-900 mb-6">Resources</h4>
|
||||
<ul class="space-y-4 text-sm">
|
||||
<li><a href="/docs" class="text-gray-600 hover:text-green-700">Documentation</a></li>
|
||||
<li><a href="/faq" class="text-gray-600 hover:text-green-700">FAQ</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div>
|
||||
<h4 class="font-bold text-gray-900 mb-6">Legal</h4>
|
||||
<ul class="space-y-4 text-sm">
|
||||
<li><a href="/privacy.html" class="text-gray-600 hover:text-green-700">Privacy Policy</a></li>
|
||||
<li><a href="/terms" class="text-gray-600 hover:text-green-700">Terms of Service</a></li>
|
||||
<li><a href="/contact" class="text-gray-600 hover:text-green-700">Contact Us</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="col-span-2 lg:col-span-1">
|
||||
<h4 class="font-bold text-gray-900 mb-6">Stay Updated</h4>
|
||||
<p class="text-gray-600 text-sm mb-4">Get the latest updates and WordPress tips.</p>
|
||||
<form id="footer-signup-form" class="flex flex-col gap-2">
|
||||
<input type="email" name="email" placeholder="Your email" required
|
||||
class="px-4 py-2 border border-green-200 rounded-lg focus:outline-none focus:ring-2 focus:ring-green-700/20 text-sm">
|
||||
<button type="submit"
|
||||
class="bg-green-700 hover:bg-green-600 text-white px-4 py-2 rounded-lg font-medium text-sm transition-colors shadow-lg shadow-green-700/10">
|
||||
Subscribe
|
||||
</button>
|
||||
</form>
|
||||
<div id="signup-message" class="mt-2 text-xs hidden"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="border-t border-gray-100 pt-8 flex justify-center">
|
||||
<p class="text-gray-500 text-xs text-center">© 2026 Plugin Compass. All rights reserved.</p>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
<script>
|
||||
// Email Signup Form Handler
|
||||
const signupForm = document.getElementById('footer-signup-form');
|
||||
const signupMessage = document.getElementById('signup-message');
|
||||
|
||||
if (signupForm) {
|
||||
signupForm.addEventListener('submit', async (e) => {
|
||||
e.preventDefault();
|
||||
const email = signupForm.querySelector('input[name="email"]').value;
|
||||
const button = signupForm.querySelector('button');
|
||||
|
||||
button.disabled = true;
|
||||
button.textContent = 'Subscribing...';
|
||||
|
||||
try {
|
||||
const response = await fetch('https://emailmarketing.modelrailway3d.co.uk/api/webhooks/incoming/wh_0Z49zi_DGj4-lKJMOPO8-g', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
email: email,
|
||||
source: 'plugin_compass_footer',
|
||||
timestamp: new Date().toISOString()
|
||||
})
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
signupMessage.textContent = 'Successfully subscribed!';
|
||||
signupMessage.className = 'mt-2 text-xs text-green-600';
|
||||
signupForm.reset();
|
||||
} else {
|
||||
throw new Error('Failed to subscribe');
|
||||
}
|
||||
} catch (error) {
|
||||
signupMessage.textContent = 'Failed to subscribe. Please try again.';
|
||||
signupMessage.className = 'mt-2 text-xs text-red-600';
|
||||
} finally {
|
||||
signupMessage.classList.remove('hidden');
|
||||
button.disabled = false;
|
||||
button.textContent = 'Subscribe';
|
||||
|
||||
setTimeout(() => {
|
||||
signupMessage.classList.add('hidden');
|
||||
}, 5000);
|
||||
}
|
||||
});
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
316
chat/public/animations.html
Normal file
@@ -0,0 +1,316 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>AI Owl Mascot Animations</title>
|
||||
<style>
|
||||
:root {
|
||||
--bg-color: #f8f9fa;
|
||||
--owl-white: #ffffff;
|
||||
--owl-gray: #e0e0e0;
|
||||
--owl-black: #1a1a1a;
|
||||
--accent-blue: #4a90e2;
|
||||
--accent-yellow: #f1c40f;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: var(--bg-color);
|
||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 20px;
|
||||
padding: 40px;
|
||||
justify-items: center;
|
||||
}
|
||||
|
||||
.card {
|
||||
background: white;
|
||||
padding: 20px;
|
||||
border-radius: 15px;
|
||||
box-shadow: 0 10px 20px rgba(0, 0, 0, 0.05);
|
||||
text-align: center;
|
||||
width: 250px;
|
||||
}
|
||||
|
||||
h3 {
|
||||
color: #555;
|
||||
margin-bottom: 20px;
|
||||
font-size: 1rem;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 1px;
|
||||
}
|
||||
|
||||
/* BASE OWL SVG STYLING */
|
||||
.owl-svg {
|
||||
width: 150px;
|
||||
height: auto;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.owl-body {
|
||||
fill: var(--owl-white);
|
||||
stroke: var(--owl-black);
|
||||
stroke-width: 8;
|
||||
}
|
||||
|
||||
.owl-eyes {
|
||||
fill: var(--owl-black);
|
||||
}
|
||||
|
||||
.owl-highlights {
|
||||
fill: white;
|
||||
}
|
||||
|
||||
.owl-beak {
|
||||
fill: var(--owl-black);
|
||||
}
|
||||
|
||||
.owl-feathers {
|
||||
fill: none;
|
||||
stroke: var(--owl-black);
|
||||
stroke-width: 4;
|
||||
stroke-linecap: round;
|
||||
}
|
||||
|
||||
.prop {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
/* --- 1. PLANNING (The Strategist) --- */
|
||||
#planning .owl-eye-group {
|
||||
animation: eye-dart 4s infinite;
|
||||
}
|
||||
|
||||
#planning .lightbulb {
|
||||
visibility: visible;
|
||||
animation: bulb-glow 2s infinite;
|
||||
}
|
||||
|
||||
@keyframes eye-dart {
|
||||
|
||||
0%,
|
||||
20%,
|
||||
100% {
|
||||
transform: translateX(0);
|
||||
}
|
||||
|
||||
30%,
|
||||
50% {
|
||||
transform: translateX(-5px);
|
||||
}
|
||||
|
||||
60%,
|
||||
80% {
|
||||
transform: translateX(5px);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes bulb-glow {
|
||||
|
||||
0%,
|
||||
100% {
|
||||
opacity: 0.3;
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
50% {
|
||||
opacity: 1;
|
||||
transform: translateY(-5px);
|
||||
}
|
||||
}
|
||||
|
||||
/* --- 2. BUILDING (The Constructor) --- */
|
||||
#building .owl-wings {
|
||||
animation: wing-tap 0.5s infinite alternate ease-in-out;
|
||||
transform-origin: center;
|
||||
}
|
||||
|
||||
#building .hardhat {
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
@keyframes wing-tap {
|
||||
from {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
|
||||
to {
|
||||
transform: rotate(-5deg) translateY(-2px);
|
||||
}
|
||||
}
|
||||
|
||||
/* --- 3. DEBUGGING (The Inspector) --- */
|
||||
#debugging .owl-svg {
|
||||
animation: lean-in 3s infinite alternate ease-in-out;
|
||||
}
|
||||
|
||||
#debugging .magnifier {
|
||||
visibility: visible;
|
||||
animation: search 3s infinite ease-in-out;
|
||||
}
|
||||
|
||||
@keyframes lean-in {
|
||||
from {
|
||||
transform: scale(1);
|
||||
}
|
||||
|
||||
to {
|
||||
transform: scale(1.1) translateY(5px);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes search {
|
||||
|
||||
0%,
|
||||
100% {
|
||||
transform: translate(0, 0);
|
||||
}
|
||||
|
||||
50% {
|
||||
transform: translate(-20px, 10px);
|
||||
}
|
||||
}
|
||||
|
||||
/* --- 4. LAUNCH (The Celebration) --- */
|
||||
#launch .owl-svg {
|
||||
animation: hop 0.6s infinite alternate cubic-bezier(0.175, 0.885, 0.32, 1.275);
|
||||
}
|
||||
|
||||
#launch .owl-wings {
|
||||
animation: flap 0.2s infinite;
|
||||
transform-origin: center;
|
||||
}
|
||||
|
||||
#launch .sparkle {
|
||||
visibility: visible;
|
||||
animation: flash 0.8s infinite;
|
||||
}
|
||||
|
||||
@keyframes hop {
|
||||
from {
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
to {
|
||||
transform: translateY(-20px);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes flap {
|
||||
from {
|
||||
transform: scaleX(1);
|
||||
}
|
||||
|
||||
to {
|
||||
transform: scaleX(1.1);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes flash {
|
||||
|
||||
0%,
|
||||
100% {
|
||||
opacity: 0;
|
||||
transform: scale(0.5);
|
||||
}
|
||||
|
||||
50% {
|
||||
opacity: 1;
|
||||
transform: scale(1.2);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<div class="card" id="planning">
|
||||
<h3>Planning</h3>
|
||||
<svg class="owl-svg" viewBox="0 0 200 220">
|
||||
<g class="prop lightbulb" transform="translate(140, 20)">
|
||||
<circle cx="0" cy="0" r="15" fill="#f1c40f" />
|
||||
<rect x="-5" y="12" width="10" height="8" fill="#95a5a6" />
|
||||
</g>
|
||||
<path class="owl-body"
|
||||
d="M100,20 C140,20 170,40 170,90 C170,150 145,200 100,200 C55,200 30,150 30,90 C30,40 60,20 100,20 Z" />
|
||||
<path d="M50,45 L35,25 L65,35 Z" fill="black" />
|
||||
<path d="M150,45 L165,25 L135,35 Z" fill="black" />
|
||||
<g class="owl-eye-group">
|
||||
<circle class="owl-eyes" cx="70" cy="90" r="28" />
|
||||
<circle class="owl-eyes" cx="130" cy="90" r="28" />
|
||||
<circle class="owl-highlights" cx="78" cy="80" r="8" />
|
||||
<circle class="owl-highlights" cx="138" cy="80" r="8" />
|
||||
</g>
|
||||
<path class="owl-beak" d="M100,105 L92,125 L108,125 Z" />
|
||||
<path class="owl-feathers" d="M80,150 Q90,160 100,150 M110,150 Q120,160 130,150 M95,170 Q105,180 115,170" />
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<div class="card" id="building">
|
||||
<h3>Building</h3>
|
||||
<svg class="owl-svg" viewBox="0 0 200 220">
|
||||
<path class="prop hardhat" d="M60,35 Q100,10 140,35 L145,45 L55,45 Z" fill="#f1c40f" stroke="black"
|
||||
stroke-width="4" />
|
||||
<g class="owl-wings">
|
||||
<path d="M30,100 Q10,130 35,170" fill="none" stroke="black" stroke-width="8" stroke-linecap="round" />
|
||||
<path d="M170,100 Q190,130 165,170" fill="none" stroke="black" stroke-width="8"
|
||||
stroke-linecap="round" />
|
||||
</g>
|
||||
<path class="owl-body"
|
||||
d="M100,20 C140,20 170,40 170,90 C170,150 145,200 100,200 C55,200 30,150 30,90 C30,40 60,20 100,20 Z" />
|
||||
<g>
|
||||
<circle class="owl-eyes" cx="70" cy="90" r="28" />
|
||||
<circle class="owl-eyes" cx="130" cy="90" r="28" />
|
||||
<circle class="owl-highlights" cx="78" cy="80" r="8" />
|
||||
<circle class="owl-highlights" cx="138" cy="80" r="8" />
|
||||
</g>
|
||||
<path class="owl-beak" d="M100,105 L92,125 L108,125 Z" />
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<div class="card" id="debugging">
|
||||
<h3>Debugging</h3>
|
||||
<svg class="owl-svg" viewBox="0 0 200 220">
|
||||
<g class="prop magnifier" transform="translate(140, 130)">
|
||||
<circle cx="0" cy="0" r="20" fill="none" stroke="black" stroke-width="5" />
|
||||
<line x1="15" y1="15" x2="30" y2="30" stroke="black" stroke-width="8" stroke-linecap="round" />
|
||||
</g>
|
||||
<path class="owl-body"
|
||||
d="M100,20 C140,20 170,40 170,90 C170,150 145,200 100,200 C55,200 30,150 30,90 C30,40 60,20 100,20 Z" />
|
||||
<g>
|
||||
<circle class="owl-eyes" cx="70" cy="90" r="28" />
|
||||
<circle class="owl-eyes" cx="130" cy="90" r="28" />
|
||||
<circle class="owl-highlights" cx="78" cy="80" r="8" />
|
||||
<circle class="owl-highlights" cx="138" cy="80" r="8" />
|
||||
</g>
|
||||
<path class="owl-beak" d="M100,105 L92,125 L108,125 Z" />
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<div class="card" id="launch">
|
||||
<h3>Launch</h3>
|
||||
<svg class="owl-svg" viewBox="0 0 200 220">
|
||||
<path class="prop sparkle" d="M20,50 L25,40 L30,50 L40,55 L30,60 L25,70 L20,60 L10,55 Z" fill="#f1c40f" />
|
||||
<path class="prop sparkle" d="M160,30 L165,20 L170,30 L180,35 L170,40 L165,50 L160,40 L150,35 Z"
|
||||
fill="#f1c40f" />
|
||||
<g class="owl-wings">
|
||||
<path d="M30,100 Q0,80 15,140" fill="none" stroke="black" stroke-width="8" stroke-linecap="round" />
|
||||
<path d="M170,100 Q200,80 185,140" fill="none" stroke="black" stroke-width="8" stroke-linecap="round" />
|
||||
</g>
|
||||
<path class="owl-body"
|
||||
d="M100,20 C140,20 170,40 170,90 C170,150 145,200 100,200 C55,200 30,150 30,90 C30,40 60,20 100,20 Z" />
|
||||
<g>
|
||||
<circle class="owl-eyes" cx="70" cy="90" r="28" />
|
||||
<circle class="owl-eyes" cx="130" cy="90" r="28" />
|
||||
<circle class="owl-highlights" cx="78" cy="80" r="8" />
|
||||
<circle class="owl-highlights" cx="138" cy="80" r="8" />
|
||||
</g>
|
||||
<path class="owl-beak" d="M100,105 L92,125 L108,125 Z" />
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
||||
316
chat/public/animations.txt
Normal file
@@ -0,0 +1,316 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>AI Owl Mascot Animations</title>
|
||||
<style>
|
||||
:root {
|
||||
--bg-color: #f8f9fa;
|
||||
--owl-white: #ffffff;
|
||||
--owl-gray: #e0e0e0;
|
||||
--owl-black: #1a1a1a;
|
||||
--accent-blue: #4a90e2;
|
||||
--accent-yellow: #f1c40f;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: var(--bg-color);
|
||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 20px;
|
||||
padding: 40px;
|
||||
justify-items: center;
|
||||
}
|
||||
|
||||
.card {
|
||||
background: white;
|
||||
padding: 20px;
|
||||
border-radius: 15px;
|
||||
box-shadow: 0 10px 20px rgba(0, 0, 0, 0.05);
|
||||
text-align: center;
|
||||
width: 250px;
|
||||
}
|
||||
|
||||
h3 {
|
||||
color: #555;
|
||||
margin-bottom: 20px;
|
||||
font-size: 1rem;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 1px;
|
||||
}
|
||||
|
||||
/* BASE OWL SVG STYLING */
|
||||
.owl-svg {
|
||||
width: 150px;
|
||||
height: auto;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.owl-body {
|
||||
fill: var(--owl-white);
|
||||
stroke: var(--owl-black);
|
||||
stroke-width: 8;
|
||||
}
|
||||
|
||||
.owl-eyes {
|
||||
fill: var(--owl-black);
|
||||
}
|
||||
|
||||
.owl-highlights {
|
||||
fill: white;
|
||||
}
|
||||
|
||||
.owl-beak {
|
||||
fill: var(--owl-black);
|
||||
}
|
||||
|
||||
.owl-feathers {
|
||||
fill: none;
|
||||
stroke: var(--owl-black);
|
||||
stroke-width: 4;
|
||||
stroke-linecap: round;
|
||||
}
|
||||
|
||||
.prop {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
/* --- 1. PLANNING (The Strategist) --- */
|
||||
#planning .owl-eye-group {
|
||||
animation: eye-dart 4s infinite;
|
||||
}
|
||||
|
||||
#planning .lightbulb {
|
||||
visibility: visible;
|
||||
animation: bulb-glow 2s infinite;
|
||||
}
|
||||
|
||||
@keyframes eye-dart {
|
||||
|
||||
0%,
|
||||
20%,
|
||||
100% {
|
||||
transform: translateX(0);
|
||||
}
|
||||
|
||||
30%,
|
||||
50% {
|
||||
transform: translateX(-5px);
|
||||
}
|
||||
|
||||
60%,
|
||||
80% {
|
||||
transform: translateX(5px);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes bulb-glow {
|
||||
|
||||
0%,
|
||||
100% {
|
||||
opacity: 0.3;
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
50% {
|
||||
opacity: 1;
|
||||
transform: translateY(-5px);
|
||||
}
|
||||
}
|
||||
|
||||
/* --- 2. BUILDING (The Constructor) --- */
|
||||
#building .owl-wings {
|
||||
animation: wing-tap 0.5s infinite alternate ease-in-out;
|
||||
transform-origin: center;
|
||||
}
|
||||
|
||||
#building .hardhat {
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
@keyframes wing-tap {
|
||||
from {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
|
||||
to {
|
||||
transform: rotate(-5deg) translateY(-2px);
|
||||
}
|
||||
}
|
||||
|
||||
/* --- 3. DEBUGGING (The Inspector) --- */
|
||||
#debugging .owl-svg {
|
||||
animation: lean-in 3s infinite alternate ease-in-out;
|
||||
}
|
||||
|
||||
#debugging .magnifier {
|
||||
visibility: visible;
|
||||
animation: search 3s infinite ease-in-out;
|
||||
}
|
||||
|
||||
@keyframes lean-in {
|
||||
from {
|
||||
transform: scale(1);
|
||||
}
|
||||
|
||||
to {
|
||||
transform: scale(1.1) translateY(5px);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes search {
|
||||
|
||||
0%,
|
||||
100% {
|
||||
transform: translate(0, 0);
|
||||
}
|
||||
|
||||
50% {
|
||||
transform: translate(-20px, 10px);
|
||||
}
|
||||
}
|
||||
|
||||
/* --- 4. LAUNCH (The Celebration) --- */
|
||||
#launch .owl-svg {
|
||||
animation: hop 0.6s infinite alternate cubic-bezier(0.175, 0.885, 0.32, 1.275);
|
||||
}
|
||||
|
||||
#launch .owl-wings {
|
||||
animation: flap 0.2s infinite;
|
||||
transform-origin: center;
|
||||
}
|
||||
|
||||
#launch .sparkle {
|
||||
visibility: visible;
|
||||
animation: flash 0.8s infinite;
|
||||
}
|
||||
|
||||
@keyframes hop {
|
||||
from {
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
to {
|
||||
transform: translateY(-20px);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes flap {
|
||||
from {
|
||||
transform: scaleX(1);
|
||||
}
|
||||
|
||||
to {
|
||||
transform: scaleX(1.1);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes flash {
|
||||
|
||||
0%,
|
||||
100% {
|
||||
opacity: 0;
|
||||
transform: scale(0.5);
|
||||
}
|
||||
|
||||
50% {
|
||||
opacity: 1;
|
||||
transform: scale(1.2);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<div class="card" id="planning">
|
||||
<h3>Planning</h3>
|
||||
<svg class="owl-svg" viewBox="0 0 200 220">
|
||||
<g class="prop lightbulb" transform="translate(140, 20)">
|
||||
<circle cx="0" cy="0" r="15" fill="#f1c40f" />
|
||||
<rect x="-5" y="12" width="10" height="8" fill="#95a5a6" />
|
||||
</g>
|
||||
<path class="owl-body"
|
||||
d="M100,20 C140,20 170,40 170,90 C170,150 145,200 100,200 C55,200 30,150 30,90 C30,40 60,20 100,20 Z" />
|
||||
<path d="M50,45 L35,25 L65,35 Z" fill="black" />
|
||||
<path d="M150,45 L165,25 L135,35 Z" fill="black" />
|
||||
<g class="owl-eye-group">
|
||||
<circle class="owl-eyes" cx="70" cy="90" r="28" />
|
||||
<circle class="owl-eyes" cx="130" cy="90" r="28" />
|
||||
<circle class="owl-highlights" cx="78" cy="80" r="8" />
|
||||
<circle class="owl-highlights" cx="138" cy="80" r="8" />
|
||||
</g>
|
||||
<path class="owl-beak" d="M100,105 L92,125 L108,125 Z" />
|
||||
<path class="owl-feathers" d="M80,150 Q90,160 100,150 M110,150 Q120,160 130,150 M95,170 Q105,180 115,170" />
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<div class="card" id="building">
|
||||
<h3>Building</h3>
|
||||
<svg class="owl-svg" viewBox="0 0 200 220">
|
||||
<path class="prop hardhat" d="M60,35 Q100,10 140,35 L145,45 L55,45 Z" fill="#f1c40f" stroke="black"
|
||||
stroke-width="4" />
|
||||
<g class="owl-wings">
|
||||
<path d="M30,100 Q10,130 35,170" fill="none" stroke="black" stroke-width="8" stroke-linecap="round" />
|
||||
<path d="M170,100 Q190,130 165,170" fill="none" stroke="black" stroke-width="8"
|
||||
stroke-linecap="round" />
|
||||
</g>
|
||||
<path class="owl-body"
|
||||
d="M100,20 C140,20 170,40 170,90 C170,150 145,200 100,200 C55,200 30,150 30,90 C30,40 60,20 100,20 Z" />
|
||||
<g>
|
||||
<circle class="owl-eyes" cx="70" cy="90" r="28" />
|
||||
<circle class="owl-eyes" cx="130" cy="90" r="28" />
|
||||
<circle class="owl-highlights" cx="78" cy="80" r="8" />
|
||||
<circle class="owl-highlights" cx="138" cy="80" r="8" />
|
||||
</g>
|
||||
<path class="owl-beak" d="M100,105 L92,125 L108,125 Z" />
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<div class="card" id="debugging">
|
||||
<h3>Debugging</h3>
|
||||
<svg class="owl-svg" viewBox="0 0 200 220">
|
||||
<g class="prop magnifier" transform="translate(140, 130)">
|
||||
<circle cx="0" cy="0" r="20" fill="none" stroke="black" stroke-width="5" />
|
||||
<line x1="15" y1="15" x2="30" y2="30" stroke="black" stroke-width="8" stroke-linecap="round" />
|
||||
</g>
|
||||
<path class="owl-body"
|
||||
d="M100,20 C140,20 170,40 170,90 C170,150 145,200 100,200 C55,200 30,150 30,90 C30,40 60,20 100,20 Z" />
|
||||
<g>
|
||||
<circle class="owl-eyes" cx="70" cy="90" r="28" />
|
||||
<circle class="owl-eyes" cx="130" cy="90" r="28" />
|
||||
<circle class="owl-highlights" cx="78" cy="80" r="8" />
|
||||
<circle class="owl-highlights" cx="138" cy="80" r="8" />
|
||||
</g>
|
||||
<path class="owl-beak" d="M100,105 L92,125 L108,125 Z" />
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<div class="card" id="launch">
|
||||
<h3>Launch</h3>
|
||||
<svg class="owl-svg" viewBox="0 0 200 220">
|
||||
<path class="prop sparkle" d="M20,50 L25,40 L30,50 L40,55 L30,60 L25,70 L20,60 L10,55 Z" fill="#f1c40f" />
|
||||
<path class="prop sparkle" d="M160,30 L165,20 L170,30 L180,35 L170,40 L165,50 L160,40 L150,35 Z"
|
||||
fill="#f1c40f" />
|
||||
<g class="owl-wings">
|
||||
<path d="M30,100 Q0,80 15,140" fill="none" stroke="black" stroke-width="8" stroke-linecap="round" />
|
||||
<path d="M170,100 Q200,80 185,140" fill="none" stroke="black" stroke-width="8" stroke-linecap="round" />
|
||||
</g>
|
||||
<path class="owl-body"
|
||||
d="M100,20 C140,20 170,40 170,90 C170,150 145,200 100,200 C55,200 30,150 30,90 C30,40 60,20 100,20 Z" />
|
||||
<g>
|
||||
<circle class="owl-eyes" cx="70" cy="90" r="28" />
|
||||
<circle class="owl-eyes" cx="130" cy="90" r="28" />
|
||||
<circle class="owl-highlights" cx="78" cy="80" r="8" />
|
||||
<circle class="owl-highlights" cx="138" cy="80" r="8" />
|
||||
</g>
|
||||
<path class="owl-beak" d="M100,105 L92,125 L108,125 Z" />
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
||||
1554
chat/public/app.js
Normal file
2497
chat/public/apps.html
Normal file
BIN
chat/public/assets/Plugin.png
Normal file
|
After Width: | Height: | Size: 59 KiB |
BIN
chat/public/assets/Zai.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
BIN
chat/public/assets/animation.webp
Normal file
|
After Width: | Height: | Size: 2.5 MiB |
1
chat/public/assets/deepseek.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg fill="none" height="1320" viewBox="3.771 6.973 23.993 17.652" width="2500" xmlns="http://www.w3.org/2000/svg"><path d="m27.501 8.469c-.252-.123-.36.111-.508.23-.05.04-.093.09-.135.135-.368.395-.797.652-1.358.621-.821-.045-1.521.213-2.14.842-.132-.776-.57-1.238-1.235-1.535-.349-.155-.701-.309-.944-.645-.171-.238-.217-.504-.303-.765-.054-.159-.108-.32-.29-.348-.197-.031-.274.135-.352.273-.31.567-.43 1.192-.419 1.825.028 1.421.628 2.554 1.82 3.36.136.093.17.186.128.321-.081.278-.178.547-.264.824-.054.178-.135.217-.324.14a5.448 5.448 0 0 1 -1.719-1.169c-.848-.82-1.614-1.726-2.57-2.435-.225-.166-.449-.32-.681-.467-.976-.95.128-1.729.383-1.82.267-.096.093-.428-.77-.424s-1.653.293-2.659.677a2.782 2.782 0 0 1 -.46.135 9.554 9.554 0 0 0 -2.853-.1c-1.866.21-3.356 1.092-4.452 2.6-1.315 1.81-1.625 3.87-1.246 6.018.399 2.261 1.552 4.136 3.326 5.601 1.837 1.518 3.955 2.262 6.37 2.12 1.466-.085 3.1-.282 4.942-1.842.465.23.952.322 1.762.392.623.059 1.223-.031 1.687-.127.728-.154.677-.828.414-.953-2.132-.994-1.665-.59-2.09-.916 1.084-1.285 2.717-2.619 3.356-6.94.05-.343.007-.558 0-.837-.004-.168.034-.235.228-.254a4.084 4.084 0 0 0 1.529-.47c1.382-.757 1.938-1.997 2.07-3.485.02-.227-.004-.463-.243-.582zm-12.041 13.391c-2.067-1.627-3.07-2.162-3.483-2.138-.387.021-.318.465-.233.754.089.285.205.482.368.732.113.166.19.414-.112.598-.666.414-1.823-.139-1.878-.166-1.347-.793-2.473-1.842-3.267-3.276-.765-1.38-1.21-2.861-1.284-4.441-.02-.383.093-.518.472-.586a4.692 4.692 0 0 1 1.514-.04c2.109.31 3.905 1.255 5.41 2.749.86.853 1.51 1.871 2.18 2.865.711 1.057 1.478 2.063 2.454 2.887.343.289.619.51.881.672-.792.088-2.117.107-3.022-.61zm.99-6.38a.304.304 0 1 1 .609 0c0 .17-.136.304-.306.304a.3.3 0 0 1 -.303-.305zm3.077 1.581c-.197.08-.394.15-.584.159a1.246 1.246 0 0 1 -.79-.252c-.27-.227-.463-.354-.546-.752a1.752 1.752 0 0 1 .016-.582c.07-.324-.008-.531-.235-.72-.187-.155-.422-.196-.682-.196a.551.551 0 0 1 -.252-.078c-.108-.055-.197-.19-.112-.356.027-.053.159-.183.19-.207.352-.201.758-.135 1.134.016.349.142.611.404.99.773.388.448.457.573.678.906.174.264.333.534.441.842.066.192-.02.35-.248.448z" fill="#4d6bfe"/></svg>
|
||||
|
After Width: | Height: | Size: 2.1 KiB |
2
chat/public/assets/google.svg
Normal file
@@ -0,0 +1,2 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||
<svg width="800px" height="800px" viewBox="-3 0 262 262" xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="xMidYMid"><path d="M255.878 133.451c0-10.734-.871-18.567-2.756-26.69H130.55v48.448h71.947c-1.45 12.04-9.283 30.172-26.69 42.356l-.244 1.622 38.755 30.023 2.685.268c24.659-22.774 38.875-56.282 38.875-96.027" fill="#4285F4"/><path d="M130.55 261.1c35.248 0 64.839-11.605 86.453-31.622l-41.196-31.913c-11.024 7.688-25.82 13.055-45.257 13.055-34.523 0-63.824-22.773-74.269-54.25l-1.531.13-40.298 31.187-.527 1.465C35.393 231.798 79.49 261.1 130.55 261.1" fill="#34A853"/><path d="M56.281 156.37c-2.756-8.123-4.351-16.827-4.351-25.82 0-8.994 1.595-17.697 4.206-25.82l-.073-1.73L15.26 71.312l-1.335.635C5.077 89.644 0 109.517 0 130.55s5.077 40.905 13.925 58.602l42.356-32.782" fill="#FBBC05"/><path d="M130.55 50.479c24.514 0 41.05 10.589 50.479 19.438l36.844-35.974C195.245 12.91 165.798 0 130.55 0 79.49 0 35.393 29.301 13.925 71.947l42.211 32.783c10.59-31.477 39.891-54.251 74.414-54.251" fill="#EB4335"/></svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
1
chat/public/assets/mistral.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg" fill-rule="evenodd" clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="2"><g transform="translate(6 79.299) scale(1.96335)"><clipPath id="prefix__a"><path d="M0 0h254.667v180H0z"/></clipPath><g clip-path="url(#prefix__a)"><g transform="scale(1.33333)"><clipPath id="prefix__b"><path d="M0 0h190.141v135H0z"/></clipPath><g clip-path="url(#prefix__b)" fill-rule="nonzero"><path fill="#ffd800" d="M27.153 0h27.169v27.089H27.153zM135.815 0h27.169v27.089h-27.169z"/><path fill="#ffaf00" d="M27.153 27.091h54.329V54.18H27.153zM108.661 27.091h54.329V54.18h-54.329z"/><path fill="#ff8205" d="M27.153 54.168h135.819v27.089H27.153z"/><path fill="#fa500f" d="M27.153 81.259h27.169v27.09H27.153zM81.492 81.259h27.169v27.09H81.492zM135.815 81.259h27.169v27.09h-27.169z"/><path fill="#e10500" d="M-.001 108.339h81.489v27.09H-.001zM108.661 108.339h81.498v27.09h-81.498z"/></g></g></g></g></svg>
|
||||
|
After Width: | Height: | Size: 953 B |
2
chat/public/assets/nvidia.svg
Normal file
@@ -0,0 +1,2 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||
<svg width="800px" height="800px" viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg"><title>file_type_cuda</title><path d="M12.447,12.265V10.591c.163-.012.327-.02.494-.026,4.577-.143,7.581,3.934,7.581,3.934S17.278,19,13.8,19a4.2,4.2,0,0,1-1.353-.217V13.712c1.782.215,2.14,1,3.212,2.788l2.383-2.009A6.312,6.312,0,0,0,13.37,12.21a8.606,8.606,0,0,0-.923.055m0-5.529v2.5c.164-.013.329-.024.494-.03,6.366-.214,10.513,5.221,10.513,5.221s-4.764,5.792-9.726,5.792a7.4,7.4,0,0,1-1.281-.112v1.545a8.528,8.528,0,0,0,1.067.069c4.618,0,7.958-2.358,11.192-5.15.535.43,2.731,1.474,3.182,1.932-3.075,2.574-10.241,4.649-14.3,4.649-.392,0-.769-.024-1.138-.06v2.172H30V6.736Zm0,12.051v1.32c-4.271-.762-5.457-5.2-5.457-5.2a9.234,9.234,0,0,1,5.457-2.64v1.447h-.006a4.1,4.1,0,0,0-3.184,1.456s.782,2.811,3.19,3.62M4.861,14.713a10.576,10.576,0,0,1,7.586-4.122V9.236C6.848,9.685,2,14.427,2,14.427s2.746,7.939,10.447,8.665v-1.44C6.8,20.941,4.861,14.713,4.861,14.713Z" style="fill:#80bc00"/></svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
2
chat/public/assets/openai.svg
Normal file
@@ -0,0 +1,2 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||
<svg fill="#000000" width="800px" height="800px" viewBox="0 0 24 24" role="img" xmlns="http://www.w3.org/2000/svg"><title>OpenAI icon</title><path d="M22.2819 9.8211a5.9847 5.9847 0 0 0-.5157-4.9108 6.0462 6.0462 0 0 0-6.5098-2.9A6.0651 6.0651 0 0 0 4.9807 4.1818a5.9847 5.9847 0 0 0-3.9977 2.9 6.0462 6.0462 0 0 0 .7427 7.0966 5.98 5.98 0 0 0 .511 4.9107 6.051 6.051 0 0 0 6.5146 2.9001A5.9847 5.9847 0 0 0 13.2599 24a6.0557 6.0557 0 0 0 5.7718-4.2058 5.9894 5.9894 0 0 0 3.9977-2.9001 6.0557 6.0557 0 0 0-.7475-7.0729zm-9.022 12.6081a4.4755 4.4755 0 0 1-2.8764-1.0408l.1419-.0804 4.7783-2.7582a.7948.7948 0 0 0 .3927-.6813v-6.7369l2.02 1.1686a.071.071 0 0 1 .038.052v5.5826a4.504 4.504 0 0 1-4.4945 4.4944zm-9.6607-4.1254a4.4708 4.4708 0 0 1-.5346-3.0137l.142.0852 4.783 2.7582a.7712.7712 0 0 0 .7806 0l5.8428-3.3685v2.3324a.0804.0804 0 0 1-.0332.0615L9.74 19.9502a4.4992 4.4992 0 0 1-6.1408-1.6464zM2.3408 7.8956a4.485 4.485 0 0 1 2.3655-1.9728V11.6a.7664.7664 0 0 0 .3879.6765l5.8144 3.3543-2.0201 1.1685a.0757.0757 0 0 1-.071 0l-4.8303-2.7865A4.504 4.504 0 0 1 2.3408 7.872zm16.5963 3.8558L13.1038 8.364 15.1192 7.2a.0757.0757 0 0 1 .071 0l4.8303 2.7913a4.4944 4.4944 0 0 1-.6765 8.1042v-5.6772a.79.79 0 0 0-.407-.667zm2.0107-3.0231l-.142-.0852-4.7735-2.7818a.7759.7759 0 0 0-.7854 0L9.409 9.2297V6.8974a.0662.0662 0 0 1 .0284-.0615l4.8303-2.7866a4.4992 4.4992 0 0 1 6.6802 4.66zM8.3065 12.863l-2.02-1.1638a.0804.0804 0 0 1-.038-.0567V6.0742a4.4992 4.4992 0 0 1 7.3757-3.4537l-.142.0805L8.704 5.459a.7948.7948 0 0 0-.3927.6813zm1.0976-2.3654l2.602-1.4998 2.6069 1.4998v2.9994l-2.5974 1.4997-2.6067-1.4997Z"/></svg>
|
||||
|
After Width: | Height: | Size: 1.7 KiB |
1
chat/public/assets/qwen.svg
Normal file
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?><svg id="uuid-ba2452e9-43b6-4d69-b5f2-1d1a79068539" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 178.77 178.6"><path d="M117.53,70.8c.4,0,.5.2.3.5l-6.8,11.9-21.2,37.3c0,.2-.2.2-.4.2s-.3,0-.4-.2l-28.1-49c-.2-.3,0-.4.2-.4h1.8l54.7-.2h0l-.1-.1ZM66.43,5.6c-.2,0-.3,0-.4.2l-23.3,40.8c-.2.4-.6.6-1.1.6h-23.3c-.5,0-.6.2-.3.6l47.2,82.6c.2.3.1.5-.3.5h-22.7c-.7,0-1.3.4-1.6,1.1l-10.7,18.8c-.4.6-.2,1,.6,1h46.5c.4,0,.7.2.8.6l11.4,20c.4.7.7.7,1.1,0l40.7-71.2,6.4-11.2c0-.2.2-.2.4-.2s.3,0,.4.2l11.6,20.6c.2.3.5.5.9.5l22.5-.2c.1,0,.2,0,.3-.2v-.3l-23.6-41.4c-.2-.3-.2-.6,0-.9l2.4-4.1,9.1-16.1c.2-.3,0-.5-.3-.5h-94.3c-.5,0-.6-.2-.3-.6l11.7-20.4c.2-.3.2-.6,0-.9l-11.1-19.5c0-.2-.2-.3-.4-.3h0l-.3-.1ZM94.33,2.4c3.2,5.6,6.4,11.2,9.5,16.9.3.5.7.7,1.3.7h45.1c1.4,0,2.6.9,3.6,2.7l11.8,20.9c1.5,2.7,2,3.9.2,6.8-2.1,3.5-4.2,7-6.2,10.6l-3,5.4c-.9,1.6-1.8,2.3-.3,4.2l21.6,37.7c1.4,2.4.9,4-.3,6.3-3.6,6.4-7.2,12.7-10.9,19-1.3,2.2-2.9,3-5.5,3-6.3-.1-12.6,0-18.9.1-.3,0-.5.2-.7.4-7.3,12.9-14.6,25.7-22,38.5-1.4,2.4-3.1,3-5.9,3h-24.5c-1.7,0-2.9-.7-3.8-2.2l-10.9-18.9c-.1-.3-.4-.4-.7-.4h-41.6c-2.3.2-4.5,0-6.5-.7l-13-22.5c-.8-1.5-.8-2.9,0-4.4l9.8-17.2c.3-.5.3-1.1,0-1.6-5.1-8.9-10.2-17.7-15.2-26.6l-6.4-11.3c-1.3-2.5-1.4-4,.8-7.8,3.8-6.6,7.5-13.2,11.3-19.8,1.1-1.9,2.5-2.7,4.7-2.7h21.1c.4,0,.7-.2.9-.5L62.53,2.2c.8-1.3,1.9-2,3.4-2h12.9l8.3-.2c2.8,0,5.9.3,7.3,2.8l-.1-.4Z" style="fill:#615ced; fill-rule:evenodd;"/></svg>
|
||||
|
After Width: | Height: | Size: 1.4 KiB |
1
chat/public/assets/xai.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg fill="currentColor" fill-rule="evenodd" height="1em" style="flex:none;line-height:1" viewBox="0 0 24 24" width="1em" xmlns="http://www.w3.org/2000/svg"><title>Grok</title><path d="M6.469 8.776L16.512 23h-4.464L2.005 8.776H6.47zm-.004 7.9l2.233 3.164L6.467 23H2l4.465-6.324zM22 2.582V23h-3.659V7.764L22 2.582zM22 1l-9.952 14.095-2.233-3.163L17.533 1H22z"></path></svg>
|
||||
|
After Width: | Height: | Size: 372 B |
2650
chat/public/builder.html
Normal file
4171
chat/public/builder.js
Normal file
487
chat/public/contact.html
Normal file
@@ -0,0 +1,487 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Contact Us - Plugin Compass</title>
|
||||
<link rel="icon" type="image/png" href="/assets/Plugin.png">
|
||||
<link rel="stylesheet" href="styles.css">
|
||||
<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=Inter:wght@400;500;600&family=Space+Grotesk:wght@500;700&display=swap"
|
||||
rel="stylesheet">
|
||||
<style>
|
||||
body {
|
||||
background-color: #f6f6f7;
|
||||
color: #202223;
|
||||
font-family: 'Inter', sans-serif;
|
||||
margin: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.header {
|
||||
background: white;
|
||||
padding: 1rem 2rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
border-bottom: 1px solid #e1e3e5;
|
||||
}
|
||||
|
||||
.logo {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
text-decoration: none;
|
||||
color: #008060;
|
||||
font-family: 'Space Grotesk', sans-serif;
|
||||
font-weight: 700;
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
|
||||
.logo img {
|
||||
height: 32px;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.container {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 2rem;
|
||||
}
|
||||
|
||||
.contact-card {
|
||||
background: white;
|
||||
padding: 3rem;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
|
||||
max-width: 500px;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-family: 'Space Grotesk', sans-serif;
|
||||
font-size: 2rem;
|
||||
margin-bottom: 1.5rem;
|
||||
color: #004c3f;
|
||||
}
|
||||
|
||||
p {
|
||||
line-height: 1.6;
|
||||
color: #6d7175;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.email-link {
|
||||
display: inline-block;
|
||||
background: linear-gradient(135deg, #008060 0%, #004c3f 100%);
|
||||
color: white;
|
||||
text-decoration: none;
|
||||
padding: 1rem 2rem;
|
||||
border-radius: 8px;
|
||||
font-weight: 600;
|
||||
transition: opacity 0.2s;
|
||||
}
|
||||
|
||||
.email-link:hover {
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.footer {
|
||||
background: white;
|
||||
padding: 2rem;
|
||||
text-align: center;
|
||||
border-top: 1px solid #e1e3e5;
|
||||
color: #6d7175;
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
.footer a {
|
||||
color: #008060;
|
||||
text-decoration: none;
|
||||
margin: 0 0.5rem;
|
||||
}
|
||||
|
||||
.footer-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
|
||||
gap: 2rem;
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.footer-brand {
|
||||
grid-column: span 2;
|
||||
max-width: 280px;
|
||||
}
|
||||
|
||||
.footer-brand .logo-text {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
margin-bottom: 1rem;
|
||||
font-weight: 700;
|
||||
font-size: 1.25rem;
|
||||
color: #1a1a1a;
|
||||
}
|
||||
|
||||
.footer-brand .logo-text img {
|
||||
height: 32px;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.footer-brand p {
|
||||
color: #6b7280;
|
||||
font-size: 0.875rem;
|
||||
line-height: 1.6;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.footer h4 {
|
||||
font-weight: 600;
|
||||
color: #1a1a1a;
|
||||
margin-bottom: 1rem;
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
.footer ul {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.footer li {
|
||||
margin-bottom: 0.75rem;
|
||||
}
|
||||
|
||||
.footer li a {
|
||||
color: #6b7280;
|
||||
font-size: 0.875rem;
|
||||
transition: color 0.2s;
|
||||
}
|
||||
|
||||
.footer li a:hover {
|
||||
color: #008060;
|
||||
}
|
||||
|
||||
.footer-newsletter {
|
||||
max-width: 280px;
|
||||
}
|
||||
|
||||
.footer-newsletter p {
|
||||
color: #6b7280;
|
||||
font-size: 0.875rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.footer-newsletter form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.footer-newsletter input[type="email"] {
|
||||
padding: 0.75rem 1rem;
|
||||
border: 1px solid #e5e7eb;
|
||||
border-radius: 8px;
|
||||
font-size: 0.875rem;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.footer-newsletter button {
|
||||
background: #008060;
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 0.75rem 1rem;
|
||||
border-radius: 8px;
|
||||
font-weight: 600;
|
||||
font-size: 0.875rem;
|
||||
cursor: pointer;
|
||||
transition: background 0.2s;
|
||||
}
|
||||
|
||||
.footer-newsletter button:hover {
|
||||
background: #006B3D;
|
||||
}
|
||||
|
||||
.footer-bottom {
|
||||
border-top: 1px solid #e5e7eb;
|
||||
margin-top: 2rem;
|
||||
padding-top: 1.5rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.footer-bottom p {
|
||||
color: #9ca3af;
|
||||
font-size: 0.75rem;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.contact-form {
|
||||
text-align: left;
|
||||
margin-top: 1.5rem;
|
||||
}
|
||||
|
||||
.contact-form .form-group {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.contact-form label {
|
||||
display: block;
|
||||
font-weight: 500;
|
||||
margin-bottom: 0.5rem;
|
||||
color: #374151;
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
.contact-form input,
|
||||
.contact-form textarea {
|
||||
width: 100%;
|
||||
padding: 0.75rem 1rem;
|
||||
border: 1px solid #e5e7eb;
|
||||
border-radius: 8px;
|
||||
font-size: 0.875rem;
|
||||
font-family: inherit;
|
||||
transition: border-color 0.2s, box-shadow 0.2s;
|
||||
}
|
||||
|
||||
.contact-form input:focus,
|
||||
.contact-form textarea:focus {
|
||||
outline: none;
|
||||
border-color: #008060;
|
||||
box-shadow: 0 0 0 3px rgba(0, 128, 96, 0.1);
|
||||
}
|
||||
|
||||
.contact-form textarea {
|
||||
min-height: 120px;
|
||||
resize: vertical;
|
||||
}
|
||||
|
||||
.contact-form button {
|
||||
background: linear-gradient(135deg, #008060 0%, #004c3f 100%);
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 1rem 2rem;
|
||||
border-radius: 8px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: opacity 0.2s;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.contact-form button:hover {
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.contact-form button:disabled {
|
||||
opacity: 0.6;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.form-message {
|
||||
padding: 0.75rem 1rem;
|
||||
border-radius: 8px;
|
||||
margin-top: 1rem;
|
||||
font-size: 0.875rem;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.form-message.success {
|
||||
background: #d1fae5;
|
||||
color: #065f46;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.form-message.error {
|
||||
background: #fee2e2;
|
||||
color: #991b1b;
|
||||
display: block;
|
||||
}
|
||||
</style>
|
||||
|
||||
<!-- PostHog Analytics -->
|
||||
<script src="/posthog.js"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<header class="header">
|
||||
<a href="/" class="logo">
|
||||
<img src="/assets/Plugin.png" alt="Plugin Compass Logo">
|
||||
<span>Plugin Compass</span>
|
||||
</a>
|
||||
</header>
|
||||
|
||||
<main class="container">
|
||||
<div class="contact-card">
|
||||
<h1>Get in Touch</h1>
|
||||
<p>Have questions or need support? We're here to help you build the perfect WordPress Plugin.</p>
|
||||
<p>Please fill out the form below and we'll get back to you as soon as possible.</p>
|
||||
<form id="contact-form" class="contact-form">
|
||||
<div class="form-group">
|
||||
<label for="contact-name">Your Name</label>
|
||||
<input type="text" id="contact-name" name="name" placeholder="John Doe" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="contact-email">Email Address</label>
|
||||
<input type="email" id="contact-email" name="email" placeholder="john@example.com" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="contact-subject">Subject</label>
|
||||
<input type="text" id="contact-subject" name="subject" placeholder="How can we help?" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="contact-message">Message</label>
|
||||
<textarea id="contact-message" name="message" placeholder="Tell us more about your project..." required></textarea>
|
||||
</div>
|
||||
<button type="submit" id="contact-submit">Send Message</button>
|
||||
<div id="contact-message-display" class="form-message"></div>
|
||||
</form>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<footer class="footer">
|
||||
<div class="footer-grid">
|
||||
<div class="footer-brand">
|
||||
<div class="logo-text">
|
||||
<img src="/assets/Plugin.png" alt="Plugin Compass">
|
||||
<span>Plugin Compass</span>
|
||||
</div>
|
||||
<p>The smart way for WordPress site owners to replace expensive plugin subscriptions with custom solutions. Save thousands monthly.</p>
|
||||
</div>
|
||||
<div>
|
||||
<h4>Product</h4>
|
||||
<ul>
|
||||
<li><a href="/features">Features</a></li>
|
||||
<li><a href="/pricing">Pricing</a></li>
|
||||
<li><a href="#">Templates</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div>
|
||||
<h4>Resources</h4>
|
||||
<ul>
|
||||
<li><a href="/docs">Documentation</a></li>
|
||||
<li><a href="/faq">FAQ</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div>
|
||||
<h4>Legal</h4>
|
||||
<ul>
|
||||
<li><a href="/privacy.html">Privacy Policy</a></li>
|
||||
<li><a href="/terms">Terms of Service</a></li>
|
||||
<li><a href="/contact">Contact Us</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="footer-newsletter">
|
||||
<h4>Stay Updated</h4>
|
||||
<p>Get the latest updates and WordPress tips.</p>
|
||||
<form id="footer-signup-form">
|
||||
<input type="email" name="email" placeholder="Your email" required>
|
||||
<button type="submit">Subscribe</button>
|
||||
</form>
|
||||
<div id="signup-message" class="form-message"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="footer-bottom">
|
||||
<p>© 2026 Plugin Compass. All rights reserved.</p>
|
||||
</div>
|
||||
</footer>
|
||||
<script>
|
||||
const contactForm = document.getElementById('contact-form');
|
||||
const contactSubmit = document.getElementById('contact-submit');
|
||||
const contactMessage = document.getElementById('contact-message-display');
|
||||
|
||||
if (contactForm) {
|
||||
contactForm.addEventListener('submit', async (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
contactSubmit.disabled = true;
|
||||
contactSubmit.textContent = 'Sending...';
|
||||
|
||||
const formData = {
|
||||
name: document.getElementById('contact-name').value,
|
||||
email: document.getElementById('contact-email').value,
|
||||
subject: document.getElementById('contact-subject').value,
|
||||
message: document.getElementById('contact-message').value
|
||||
};
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/contact', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(formData)
|
||||
});
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
if (response.ok) {
|
||||
contactMessage.textContent = 'Thank you for your message! We\'ll get back to you soon.';
|
||||
contactMessage.className = 'form-message success';
|
||||
contactForm.reset();
|
||||
} else {
|
||||
throw new Error(result.error || 'Failed to send message');
|
||||
}
|
||||
} catch (error) {
|
||||
contactMessage.textContent = error.message || 'Failed to send message. Please try again.';
|
||||
contactMessage.className = 'form-message error';
|
||||
} finally {
|
||||
contactSubmit.disabled = false;
|
||||
contactSubmit.textContent = 'Send Message';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const signupForm = document.getElementById('footer-signup-form');
|
||||
const signupMessage = document.getElementById('signup-message');
|
||||
|
||||
if (signupForm) {
|
||||
signupForm.addEventListener('submit', async (e) => {
|
||||
e.preventDefault();
|
||||
const email = signupForm.querySelector('input[name="email"]').value;
|
||||
const button = signupForm.querySelector('button');
|
||||
|
||||
button.disabled = true;
|
||||
button.textContent = 'Subscribing...';
|
||||
|
||||
try {
|
||||
const response = await fetch('https://emailmarketing.modelrailway3d.co.uk/api/webhooks/incoming/wh_0Z49zi_DGj4-lKJMOPO8-g', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
email: email,
|
||||
source: 'plugin_compass_footer',
|
||||
timestamp: new Date().toISOString()
|
||||
})
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
signupMessage.textContent = 'Successfully subscribed!';
|
||||
signupMessage.className = 'form-message success';
|
||||
signupForm.reset();
|
||||
} else {
|
||||
throw new Error('Failed to subscribe');
|
||||
}
|
||||
} catch (error) {
|
||||
signupMessage.textContent = 'Failed to subscribe. Please try again.';
|
||||
signupMessage.className = 'form-message error';
|
||||
} finally {
|
||||
button.disabled = false;
|
||||
button.textContent = 'Subscribe';
|
||||
}
|
||||
});
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
474
chat/public/credits.html
Normal file
@@ -0,0 +1,474 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" class="scroll-smooth">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="description"
|
||||
content="Understanding AI credits, tokens, and top-ups at Plugin Compass. Learn how our credit system works.">
|
||||
<meta name="keywords"
|
||||
content="AI credits, tokens, token usage, credit top-up, Plugin Compass credits system">
|
||||
<meta name="robots" content="index, follow">
|
||||
<meta property="og:title" content="AI Credits System - Plugin Compass">
|
||||
<meta property="og:description"
|
||||
content="Learn how AI credits, tokens, and top-ups work at Plugin Compass.">
|
||||
<meta property="og:type" content="website">
|
||||
<meta property="og:url" content="">
|
||||
<meta property="og:site_name" content="Plugin Compass">
|
||||
<meta name="twitter:card" content="summary_large_image">
|
||||
<meta name="twitter:title" content="AI Credits System - Plugin Compass">
|
||||
<meta name="twitter:description"
|
||||
content="Learn how AI credits, tokens, and top-ups work at Plugin Compass.">
|
||||
<link rel="canonical" href="">
|
||||
<title>AI Credits System | Plugin Compass</title>
|
||||
<link rel="icon" type="image/png" href="/assets/Plugin.png">
|
||||
<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">
|
||||
<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=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
|
||||
|
||||
<script>
|
||||
tailwind.config = {
|
||||
theme: {
|
||||
extend: {
|
||||
fontFamily: {
|
||||
sans: ['Inter', 'sans-serif'],
|
||||
},
|
||||
colors: {
|
||||
brand: {
|
||||
50: '#eef2ff',
|
||||
100: '#e0e7ff',
|
||||
200: '#c7d2fe',
|
||||
300: '#a5b4fc',
|
||||
400: '#818cf8',
|
||||
500: '#6366f1',
|
||||
600: '#4f46e5',
|
||||
700: '#4338ca',
|
||||
800: '#3730a3',
|
||||
900: '#312e81',
|
||||
950: '#1e1b4b',
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style>
|
||||
::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
background: #fdf6ed;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: #d1d5db;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background: #9ca3af;
|
||||
}
|
||||
|
||||
.glass-nav {
|
||||
background: rgba(251, 246, 239, 0.9);
|
||||
backdrop-filter: blur(12px);
|
||||
-webkit-backdrop-filter: blur(12px);
|
||||
border-bottom: 1px solid rgba(0, 66, 37, 0.1);
|
||||
}
|
||||
</style>
|
||||
|
||||
<!-- PostHog Analytics -->
|
||||
<script src="/posthog.js"></script>
|
||||
</head>
|
||||
|
||||
<body class="bg-amber-50 text-gray-900 font-sans antialiased overflow-x-hidden">
|
||||
|
||||
<nav class="fixed w-full z-50 glass-nav transition-all duration-300" id="navbar">
|
||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div class="flex justify-between items-center h-20">
|
||||
<a href="/" class="flex-shrink-0 flex items-center gap-2 cursor-pointer">
|
||||
<img src="/assets/Plugin.png" alt="Plugin Compass" class="w-8 h-8">
|
||||
<span class="font-bold text-xl tracking-tight text-gray-800">Plugin<span
|
||||
class="text-green-700">Compass</span></span>
|
||||
</a>
|
||||
|
||||
<div class="hidden md:flex items-center space-x-8">
|
||||
<a href="/features"
|
||||
class="text-gray-700 hover:text-gray-900 transition-colors text-sm font-medium">Features</a>
|
||||
<a href="/pricing"
|
||||
class="text-gray-700 hover:text-gray-900 transition-colors text-sm font-medium">Pricing</a>
|
||||
<a href="/docs"
|
||||
class="text-gray-700 hover:text-gray-900 transition-colors text-sm font-medium">Docs</a>
|
||||
<a href="/faq"
|
||||
class="text-gray-700 hover:text-gray-900 transition-colors text-sm font-medium">FAQ</a>
|
||||
</div>
|
||||
|
||||
<div class="hidden md:flex items-center gap-4">
|
||||
<a href="/login"
|
||||
class="text-gray-700 hover:text-gray-900 font-medium text-sm transition-colors">Sign In</a>
|
||||
<a href="/signup"
|
||||
class="bg-green-700 hover:bg-green-600 text-white px-5 py-2.5 rounded-full font-medium text-sm transition-all shadow-lg shadow-green-700/20 hover:shadow-green-700/40 transform hover:-translate-y-0.5">
|
||||
Get Started
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="md:hidden flex items-center">
|
||||
<button id="mobile-menu-btn" class="text-gray-700 hover:text-gray-900 focus:outline-none">
|
||||
<i class="fa-solid fa-bars text-xl"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="mobile-menu" class="hidden md:hidden bg-amber-50 border-b border-amber-200">
|
||||
<div class="px-4 pt-2 pb-6 space-y-1">
|
||||
<a href="/features"
|
||||
class="block px-3 py-3 text-base font-medium text-gray-700 hover:text-gray-900 hover:bg-amber-100 rounded-md">Features</a>
|
||||
<a href="/pricing"
|
||||
class="block px-3 py-3 text-base font-medium text-gray-700 hover:text-gray-900 hover:bg-amber-100 rounded-md">Pricing</a>
|
||||
<a href="/docs"
|
||||
class="block px-3 py-3 text-base font-medium text-gray-700 hover:text-gray-900 hover:bg-amber-100 rounded-md">Docs</a>
|
||||
<a href="/faq"
|
||||
class="block px-3 py-3 text-base font-medium text-gray-700 hover:text-gray-900 hover:bg-amber-100 rounded-md">FAQ</a>
|
||||
<div class="pt-4 flex flex-col gap-3">
|
||||
<a href="/login" class="w-full text-center py-2 text-gray-700 font-medium">Sign In</a>
|
||||
<a href="/signup" class="w-full bg-green-700 text-white text-center py-3 rounded-lg font-medium">Get
|
||||
Started</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<section class="py-24 bg-amber-50">
|
||||
<div class="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div class="text-center mb-16">
|
||||
<h1 class="text-4xl md:text-5xl font-bold text-gray-900 mb-4">Understanding AI Credits</h1>
|
||||
<p class="text-xl text-gray-700">Everything you need to know about how credits, tokens, and top-ups work.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="bg-white rounded-3xl p-8 border border-gray-100 shadow-xl shadow-gray-200/50 mb-8">
|
||||
<h2 class="text-2xl font-bold text-gray-900 mb-6">
|
||||
<i class="fa-solid fa-coins text-green-600 mr-3"></i>What Are AI Credits?
|
||||
</h2>
|
||||
<p class="text-gray-700 leading-relaxed mb-4">
|
||||
AI credits are the usage unit for Plugin Compass. Every AI-powered action—like generating code,
|
||||
analyzing files, or building plugins—consumes credits from your monthly allowance. This system
|
||||
ensures fair usage across all plans while providing flexibility for different project sizes.
|
||||
</p>
|
||||
<p class="text-gray-700 leading-relaxed">
|
||||
Your plan includes a set number of credits each month that reset on your billing date. Credits do not
|
||||
roll over to the next month, so it's important to use them within your billing period.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="bg-white rounded-3xl p-8 border border-gray-100 shadow-xl shadow-gray-200/50 mb-8">
|
||||
<h2 class="text-2xl font-bold text-gray-900 mb-6">
|
||||
<i class="fa-solid fa-microchip text-green-600 mr-3"></i>How Tokens Relate to Credits
|
||||
</h2>
|
||||
<p class="text-gray-700 leading-relaxed mb-4">
|
||||
Tokens are the basic units of text that AI models process. Think of them as words or word pieces—the
|
||||
average English word is roughly 4 characters or about 1.3 tokens. However, not all models use tokens
|
||||
at the same rate.
|
||||
</p>
|
||||
<div class="bg-green-50 rounded-xl p-6 border border-green-200 mb-4">
|
||||
<h3 class="font-semibold text-gray-900 mb-3">Model Multipliers</h3>
|
||||
<p class="text-gray-700 text-sm mb-4">Different AI models have different capabilities and costs, so
|
||||
they burn credits at different rates:</p>
|
||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||
<div class="bg-white rounded-lg p-4 border border-green-100">
|
||||
<div class="flex items-center gap-2 mb-2">
|
||||
<span class="bg-green-100 text-green-700 text-xs font-bold px-2 py-1 rounded-full">1x</span>
|
||||
<span class="font-semibold text-gray-900">Standard Models</span>
|
||||
</div>
|
||||
<p class="text-gray-600 text-sm">Efficient models for everyday tasks. 1 token = 1 credit.
|
||||
</p>
|
||||
</div>
|
||||
<div class="bg-white rounded-lg p-4 border border-green-100">
|
||||
<div class="flex items-center gap-2 mb-2">
|
||||
<span class="bg-yellow-100 text-yellow-700 text-xs font-bold px-2 py-1 rounded-full">2x</span>
|
||||
<span class="font-semibold text-gray-900">Advanced Models</span>
|
||||
</div>
|
||||
<p class="text-gray-600 text-sm">More capable models for complex tasks. 1 token = 2 credits.
|
||||
</p>
|
||||
</div>
|
||||
<div class="bg-white rounded-lg p-4 border border-green-100">
|
||||
<div class="flex items-center gap-2 mb-2">
|
||||
<span class="bg-red-100 text-red-700 text-xs font-bold px-2 py-1 rounded-full">3x</span>
|
||||
<span class="font-semibold text-gray-900">Premium Models</span>
|
||||
</div>
|
||||
<p class="text-gray-600 text-sm">Most powerful models for specialized tasks. 1 token = 3
|
||||
credits.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<p class="text-gray-700 leading-relaxed">
|
||||
<strong>Example:</strong> If you use a 2x model and process 2,500 tokens, you'll consume 5,000 credits
|
||||
from your monthly allowance. This multiplier system allows us to offer access to cutting-edge AI
|
||||
models while keeping pricing fair across all plan levels.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="bg-white rounded-3xl p-8 border border-gray-100 shadow-xl shadow-gray-200/50 mb-8">
|
||||
<h2 class="text-2xl font-bold text-gray-900 mb-6">
|
||||
<i class="fa-solid fa-calendar text-green-600 mr-3"></i>Monthly Credit Limits by Plan
|
||||
</h2>
|
||||
<p class="text-gray-700 leading-relaxed mb-6">Each plan includes a monthly allocation of AI credits that
|
||||
resets at the start of each billing cycle:</p>
|
||||
<div class="space-y-4">
|
||||
<div class="flex items-center justify-between p-4 bg-gray-50 rounded-xl border border-gray-100">
|
||||
<div class="flex items-center gap-3">
|
||||
<span class="w-24 text-sm font-medium text-gray-500">Hobby</span>
|
||||
<span class="font-semibold text-gray-900">50,000 credits</span>
|
||||
<span class="text-gray-500 text-sm">/ month</span>
|
||||
</div>
|
||||
<span class="text-green-600 text-sm font-medium">Included</span>
|
||||
</div>
|
||||
<div class="flex items-center justify-between p-4 bg-gray-50 rounded-xl border border-gray-100">
|
||||
<div class="flex items-center gap-3">
|
||||
<span class="w-24 text-sm font-medium text-gray-500">Starter</span>
|
||||
<span class="font-semibold text-gray-900">100,000 credits</span>
|
||||
<span class="text-gray-500 text-sm">/ month</span>
|
||||
</div>
|
||||
<span class="text-green-600 text-sm font-medium">Included</span>
|
||||
</div>
|
||||
<div class="flex items-center justify-between p-4 bg-gray-50 rounded-xl border border-gray-100">
|
||||
<div class="flex items-center gap-3">
|
||||
<span class="w-24 text-sm font-medium text-gray-500">Professional</span>
|
||||
<span class="font-semibold text-gray-900">10,000,000 credits</span>
|
||||
<span class="text-gray-500 text-sm">/ month</span>
|
||||
</div>
|
||||
<span class="text-green-600 text-sm font-medium">Included</span>
|
||||
</div>
|
||||
<div class="flex items-center justify-between p-4 bg-gray-50 rounded-xl border border-gray-100">
|
||||
<div class="flex items-center gap-3">
|
||||
<span class="w-24 text-sm font-medium text-gray-500">Enterprise</span>
|
||||
<span class="font-semibold text-gray-900">50,000,000 credits</span>
|
||||
<span class="text-gray-500 text-sm">/ month</span>
|
||||
</div>
|
||||
<span class="text-green-600 text-sm font-medium">Included</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bg-white rounded-3xl p-8 border border-gray-100 shadow-xl shadow-gray-200/50 mb-8">
|
||||
<h2 class="text-2xl font-bold text-gray-900 mb-6">
|
||||
<i class="fa-solid fa-bolt text-green-600 mr-3"></i>Credit Top-Ups
|
||||
</h2>
|
||||
<p class="text-gray-700 leading-relaxed mb-4">
|
||||
Need more credits than your monthly allowance provides? You can purchase one-off credit top-ups at
|
||||
any time. These are instant and never expire, giving you flexibility for large projects or
|
||||
unexpected needs.
|
||||
</p>
|
||||
<div class="bg-green-50 rounded-xl p-6 border border-green-200 mb-4">
|
||||
<h3 class="font-semibold text-gray-900 mb-3">Plan Discounts on Top-Ups</h3>
|
||||
<p class="text-gray-700 text-sm mb-4">Paid plan subscribers receive automatic discounts on credit
|
||||
top-ups:</p>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div class="bg-white rounded-lg p-4 border border-green-100">
|
||||
<div class="flex items-center gap-2 mb-2">
|
||||
<span class="font-semibold text-gray-900">Professional Plan</span>
|
||||
</div>
|
||||
<p class="text-gray-600 text-sm">2.5% discount on all top-up purchases.</p>
|
||||
</div>
|
||||
<div class="bg-white rounded-lg p-4 border border-green-100">
|
||||
<div class="flex items-center gap-2 mb-2">
|
||||
<span class="font-semibold text-gray-900">Enterprise Plan</span>
|
||||
</div>
|
||||
<p class="text-gray-600 text-sm">5% discount on all top-up purchases.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<p class="text-gray-700 leading-relaxed">
|
||||
Top-ups can be purchased from your account settings or by visiting the <a href="/topup"
|
||||
class="text-green-700 font-medium hover:underline">token top-ups page</a>. Payment is processed
|
||||
securely through Dodo Payments, and credits are added to your account immediately after successful
|
||||
payment.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="bg-white rounded-3xl p-8 border border-gray-100 shadow-xl shadow-gray-200/50 mb-8">
|
||||
<h2 class="text-2xl font-bold text-gray-900 mb-6">
|
||||
<i class="fa-solid fa-exclamation-circle text-green-600 mr-3"></i>What Happens When You Run Out?
|
||||
</h2>
|
||||
<p class="text-gray-700 leading-relaxed mb-4">
|
||||
When you approach or exceed your monthly credit limit, you'll receive notifications in the builder
|
||||
interface. You have several options:
|
||||
</p>
|
||||
<div class="space-y-4">
|
||||
<div class="flex items-start gap-4 p-4 bg-gray-50 rounded-xl">
|
||||
<div
|
||||
class="w-10 h-10 rounded-full bg-green-100 flex items-center justify-center flex-shrink-0 mt-0.5">
|
||||
<i class="fa-solid fa-arrow-up text-green-700"></i>
|
||||
</div>
|
||||
<div>
|
||||
<h4 class="font-semibold text-gray-900 mb-1">Upgrade Your Plan</h4>
|
||||
<p class="text-gray-700 text-sm">Choose a higher plan with more monthly credits. Changes
|
||||
take effect immediately.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-start gap-4 p-4 bg-gray-50 rounded-xl">
|
||||
<div
|
||||
class="w-10 h-10 rounded-full bg-green-100 flex items-center justify-center flex-shrink-0 mt-0.5">
|
||||
<i class="fa-solid fa-bolt text-green-700"></i>
|
||||
</div>
|
||||
<div>
|
||||
<h4 class="font-semibold text-gray-900 mb-1">Purchase a Top-Up</h4>
|
||||
<p class="text-gray-700 text-sm">Buy additional credits instantly. These never expire and
|
||||
are available immediately.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-start gap-4 p-4 bg-gray-50 rounded-xl">
|
||||
<div
|
||||
class="w-10 h-10 rounded-full bg-green-100 flex items-center justify-center flex-shrink-0 mt-0.5">
|
||||
<i class="fa-solid fa-clock text-green-700"></i>
|
||||
</div>
|
||||
<div>
|
||||
<h4 class="font-semibold text-gray-900 mb-1">Wait for Reset</h4>
|
||||
<p class="text-gray-700 text-sm">Your credits reset at the start of your next billing
|
||||
cycle. Check your account for your exact reset date.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bg-white rounded-3xl p-8 border border-gray-100 shadow-xl shadow-gray-200/50">
|
||||
<h2 class="text-2xl font-bold text-gray-900 mb-6">
|
||||
<i class="fa-solid fa-circle-question text-green-600 mr-3"></i>Quick Reference
|
||||
</h2>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<div>
|
||||
<h3 class="font-semibold text-gray-900 mb-3">Average Usage Examples</h3>
|
||||
<ul class="space-y-2 text-sm text-gray-700">
|
||||
<li class="flex items-center gap-2">
|
||||
<i class="fa-solid fa-check text-green-600"></i>
|
||||
Small plugin (simple features): ~5,000-15,000 credits
|
||||
</li>
|
||||
<li class="flex items-center gap-2">
|
||||
<i class="fa-solid fa-check text-green-600"></i>
|
||||
Medium plugin (moderate complexity): ~15,000-50,000 credits
|
||||
</li>
|
||||
<li class="flex items-center gap-2">
|
||||
<i class="fa-solid fa-check text-green-600"></i>
|
||||
Large plugin (complex features): ~50,000-200,000 credits
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="font-semibold text-gray-900 mb-3">Tips to Maximize Credits</h3>
|
||||
<ul class="space-y-2 text-sm text-gray-700">
|
||||
<li class="flex items-center gap-2">
|
||||
<i class="fa-solid fa-check text-green-600"></i>
|
||||
Use standard models for simple tasks
|
||||
</li>
|
||||
<li class="flex items-center gap-2">
|
||||
<i class="fa-solid fa-check text-green-600"></i>
|
||||
Review code before regeneration
|
||||
</li>
|
||||
<li class="flex items-center gap-2">
|
||||
<i class="fa-solid fa-check text-green-600"></i>
|
||||
Break large projects into smaller plugins
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<footer class="bg-white border-t border-green-200 pt-16 pb-8">
|
||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div class="grid grid-cols-2 md:grid-cols-4 gap-12 mb-16">
|
||||
<div class="col-span-2 md:col-span-1">
|
||||
<div class="flex items-center gap-2 mb-6">
|
||||
<img src="/assets/Plugin.png" alt="Plugin Compass" class="w-8 h-8">
|
||||
<span class="font-bold text-xl tracking-tight text-gray-800">Plugin<span
|
||||
class="text-green-700">Compass</span></span>
|
||||
</div>
|
||||
<p class="text-gray-600 text-sm leading-relaxed">
|
||||
The smart way for WordPress site owners to replace expensive plugin subscriptions with custom
|
||||
solutions. Save thousands monthly.
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<h4 class="font-bold text-gray-900 mb-6">Product</h4>
|
||||
<ul class="space-y-4 text-sm">
|
||||
<li><a href="/features" class="text-gray-600 hover:text-green-700">Features</a></li>
|
||||
<li><a href="/pricing" class="text-gray-600 hover:text-green-700">Pricing</a></li>
|
||||
<li><a href="/templates.html" class="text-gray-600 hover:text-green-700">Templates</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div>
|
||||
<h4 class="font-bold text-gray-900 mb-6">Resources</h4>
|
||||
<ul class="space-y-4 text-sm">
|
||||
<li><a href="/docs" class="text-gray-600 hover:text-green-700">Documentation</a></li>
|
||||
<li><a href="/faq" class="text-gray-600 hover:text-green-700">FAQ</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div>
|
||||
<h4 class="font-bold text-gray-900 mb-6">Legal</h4>
|
||||
<ul class="space-y-4 text-sm">
|
||||
<li><a href="/privacy.html" class="text-gray-600 hover:text-green-700">Privacy Policy</a></li>
|
||||
<li><a href="/terms" class="text-gray-600 hover:text-green-700">Terms of Service</a></li>
|
||||
<li><a href="/contact" class="text-gray-600 hover:text-green-700">Contact Us</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="border-t border-gray-100 pt-8 flex justify-center">
|
||||
<p class="text-gray-500 text-xs text-center">© 2026 Plugin Compass. All rights reserved.</p>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<script>
|
||||
const mobileMenuBtn = document.getElementById('mobile-menu-btn');
|
||||
const mobileMenu = document.getElementById('mobile-menu');
|
||||
|
||||
mobileMenuBtn.addEventListener('click', (e) => {
|
||||
e.stopPropagation();
|
||||
mobileMenu.classList.toggle('hidden');
|
||||
const isOpen = !mobileMenu.classList.contains('hidden');
|
||||
mobileMenuBtn.innerHTML = isOpen ?
|
||||
'<i class="fa-solid fa-times text-xl"></i>' :
|
||||
'<i class="fa-solid fa-bars text-xl"></i>';
|
||||
});
|
||||
|
||||
document.addEventListener('click', (e) => {
|
||||
if (mobileMenuBtn && mobileMenu) {
|
||||
if (!mobileMenuBtn.contains(e.target) && !mobileMenu.contains(e.target)) {
|
||||
mobileMenu.classList.add('hidden');
|
||||
mobileMenuBtn.innerHTML = '<i class="fa-solid fa-bars text-xl"></i>';
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Hide upgrade section for enterprise users
|
||||
async function checkPlanAndHideUpgrade() {
|
||||
try {
|
||||
const response = await fetch('/api/me');
|
||||
if (response.ok) {
|
||||
const data = await response.json();
|
||||
const plan = (data.account?.plan || '').toLowerCase();
|
||||
if (plan === 'enterprise') {
|
||||
const upgradeSection = document.querySelector('h4.font-semibold.mb-1');
|
||||
if (upgradeSection && upgradeSection.textContent === 'Upgrade Your Plan') {
|
||||
const parent = upgradeSection.closest('.bg-gray-50.rounded-xl');
|
||||
if (parent) {
|
||||
parent.style.display = 'none';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Failed to check plan:', err);
|
||||
}
|
||||
}
|
||||
|
||||
checkPlanAndHideUpgrade();
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
679
chat/public/docs.html
Normal file
@@ -0,0 +1,679 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" class="scroll-smooth">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="description"
|
||||
content="Plugin Compass documentation. Learn how to plan, build, export, and install AI-generated WordPress plugins using the Plugin Compass builder.">
|
||||
<title>Documentation - Plugin Compass</title>
|
||||
<link rel="icon" type="image/png" href="/assets/Plugin.png">
|
||||
<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">
|
||||
<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=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
|
||||
|
||||
<script>
|
||||
tailwind.config = {
|
||||
theme: {
|
||||
extend: {
|
||||
fontFamily: {
|
||||
sans: ['Inter', 'sans-serif'],
|
||||
},
|
||||
colors: {
|
||||
brand: {
|
||||
50: '#eef2ff',
|
||||
100: '#e0e7ff',
|
||||
200: '#c7d2fe',
|
||||
300: '#a5b4fc',
|
||||
400: '#818cf8',
|
||||
500: '#6366f1',
|
||||
600: '#4f46e5',
|
||||
700: '#4338ca',
|
||||
800: '#3730a3',
|
||||
900: '#312e81',
|
||||
950: '#1e1b4b',
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style>
|
||||
::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
background: #fdf6ed;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: #d1d5db;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background: #9ca3af;
|
||||
}
|
||||
|
||||
.glass-nav {
|
||||
background: rgba(251, 246, 239, 0.9);
|
||||
backdrop-filter: blur(12px);
|
||||
-webkit-backdrop-filter: blur(12px);
|
||||
border-bottom: 1px solid rgba(0, 66, 37, 0.1);
|
||||
}
|
||||
</style>
|
||||
|
||||
<!-- PostHog Analytics -->
|
||||
<script src="/posthog.js"></script>
|
||||
</head>
|
||||
|
||||
<body class="bg-white text-gray-900 font-sans antialiased overflow-x-hidden">
|
||||
|
||||
<!-- Navigation -->
|
||||
<nav class="fixed w-full z-50 glass-nav transition-all duration-300" id="navbar">
|
||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div class="flex justify-between items-center h-20">
|
||||
<!-- Logo -->
|
||||
<a href="/" class="flex-shrink-0 flex items-center gap-2 cursor-pointer">
|
||||
<img src="/assets/Plugin.png" alt="Plugin Compass" class="w-8 h-8 rounded-lg">
|
||||
<span class="font-bold text-xl tracking-tight text-gray-800">Plugin<span
|
||||
class="text-green-700">Compass</span></span>
|
||||
</a>
|
||||
|
||||
<!-- Desktop Menu -->
|
||||
<div class="hidden md:flex items-center space-x-8">
|
||||
<a href="/features"
|
||||
class="text-gray-700 hover:text-gray-900 transition-colors text-sm font-medium">Features</a>
|
||||
<a href="/pricing"
|
||||
class="text-gray-700 hover:text-gray-900 transition-colors text-sm font-medium">Pricing</a>
|
||||
<a href="/docs"
|
||||
class="text-gray-700 hover:text-gray-900 transition-colors text-sm font-medium">Docs</a>
|
||||
<a href="/faq"
|
||||
class="text-gray-700 hover:text-gray-900 transition-colors text-sm font-medium">FAQ</a>
|
||||
</div>
|
||||
|
||||
<!-- CTA Buttons -->
|
||||
<div class="hidden md:flex items-center gap-4">
|
||||
<a href="/login"
|
||||
class="text-gray-700 hover:text-gray-900 font-medium text-sm transition-colors">Sign In</a>
|
||||
<a href="/signup"
|
||||
class="bg-green-700 hover:bg-green-600 text-white px-5 py-2.5 rounded-full font-medium text-sm transition-all shadow-lg shadow-green-700/20 hover:shadow-green-700/40 transform hover:-translate-y-0.5">
|
||||
Get Started
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<!-- Mobile Menu Button -->
|
||||
<div class="md:hidden flex items-center">
|
||||
<button id="mobile-menu-btn" class="text-gray-700 hover:text-gray-900 focus:outline-none">
|
||||
<i class="fa-solid fa-bars text-xl"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Mobile Menu Panel -->
|
||||
<div id="mobile-menu" class="hidden md:hidden bg-white border-b border-slate-200">
|
||||
<div class="px-4 pt-2 pb-6 space-y-1">
|
||||
<a href="/features"
|
||||
class="block px-3 py-3 text-base font-medium text-gray-700 hover:text-gray-900 hover:bg-amber-100 rounded-md">Features</a>
|
||||
<a href="/pricing"
|
||||
class="block px-3 py-3 text-base font-medium text-gray-700 hover:text-gray-900 hover:bg-amber-100 rounded-md">Pricing</a>
|
||||
<a href="/docs"
|
||||
class="block px-3 py-3 text-base font-medium text-gray-700 hover:text-gray-900 hover:bg-amber-100 rounded-md">Docs</a>
|
||||
<a href="/faq"
|
||||
class="block px-3 py-3 text-base font-medium text-gray-700 hover:text-gray-900 hover:bg-amber-100 rounded-md">FAQ</a>
|
||||
<div class="pt-4 flex flex-col gap-3">
|
||||
<a href="/login" class="w-full text-center py-2 text-gray-700 font-medium">Sign In</a>
|
||||
<a href="/signup" class="w-full bg-green-700 text-white text-center py-3 rounded-lg font-medium">Get
|
||||
Started</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<main class="max-w-6xl mx-auto px-4 pt-24 lg:pt-32 pb-12 grid lg:grid-cols-[280px,1fr] gap-8">
|
||||
<aside class="bg-white border border-slate-200 rounded-xl p-4 h-fit shadow-sm lg:sticky lg:top-24">
|
||||
<h2 class="text-sm font-semibold text-slate-700 mb-3">Contents</h2>
|
||||
<nav class="space-y-2 text-sm flex lg:flex-col flex-wrap gap-x-4 gap-y-2 lg:gap-0 lg:space-y-2">
|
||||
<a class="block text-green-700 font-semibold" href="#overview">Overview</a>
|
||||
<a class="block hover:text-green-700" href="#quick-start">Quick start</a>
|
||||
<a class="block hover:text-green-700" href="#projects">Projects & sessions</a>
|
||||
<a class="block hover:text-green-700" href="#builder">Builder workflow</a>
|
||||
<a class="block hover:text-green-700" href="#prompting">Writing a great spec</a>
|
||||
<a class="block hover:text-green-700" href="#install">Export & install</a>
|
||||
<a class="block hover:text-green-700" href="#billing">Billing & account</a>
|
||||
<a class="block hover:text-green-700" href="#security">Security best practices</a>
|
||||
<a class="block hover:text-green-700" href="#troubleshooting">Troubleshooting</a>
|
||||
<a class="block hover:text-green-700" href="#support">Support</a>
|
||||
</nav>
|
||||
</aside>
|
||||
|
||||
<section class="space-y-6 lg:space-y-10 min-w-0">
|
||||
<article id="overview" class="bg-white border border-slate-200 rounded-xl p-4 sm:p-6 shadow-sm">
|
||||
<h1 class="text-2xl sm:text-3xl font-extrabold tracking-tight">Documentation</h1>
|
||||
<p class="text-slate-600 mt-3 leading-relaxed">
|
||||
Plugin Compass is an AI builder for creating custom WordPress plugins. Describe what you want,
|
||||
review a clear plan, then let the builder generate and iterate on production-ready code.
|
||||
</p>
|
||||
|
||||
<div class="grid md:grid-cols-2 gap-4 mt-6">
|
||||
<div class="bg-white border border-slate-200 rounded-lg p-4 mx-auto w-full">
|
||||
<h2 class="font-semibold text-gray-900 flex items-center gap-2">
|
||||
<i class="fa-solid fa-check"></i>
|
||||
Best for
|
||||
</h2>
|
||||
<ul class="list-disc list-inside space-y-2 mt-3 text-sm text-gray-700">
|
||||
<li>Replacing expensive plugin subscriptions with a plugin you own</li>
|
||||
<li>Building admin panels, dashboards, forms, and workflow automations</li>
|
||||
<li>Creating custom post types, taxonomies, shortcodes, and Gutenberg blocks</li>
|
||||
<li>Integrating with third-party APIs (CRMs, email providers, internal services)</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="bg-white border border-slate-200 rounded-lg p-4 mx-auto w-full">
|
||||
<h2 class="font-semibold text-gray-900 flex items-center gap-2">
|
||||
<i class="fa-solid fa-shield-halved"></i>
|
||||
Before you ship
|
||||
</h2>
|
||||
<ul class="list-disc list-inside space-y-2 mt-3 text-sm text-gray-700">
|
||||
<li>Test on a staging site and keep WordPress + plugins up to date</li>
|
||||
<li>Review permissions, sanitization, and nonces on admin actions</li>
|
||||
<li>Confirm your plugin doesn’t break theme templates or existing workflows</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<article id="quick-start" class="bg-white border border-slate-200 rounded-xl p-4 sm:p-6 shadow-sm">
|
||||
<h2 class="text-2xl font-bold">Quick start</h2>
|
||||
<p class="text-slate-600 mt-2">The fastest path from idea → plugin ZIP.</p>
|
||||
|
||||
<ol class="list-decimal list-inside space-y-2 mt-4 text-slate-700">
|
||||
<li>
|
||||
Create an account at <a href="/signup" class="text-green-700 font-semibold">/signup</a> (or sign
|
||||
in at
|
||||
<a href="/login" class="text-green-700 font-semibold">/login</a>).
|
||||
</li>
|
||||
<li>
|
||||
After verification you’ll be asked to pick a plan (if you haven’t already), then you’ll land on
|
||||
<a href="/apps" class="text-green-700 font-semibold">/apps</a>.
|
||||
</li>
|
||||
<li>
|
||||
Click <strong>Create New Plugin</strong> to open the builder.
|
||||
</li>
|
||||
<li>
|
||||
Start with a detailed request (use the template below).
|
||||
</li>
|
||||
<li>
|
||||
Review the plan, approve it, then let the builder generate code.
|
||||
</li>
|
||||
<li>
|
||||
Click <strong>Download ZIP</strong> to export your plugin and install it in WordPress.
|
||||
</li>
|
||||
</ol>
|
||||
|
||||
<div class="mt-6">
|
||||
<h3 class="font-semibold">Starter prompt template</h3>
|
||||
<p class="text-sm text-slate-600 mt-1">Copy/paste this into the builder and fill in the brackets.
|
||||
</p>
|
||||
<pre
|
||||
class="mt-3 overflow-x-auto rounded-lg bg-slate-950 text-slate-100 p-4 text-sm leading-relaxed"><code>Build me a WordPress plugin called: [Plugin Name]
|
||||
|
||||
Goal:
|
||||
- [What business problem it solves]
|
||||
|
||||
Users & permissions:
|
||||
- Admin can: [list]
|
||||
- Editor can: [list]
|
||||
- Logged-out users can: [list]
|
||||
|
||||
Admin UI:
|
||||
- Add a new menu item under: [Tools / Settings / custom top-level]
|
||||
- Screens needed:
|
||||
1) [Screen name] - [what it does]
|
||||
2) [Screen name] - [what it does]
|
||||
|
||||
Data model:
|
||||
- Store data as: [options / post meta / custom table]
|
||||
- Fields:
|
||||
- [field] (type) - validation rules
|
||||
|
||||
Workflows:
|
||||
- When [event] happens, do [action]
|
||||
- Send email/notification to [who] when [condition]
|
||||
|
||||
Acceptance criteria:
|
||||
- [bullet list of “done” conditions]
|
||||
</code></pre>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<article id="projects" class="bg-white border border-slate-200 rounded-xl p-4 sm:p-6 shadow-sm">
|
||||
<h2 class="text-2xl font-bold">Projects & sessions</h2>
|
||||
<p class="text-slate-600 mt-2">
|
||||
Each plugin you build lives in its own project (also called a <em>session</em>). Sessions keep your
|
||||
chat history,
|
||||
plan approvals, and generated files together.
|
||||
</p>
|
||||
|
||||
<div class="grid md:grid-cols-2 gap-4 mt-4">
|
||||
<div class="bg-white border border-slate-200 rounded-lg p-4 mx-auto w-full">
|
||||
<h3 class="font-semibold text-gray-900">Create & manage projects</h3>
|
||||
<ul class="list-disc list-inside space-y-2 mt-3 text-sm text-slate-700">
|
||||
<li>Go to <a href="/apps" class="text-green-700 font-semibold">/apps</a> to see all
|
||||
projects.</li>
|
||||
<li>Rename a project to match the plugin’s purpose (e.g. “Membership Portal”).</li>
|
||||
<li>Delete old experiments to keep your dashboard clean.</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="bg-white border border-slate-200 rounded-lg p-4 mx-auto w-full">
|
||||
<h3 class="font-semibold text-gray-900">Import an existing plugin (paid plans)</h3>
|
||||
<p class="text-sm text-gray-700 mt-2">
|
||||
If you already have a plugin ZIP, you can upload it from <strong>/apps</strong> and continue
|
||||
iterating in the builder.
|
||||
This is great for adding features, fixing bugs, or modernizing an older plugin.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<article id="builder" class="bg-white border border-slate-200 rounded-xl p-4 sm:p-6 shadow-sm">
|
||||
<h2 class="text-2xl font-bold">Builder workflow</h2>
|
||||
<p class="text-slate-600 mt-2">A predictable loop: plan → approve → build → iterate.</p>
|
||||
|
||||
<div class="grid md:grid-cols-2 gap-4 mt-4">
|
||||
<div class="bg-white border border-slate-200 rounded-lg p-4 mx-auto w-full">
|
||||
<h3 class="font-semibold text-gray-900">1) Plan</h3>
|
||||
<ul class="list-disc list-inside space-y-2 mt-3 text-sm text-gray-700">
|
||||
<li>Clarifies requirements and edge cases before code is generated</li>
|
||||
<li>Proposes screens, data storage, and a file layout</li>
|
||||
<li>Lists acceptance criteria so you can quickly verify “done”</li>
|
||||
</ul>
|
||||
<p class="text-sm text-gray-700 mt-3">
|
||||
If anything is missing, reply with changes (e.g. “add role-based access” or “store data in a
|
||||
custom table”).
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="bg-white border border-slate-200 rounded-lg p-4 mx-auto w-full">
|
||||
<h3 class="font-semibold text-gray-900">2) Build</h3>
|
||||
<ul class="list-disc list-inside space-y-2 mt-3 text-sm text-slate-700">
|
||||
<li>Generates plugin scaffolding, admin UI, and core logic</li>
|
||||
<li>Updates existing files instead of starting over when you request changes</li>
|
||||
<li>Surfaces progress and keeps context in the project session</li>
|
||||
</ul>
|
||||
<p class="text-sm text-slate-700 mt-3">
|
||||
Iteration works best when you describe the desired behavior and include exact error messages
|
||||
or screenshots.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bg-white border border-slate-200 rounded-lg p-4 mx-auto w-full mt-6">
|
||||
<h3 class="font-semibold text-gray-900">Common iteration requests</h3>
|
||||
<div class="grid md:grid-cols-2 gap-3 mt-3 text-sm text-slate-700">
|
||||
<div class="bg-white rounded-lg border border-slate-200 p-3 mx-auto w-full">
|
||||
“Add a settings screen for API keys and validate input.”
|
||||
</div>
|
||||
<div class="bg-white rounded-lg border border-slate-200 p-3 mx-auto w-full">
|
||||
“Fix the activation error and add a database migration routine.”
|
||||
</div>
|
||||
<div class="bg-white rounded-lg border border-slate-200 p-3 mx-auto w-full">
|
||||
“Make the admin table sortable + add search and filters.”
|
||||
</div>
|
||||
<div class="bg-white rounded-lg border border-slate-200 p-3 mx-auto w-full">
|
||||
“Add WP-CLI commands for batch processing.”
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<article id="prompting" class="bg-white border border-slate-200 rounded-xl p-4 sm:p-6 shadow-sm">
|
||||
<h2 class="text-2xl font-bold">Writing a great spec</h2>
|
||||
<p class="text-slate-600 mt-2">The more specific your inputs, the more reliable the output.</p>
|
||||
|
||||
<div class="grid md:grid-cols-2 gap-4 mt-4">
|
||||
<div class="bg-white border border-slate-200 rounded-lg p-4 mx-auto w-full">
|
||||
<h3 class="font-semibold text-gray-900">Include these details</h3>
|
||||
<ul class="list-disc list-inside space-y-2 mt-3 text-sm text-slate-700">
|
||||
<li><strong>Actors</strong>: who uses it (admin, editor, member, guest)</li>
|
||||
<li><strong>Data</strong>: what you store, where it lives, and retention requirements</li>
|
||||
<li><strong>UI</strong>: what screens exist and what actions each screen supports</li>
|
||||
<li><strong>Rules</strong>: validation, permissions, and edge cases</li>
|
||||
<li><strong>Acceptance criteria</strong>: concrete checks to confirm it’s correct</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="bg-white border border-slate-200 rounded-lg p-4 mx-auto w-full">
|
||||
<h3 class="font-semibold text-gray-900">When reporting a bug</h3>
|
||||
<ul class="list-disc list-inside space-y-2 mt-3 text-sm text-gray-700">
|
||||
<li>Exact WordPress version + PHP version</li>
|
||||
<li>What you expected vs what happened</li>
|
||||
<li>Any fatal error text from the WP debug log</li>
|
||||
<li>The exact page URL and steps to reproduce</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-6 bg-white border border-slate-200 rounded-lg p-4 mx-auto w-full">
|
||||
<h3 class="font-semibold text-gray-900">Examples of “good” vs “better”</h3>
|
||||
<div class="grid lg:grid-cols-2 gap-3 mt-3 text-sm">
|
||||
<div class="bg-white border border-slate-200 rounded-lg p-4 mx-auto w-full">
|
||||
<p class="font-semibold text-gray-900">Good</p>
|
||||
<p class="text-slate-700 mt-2">“Make a plugin to collect leads.”</p>
|
||||
</div>
|
||||
<div class="bg-white border border-slate-200 rounded-lg p-4 mx-auto w-full">
|
||||
<p class="font-semibold text-gray-900">Better</p>
|
||||
<p class="text-slate-700 mt-2">
|
||||
“Add a lead capture form shortcode with name/email/company fields, store submissions in
|
||||
a custom table,
|
||||
add an admin list screen with search + CSV export, and send a notification email to the
|
||||
site admin.”
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<article id="install" class="bg-white border border-slate-200 rounded-xl p-4 sm:p-6 shadow-sm">
|
||||
<h2 class="text-2xl font-bold">Export & install</h2>
|
||||
<p class="text-slate-600 mt-2">Download your plugin as a ZIP and install it like any other WordPress
|
||||
plugin.</p>
|
||||
|
||||
<div class="grid md:grid-cols-2 gap-4 mt-4">
|
||||
<div class="bg-white border border-slate-200 rounded-lg p-4 mx-auto w-full">
|
||||
<h3 class="font-semibold text-gray-900">Export from the builder</h3>
|
||||
<ol class="list-decimal list-inside space-y-2 mt-3 text-sm text-slate-700">
|
||||
<li>Open your project in the builder.</li>
|
||||
<li>Click <strong>Download ZIP</strong>.</li>
|
||||
<li>Save the ZIP locally (don’t unzip it for WordPress upload).</li>
|
||||
</ol>
|
||||
</div>
|
||||
<div class="bg-white border border-slate-200 rounded-lg p-4 mx-auto w-full">
|
||||
<h3 class="font-semibold text-gray-900">Install in WordPress</h3>
|
||||
<ol class="list-decimal list-inside space-y-2 mt-3 text-sm text-gray-700">
|
||||
<li>In wp-admin: <strong>Plugins → Add New → Upload Plugin</strong>.</li>
|
||||
<li>Select the exported ZIP and click <strong>Install Now</strong>.</li>
|
||||
<li>Click <strong>Activate</strong>.</li>
|
||||
</ol>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bg-white border border-slate-200 rounded-lg p-4 mx-auto w-full mt-6">
|
||||
<h3 class="font-semibold text-gray-900">Recommended deployment flow</h3>
|
||||
<ul class="list-disc list-inside space-y-2 mt-3 text-sm text-slate-700">
|
||||
<li>Install on a staging environment first.</li>
|
||||
<li>Enable <code class="px-1 py-0.5 bg-slate-100 rounded">WP_DEBUG</code> to catch
|
||||
notices/fatals early.</li>
|
||||
<li>Only deploy to production once you’ve validated permissions, forms, and edge-case behavior.
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<article id="billing" class="bg-white border border-slate-200 rounded-xl p-4 sm:p-6 shadow-sm">
|
||||
<h2 class="text-2xl font-bold">Billing & account</h2>
|
||||
<p class="text-slate-600 mt-2">Manage your subscription and payment method from settings.</p>
|
||||
|
||||
<div class="grid md:grid-cols-2 gap-4 mt-4">
|
||||
<div class="bg-white border border-slate-200 rounded-lg p-4 mx-auto w-full">
|
||||
<h3 class="font-semibold text-gray-900">Plans</h3>
|
||||
<ul class="list-disc list-inside space-y-2 mt-3 text-sm text-slate-700">
|
||||
<li>New users are prompted to choose a plan after email verification.</li>
|
||||
<li>Plan limits can affect things like project count, usage, and importing existing plugins.
|
||||
</li>
|
||||
<li>You can upgrade or downgrade later.</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="bg-white border border-slate-200 rounded-lg p-4 mx-auto w-full">
|
||||
<h3 class="font-semibold text-gray-900">Settings</h3>
|
||||
<ul class="list-disc list-inside space-y-2 mt-3 text-sm text-slate-700">
|
||||
<li>Visit <a href="/settings" class="text-green-700 font-semibold">/settings</a> to manage
|
||||
your account.</li>
|
||||
<li>Update your payment method at any time.</li>
|
||||
<li>Cancel or resume a subscription from the same page.</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<article id="security" class="bg-white border border-slate-200 rounded-xl p-4 sm:p-6 shadow-sm">
|
||||
<h2 class="text-2xl font-bold">Security best practices</h2>
|
||||
<p class="text-slate-600 mt-2">WordPress plugins run inside your site—treat them like production
|
||||
software.</p>
|
||||
|
||||
<div class="grid md:grid-cols-2 gap-4 mt-4">
|
||||
<div class="bg-white border border-slate-200 rounded-lg p-4 mx-auto w-full">
|
||||
<h3 class="font-semibold text-gray-900">Admin actions</h3>
|
||||
<ul class="list-disc list-inside space-y-2 mt-3 text-sm text-slate-700">
|
||||
<li>Use capability checks (e.g. <code
|
||||
class="px-1 py-0.5 bg-slate-100 rounded">manage_options</code>)</li>
|
||||
<li>Protect form submissions with nonces</li>
|
||||
<li>Sanitize input and validate server-side</li>
|
||||
<li>Escape output in templates and admin screens</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="bg-white border border-slate-200 rounded-lg p-4 mx-auto w-full">
|
||||
<h3 class="font-semibold text-gray-900">Data handling</h3>
|
||||
<ul class="list-disc list-inside space-y-2 mt-3 text-sm text-gray-700">
|
||||
<li>Store secrets (API keys) in the options table and restrict access</li>
|
||||
<li>Avoid logging sensitive data</li>
|
||||
<li>Use prepared statements for custom SQL queries</li>
|
||||
<li>Document retention/export/deletion requirements when you collect personal data</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<article id="troubleshooting" class="bg-white border border-slate-200 rounded-xl p-4 sm:p-6 shadow-sm">
|
||||
<h2 class="text-2xl font-bold">Troubleshooting</h2>
|
||||
<p class="text-slate-600 mt-2">Common issues and how to resolve them quickly.</p>
|
||||
|
||||
<div class="mt-4 space-y-4">
|
||||
<div class="bg-white border border-slate-200 rounded-lg p-4 mx-auto w-full">
|
||||
<h3 class="font-semibold text-gray-900">I can’t access the dashboard / builder</h3>
|
||||
<ul class="list-disc list-inside space-y-2 mt-2 text-sm text-slate-700">
|
||||
<li>Make sure you’re signed in (try <a href="/login"
|
||||
class="text-green-700 font-semibold">/login</a>).</li>
|
||||
<li>If you’re redirected to plan selection, choose a plan first.</li>
|
||||
<li>If you recently changed browsers/devices, your session cookie may be missing—sign in
|
||||
again.</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="bg-white border border-slate-200 rounded-lg p-4 mx-auto w-full">
|
||||
<h3 class="font-semibold text-gray-900">My export ZIP is empty</h3>
|
||||
<ul class="list-disc list-inside space-y-2 mt-2 text-sm text-slate-700">
|
||||
<li>Make sure you’ve completed at least one build step that generates files.</li>
|
||||
<li>Try asking the builder to list the files it created, then export again.</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="bg-white border border-slate-200 rounded-lg p-4 mx-auto w-full">
|
||||
<h3 class="font-semibold text-gray-900">The plugin fails to activate on WordPress</h3>
|
||||
<ul class="list-disc list-inside space-y-2 mt-2 text-sm text-slate-700">
|
||||
<li>Enable WP debug logging and capture the exact fatal error text.</li>
|
||||
<li>Share the error message with the builder and ask for a fix.</li>
|
||||
<li>Confirm your hosting PHP version meets the plugin’s requirements.</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<article id="support" class="bg-white border border-slate-200 rounded-xl p-4 sm:p-6 shadow-sm">
|
||||
<h2 class="text-2xl font-bold">Support</h2>
|
||||
<p class="text-slate-600 mt-2">Need help refining a plugin or debugging an issue?</p>
|
||||
|
||||
<ul class="list-disc list-inside space-y-2 mt-4 text-slate-700">
|
||||
<li>Start with your project session in the builder and describe the goal or issue.</li>
|
||||
<li>Include URLs, screenshots, and exact error text whenever possible.</li>
|
||||
<li>Check <a href="/settings" class="text-green-700 font-semibold">/settings</a> for account and
|
||||
plan status.</li>
|
||||
</ul>
|
||||
</article>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<!-- CTA Footer (from homepage) -->
|
||||
<section class="py-24 bg-white">
|
||||
<div class="max-w-5xl mx-auto px-4 sm:px-6 lg:px-8 text-center">
|
||||
<h2 class="text-3xl sm:text-4xl font-bold mb-8 text-gray-900">Build Your Custom Plugin Today</h2>
|
||||
<p class="text-xl text-gray-700 mb-10">Start building WordPress plugins that fit your exact needs. No coding
|
||||
experience required.</p>
|
||||
<a href="/signup"
|
||||
class="inline-flex items-center justify-center px-8 py-4 bg-green-700 text-white rounded-full font-bold hover:bg-green-600 transition-all shadow-xl shadow-green-700/20">
|
||||
Get Started Free <i class="fa-solid fa-arrow-right ml-2"></i>
|
||||
</a>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Footer (from homepage) -->
|
||||
<footer class="bg-white border-t border-green-200 pt-16 pb-8">
|
||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div class="grid grid-cols-2 lg:grid-cols-5 gap-12 mb-16">
|
||||
<div class="col-span-2 lg:col-span-1">
|
||||
<div class="flex items-center gap-2 mb-6">
|
||||
<img src="/assets/Plugin.png" alt="Plugin Compass" class="w-8 h-8 rounded-lg">
|
||||
<span class="font-bold text-xl tracking-tight text-gray-800">Plugin<span
|
||||
class="text-green-700">Compass</span></span>
|
||||
</div>
|
||||
<p class="text-gray-600 text-sm leading-relaxed">
|
||||
The smart way for WordPress site owners to replace expensive plugin subscriptions with custom
|
||||
solutions. Save thousands monthly.
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<h4 class="font-bold text-gray-900 mb-6">Product</h4>
|
||||
<ul class="space-y-4 text-sm">
|
||||
<li><a href="/features" class="text-gray-600 hover:text-green-700">Features</a></li>
|
||||
<li><a href="/pricing" class="text-gray-600 hover:text-green-700">Pricing</a></li>
|
||||
<li><a href="#" class="text-gray-600 hover:text-green-700">Templates</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div>
|
||||
<h4 class="font-bold text-gray-900 mb-6">Resources</h4>
|
||||
<ul class="space-y-4 text-sm">
|
||||
<li><a href="/docs" class="text-gray-600 hover:text-green-700">Documentation</a></li>
|
||||
<li><a href="/faq" class="text-gray-600 hover:text-green-700">FAQ</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div>
|
||||
<h4 class="font-bold text-gray-900 mb-6">Legal</h4>
|
||||
<ul class="space-y-4 text-sm">
|
||||
<li><a href="/privacy.html" class="text-gray-600 hover:text-green-700">Privacy Policy</a></li>
|
||||
<li><a href="/terms" class="text-gray-600 hover:text-green-700">Terms of Service</a></li>
|
||||
<li><a href="/contact" class="text-gray-600 hover:text-green-700">Contact Us</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="col-span-2 lg:col-span-1">
|
||||
<h4 class="font-bold text-gray-900 mb-6">Stay Updated</h4>
|
||||
<p class="text-gray-600 text-sm mb-4">Get the latest updates and WordPress tips.</p>
|
||||
<form id="footer-signup-form" class="flex flex-col gap-2">
|
||||
<input type="email" name="email" placeholder="Your email" required
|
||||
class="px-4 py-2 border border-green-200 rounded-lg focus:outline-none focus:ring-2 focus:ring-green-700/20 text-sm">
|
||||
<button type="submit"
|
||||
class="bg-green-700 hover:bg-green-600 text-white px-4 py-2 rounded-lg font-medium text-sm transition-colors shadow-lg shadow-green-700/10">
|
||||
Subscribe
|
||||
</button>
|
||||
</form>
|
||||
<div id="signup-message" class="mt-2 text-xs hidden"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="border-t border-gray-100 pt-8 flex justify-center">
|
||||
<p class="text-gray-500 text-xs text-center">© 2026 Plugin Compass. All rights reserved. Built for
|
||||
WordPress.</p>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<script>
|
||||
const mobileMenuBtn = document.getElementById('mobile-menu-btn');
|
||||
const mobileMenu = document.getElementById('mobile-menu');
|
||||
|
||||
mobileMenuBtn.addEventListener('click', () => {
|
||||
mobileMenu.classList.toggle('hidden');
|
||||
});
|
||||
|
||||
// Smooth scroll for anchor links
|
||||
document.querySelectorAll('a[href^="#"]').forEach(anchor => {
|
||||
anchor.addEventListener('click', function (e) {
|
||||
const href = this.getAttribute('href');
|
||||
if (!href || href === '#') return;
|
||||
|
||||
e.preventDefault();
|
||||
const target = document.querySelector(href);
|
||||
if (target) {
|
||||
target.scrollIntoView({
|
||||
behavior: 'smooth'
|
||||
});
|
||||
// Close mobile menu if open
|
||||
mobileMenu.classList.add('hidden');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Navbar scroll effect
|
||||
window.addEventListener('scroll', () => {
|
||||
const navbar = document.getElementById('navbar');
|
||||
if (window.scrollY > 20) {
|
||||
navbar.classList.add('shadow-md', 'h-16');
|
||||
navbar.classList.remove('h-20');
|
||||
} else {
|
||||
navbar.classList.remove('shadow-md', 'h-16');
|
||||
navbar.classList.add('h-20');
|
||||
}
|
||||
});
|
||||
|
||||
// Email Signup Form Handler
|
||||
const signupForm = document.getElementById('footer-signup-form');
|
||||
const signupMessage = document.getElementById('signup-message');
|
||||
|
||||
if (signupForm) {
|
||||
signupForm.addEventListener('submit', async (e) => {
|
||||
e.preventDefault();
|
||||
const email = signupForm.querySelector('input[name="email"]').value;
|
||||
const button = signupForm.querySelector('button');
|
||||
|
||||
button.disabled = true;
|
||||
button.textContent = 'Subscribing...';
|
||||
|
||||
try {
|
||||
const response = await fetch('https://emailmarketing.modelrailway3d.co.uk/api/webhooks/incoming/wh_0Z49zi_DGj4-lKJMOPO8-g', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
email: email,
|
||||
source: 'plugin_compass_footer',
|
||||
timestamp: new Date().toISOString()
|
||||
})
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
signupMessage.textContent = 'Successfully subscribed!';
|
||||
signupMessage.className = 'mt-2 text-xs text-green-600';
|
||||
signupForm.reset();
|
||||
} else {
|
||||
throw new Error('Failed to subscribe');
|
||||
}
|
||||
} catch (error) {
|
||||
signupMessage.textContent = 'Failed to subscribe. Please try again.';
|
||||
signupMessage.className = 'mt-2 text-xs text-red-600';
|
||||
} finally {
|
||||
signupMessage.classList.remove('hidden');
|
||||
button.disabled = false;
|
||||
button.textContent = 'Subscribe';
|
||||
|
||||
setTimeout(() => {
|
||||
signupMessage.classList.add('hidden');
|
||||
}, 5000);
|
||||
}
|
||||
});
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
1069
chat/public/faq.html
Normal file
882
chat/public/feature-requests.html
Normal file
@@ -0,0 +1,882 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Feature Requests - 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&family=Inter:wght@400;600&display=swap"
|
||||
rel="stylesheet">
|
||||
<link rel="stylesheet" href="/chat/styles.css">
|
||||
<style>
|
||||
:root {
|
||||
--shopify-green: #008060;
|
||||
--shopify-green-dark: #004c3f;
|
||||
--shopify-green-light: #e3f5ef;
|
||||
--accent: #5A31F4;
|
||||
--accent-2: #8B5CF6;
|
||||
}
|
||||
|
||||
body {
|
||||
background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%);
|
||||
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
||||
}
|
||||
|
||||
.fr-container {
|
||||
max-width: 900px;
|
||||
margin: 0 auto;
|
||||
padding: 40px 24px;
|
||||
}
|
||||
|
||||
.fr-header {
|
||||
background: #fff;
|
||||
border: 1px solid var(--border, #e6e9ee);
|
||||
border-radius: 16px;
|
||||
padding: 24px;
|
||||
box-shadow: 0 15px 40px rgba(0, 0, 0, 0.05);
|
||||
margin-bottom: 32px;
|
||||
}
|
||||
|
||||
.fr-header h1 {
|
||||
font-family: 'Space Grotesk', sans-serif;
|
||||
font-size: 28px;
|
||||
font-weight: 600;
|
||||
color: #1a1a1a;
|
||||
margin: 0 0 8px 0;
|
||||
}
|
||||
|
||||
.fr-header p {
|
||||
color: #6b757d;
|
||||
font-size: 15px;
|
||||
margin: 0;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.fr-form {
|
||||
background: #fff;
|
||||
border: 1px solid var(--border, #e6e9ee);
|
||||
border-radius: 16px;
|
||||
padding: 24px;
|
||||
margin-bottom: 32px;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.04);
|
||||
}
|
||||
|
||||
.fr-form h2 {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
margin: 0 0 16px 0;
|
||||
color: #1a1a1a;
|
||||
}
|
||||
|
||||
.fr-input {
|
||||
width: 100%;
|
||||
padding: 12px 16px;
|
||||
border: 2px solid #dee2e6;
|
||||
border-radius: 12px;
|
||||
font-size: 15px;
|
||||
color: #1a1a1a;
|
||||
transition: all 0.2s;
|
||||
outline: none;
|
||||
margin-bottom: 12px;
|
||||
font-family: inherit;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.fr-input:focus {
|
||||
border-color: var(--shopify-green);
|
||||
box-shadow: 0 0 0 3px var(--shopify-green-light);
|
||||
}
|
||||
|
||||
.fr-input::placeholder {
|
||||
color: #adb5bd;
|
||||
}
|
||||
|
||||
.fr-textarea {
|
||||
width: 100%;
|
||||
padding: 12px 16px;
|
||||
border: 2px solid #dee2e6;
|
||||
border-radius: 12px;
|
||||
font-size: 15px;
|
||||
color: #1a1a1a;
|
||||
transition: all 0.2s;
|
||||
outline: none;
|
||||
margin-bottom: 16px;
|
||||
font-family: inherit;
|
||||
min-height: 100px;
|
||||
resize: vertical;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.fr-textarea:focus {
|
||||
border-color: var(--shopify-green);
|
||||
box-shadow: 0 0 0 3px var(--shopify-green-light);
|
||||
}
|
||||
|
||||
.fr-submit-btn {
|
||||
background: linear-gradient(135deg, var(--shopify-green), var(--shopify-green-dark));
|
||||
color: #fff;
|
||||
border: none;
|
||||
border-radius: 12px;
|
||||
padding: 12px 24px;
|
||||
font-weight: 700;
|
||||
font-size: 14px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.fr-submit-btn:hover:not(:disabled) {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 8px 24px rgba(0, 128, 96, 0.2);
|
||||
}
|
||||
|
||||
.fr-submit-btn:disabled {
|
||||
opacity: 0.6;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.fr-list-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.fr-list-header h2 {
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
margin: 0;
|
||||
color: #1a1a1a;
|
||||
}
|
||||
|
||||
.fr-sort {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.fr-sort-btn {
|
||||
padding: 6px 12px;
|
||||
border: 1px solid #dee2e6;
|
||||
background: #fff;
|
||||
border-radius: 8px;
|
||||
font-size: 13px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
color: #6b757d;
|
||||
}
|
||||
|
||||
.fr-sort-btn.active {
|
||||
background: var(--shopify-green);
|
||||
color: #fff;
|
||||
border-color: var(--shopify-green);
|
||||
}
|
||||
|
||||
.fr-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.fr-card {
|
||||
background: #fff;
|
||||
border: 1px solid var(--border, #e6e9ee);
|
||||
border-radius: 16px;
|
||||
padding: 20px;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.04);
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.fr-card:hover {
|
||||
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
.fr-card-header {
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.fr-vote {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
min-width: 48px;
|
||||
}
|
||||
|
||||
.fr-vote-btn {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 10px;
|
||||
border: 1px solid #dee2e6;
|
||||
background: #fff;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #6b757d;
|
||||
}
|
||||
|
||||
.fr-vote-btn:hover:not(:disabled) {
|
||||
border-color: var(--shopify-green);
|
||||
color: var(--shopify-green);
|
||||
background: var(--shopify-green-light);
|
||||
}
|
||||
|
||||
.fr-vote-btn.voted {
|
||||
background: var(--shopify-green);
|
||||
color: #fff;
|
||||
border-color: var(--shopify-green);
|
||||
}
|
||||
|
||||
.fr-vote-btn:disabled {
|
||||
cursor: not-allowed;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.fr-vote-count {
|
||||
font-weight: 700;
|
||||
font-size: 16px;
|
||||
color: #1a1a1a;
|
||||
}
|
||||
|
||||
.fr-content {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.fr-title {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #1a1a1a;
|
||||
margin: 0 0 8px 0;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.fr-description {
|
||||
color: #6b757d;
|
||||
font-size: 14px;
|
||||
line-height: 1.6;
|
||||
margin: 0 0 12px 0;
|
||||
}
|
||||
|
||||
.fr-meta {
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
font-size: 12px;
|
||||
color: #adb5bd;
|
||||
}
|
||||
|
||||
.fr-empty {
|
||||
text-align: center;
|
||||
padding: 60px 20px;
|
||||
background: #fff;
|
||||
border-radius: 16px;
|
||||
border: 2px dashed #dee2e6;
|
||||
}
|
||||
|
||||
.fr-empty-icon {
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
border-radius: 50%;
|
||||
background: var(--shopify-green-light);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 28px;
|
||||
margin: 0 auto 16px;
|
||||
color: var(--shopify-green);
|
||||
}
|
||||
|
||||
.fr-empty h3 {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
margin: 0 0 8px 0;
|
||||
color: #1a1a1a;
|
||||
}
|
||||
|
||||
.fr-empty p {
|
||||
color: #6b757d;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.loading {
|
||||
text-align: center;
|
||||
padding: 60px 20px;
|
||||
}
|
||||
|
||||
.spinner {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border: 3px solid #f1f3f5;
|
||||
border-top-color: var(--shopify-green);
|
||||
border-radius: 50%;
|
||||
animation: spin 0.8s linear infinite;
|
||||
margin: 0 auto 16px;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
.nav-bar {
|
||||
background: white;
|
||||
border-bottom: 1px solid #e9ecef;
|
||||
padding: 16px 24px;
|
||||
margin-bottom: 32px;
|
||||
}
|
||||
|
||||
.nav-content {
|
||||
max-width: 900px;
|
||||
margin: 0 auto;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.brand {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.brand-mark {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 10px;
|
||||
background: linear-gradient(135deg, var(--shopify-green), var(--shopify-green-dark));
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: white;
|
||||
font-weight: 700;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.brand-text {
|
||||
font-family: 'Space Grotesk', sans-serif;
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
color: #1a1a1a;
|
||||
}
|
||||
|
||||
.nav-links {
|
||||
display: flex;
|
||||
gap: 24px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.user-chip {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 8px 10px;
|
||||
border: 1px solid #e9ecef;
|
||||
border-radius: 12px;
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
background: #fff;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.user-chip:hover {
|
||||
border-color: var(--shopify-green);
|
||||
background: #f8f9fa;
|
||||
}
|
||||
|
||||
.user-chip-avatar {
|
||||
width: 34px;
|
||||
height: 34px;
|
||||
border-radius: 999px;
|
||||
background: var(--shopify-green-light);
|
||||
color: var(--shopify-green);
|
||||
display: grid;
|
||||
place-items: center;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.02em;
|
||||
text-transform: uppercase;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.nav-link {
|
||||
color: #6c757d;
|
||||
text-decoration: none;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
transition: color 0.2s;
|
||||
}
|
||||
|
||||
.nav-link:hover {
|
||||
color: var(--shopify-green);
|
||||
}
|
||||
|
||||
.toast {
|
||||
position: fixed;
|
||||
bottom: 24px;
|
||||
right: 24px;
|
||||
background: #1a1a1a;
|
||||
color: #fff;
|
||||
padding: 12px 20px;
|
||||
border-radius: 12px;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.2);
|
||||
transform: translateY(100px);
|
||||
opacity: 0;
|
||||
transition: all 0.3s ease;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.toast.show {
|
||||
transform: translateY(0);
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.toast.error {
|
||||
background: #dc3545;
|
||||
}
|
||||
|
||||
.toast.success {
|
||||
background: #28a745;
|
||||
}
|
||||
</style>
|
||||
|
||||
<!-- PostHog Analytics -->
|
||||
<script src="/posthog.js"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<nav class="nav-bar">
|
||||
<div class="nav-content">
|
||||
<a href="/" class="brand">
|
||||
<img src="/assets/Plugin.png" alt="Plugin Compass" style="width: 32px; height: 32px; border-radius: 8px;">
|
||||
<span class="brand-text">Plugin Compass</span>
|
||||
</a>
|
||||
<div class="nav-links">
|
||||
<a href="/apps" class="nav-link">My Apps</a>
|
||||
<a href="/settings" class="nav-link">Settings</a>
|
||||
<div id="nav-auth-section">
|
||||
<!-- This will be populated by JavaScript based on auth state -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<div class="fr-container">
|
||||
<div class="fr-header">
|
||||
<h1>Feature Requests</h1>
|
||||
<p>Help shape the future of Plugin Compass. Share your ideas and vote on features you'd like to see.</p>
|
||||
</div>
|
||||
|
||||
<div class="fr-form">
|
||||
<h2>Submit a Feature Request</h2>
|
||||
<div id="auth-notice" style="display: none; background: #fff3cd; border: 1px solid #ffeaa7; border-radius: 8px; padding: 12px; margin-bottom: 16px; color: #856404;">
|
||||
<strong>Sign in required:</strong> Please <a href="/login?next=%2Ffeature-requests" style="color: #0056b3;">sign in</a> to submit feature requests.
|
||||
</div>
|
||||
<input type="text" id="fr-title" class="fr-input" placeholder="Feature title (e.g., 'Add dark mode support')" maxlength="150" />
|
||||
<textarea id="fr-description" class="fr-textarea" placeholder="Describe your feature request in detail. What problem would it solve? How would you use it?" maxlength="2000"></textarea>
|
||||
<button class="fr-submit-btn" id="fr-submit">
|
||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<line x1="12" y1="5" x2="12" y2="19"></line>
|
||||
<line x1="5" y1="12" x2="19" y2="12"></line>
|
||||
</svg>
|
||||
Submit Feature Request
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="fr-list-header">
|
||||
<h2>All Requests</h2>
|
||||
<div class="fr-sort">
|
||||
<button class="fr-sort-btn active" data-sort="votes">Most Voted</button>
|
||||
<button class="fr-sort-btn" data-sort="newest">Newest</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="fr-list">
|
||||
<div class="loading">
|
||||
<div class="spinner"></div>
|
||||
<p>Loading feature requests...</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="toast" id="toast"></div>
|
||||
|
||||
<script>
|
||||
const state = {
|
||||
userId: null,
|
||||
featureRequests: [],
|
||||
sortBy: 'votes',
|
||||
};
|
||||
|
||||
const userChip = document.getElementById('fr-user-chip');
|
||||
const userAvatar = document.getElementById('fr-user-avatar');
|
||||
const frTitle = document.getElementById('fr-title');
|
||||
const frDescription = document.getElementById('fr-description');
|
||||
const frSubmit = document.getElementById('fr-submit');
|
||||
const frList = document.getElementById('fr-list');
|
||||
const toast = document.getElementById('toast');
|
||||
|
||||
function cyrb53(str, seed = 0) {
|
||||
let h1 = 0xdeadbeef ^ seed;
|
||||
let h2 = 0x41c6ce57 ^ seed;
|
||||
for (let i = 0, ch; i < str.length; i++) {
|
||||
ch = str.charCodeAt(i);
|
||||
h1 = Math.imul(h1 ^ ch, 2654435761);
|
||||
h2 = Math.imul(h2 ^ ch, 1597334677);
|
||||
}
|
||||
h1 = Math.imul(h1 ^ (h1 >>> 16), 2246822507) ^ Math.imul(h2 ^ (h2 >>> 13), 3266489909);
|
||||
h2 = Math.imul(h2 ^ (h2 >>> 16), 2246822507) ^ Math.imul(h1 ^ (h1 >>> 13), 3266489909);
|
||||
return 4294967296 * (2097151 & h2) + (h1 >>> 0);
|
||||
}
|
||||
|
||||
function computeAccountId(email) {
|
||||
const normalized = (email || '').trim().toLowerCase();
|
||||
if (!normalized) return '';
|
||||
const hash = cyrb53(normalized);
|
||||
return `acct-${hash.toString(16)}`;
|
||||
}
|
||||
|
||||
function resolveUserId() {
|
||||
try {
|
||||
const keys = ['shopify_ai_user', 'wordpress_plugin_ai_user'];
|
||||
for (const key of keys) {
|
||||
const stored = localStorage.getItem(key);
|
||||
if (stored) {
|
||||
const parsed = JSON.parse(stored);
|
||||
if (parsed && parsed.email) {
|
||||
return parsed.accountId || computeAccountId(parsed.email);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (_) { }
|
||||
return '';
|
||||
}
|
||||
|
||||
function readLocalEmail() {
|
||||
const keys = ['shopify_ai_user', 'wordpress_plugin_ai_user'];
|
||||
for (const key of keys) {
|
||||
try {
|
||||
const raw = localStorage.getItem(key);
|
||||
if (!raw) continue;
|
||||
const parsed = JSON.parse(raw);
|
||||
if (parsed?.email) return parsed.email;
|
||||
} catch (_) { }
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
function setUserChipEmail(email) {
|
||||
const safe = (email || '').trim();
|
||||
if (userAvatar) userAvatar.textContent = safe ? safe.charAt(0).toUpperCase() : '?';
|
||||
}
|
||||
|
||||
async function loadUserChip() {
|
||||
let email = '';
|
||||
try {
|
||||
const resp = await fetch('/api/account', { credentials: 'same-origin' });
|
||||
if (resp.ok) {
|
||||
const data = await resp.json().catch(() => ({}));
|
||||
email = data?.account?.email || '';
|
||||
}
|
||||
} catch (_) { }
|
||||
if (!email) email = readLocalEmail();
|
||||
setUserChipEmail(email);
|
||||
}
|
||||
|
||||
state.userId = resolveUserId();
|
||||
try {
|
||||
document.cookie = `chat_user=${encodeURIComponent(state.userId)}; path=/; SameSite=Lax`;
|
||||
} catch (_) { }
|
||||
loadUserChip();
|
||||
|
||||
function updateFormState() {
|
||||
const authNotice = document.getElementById('auth-notice');
|
||||
const submitBtn = document.getElementById('fr-submit');
|
||||
const titleInput = document.getElementById('fr-title');
|
||||
const descInput = document.getElementById('fr-description');
|
||||
|
||||
if (!state.userId) {
|
||||
// Unauthenticated state
|
||||
authNotice.style.display = 'block';
|
||||
submitBtn.disabled = true;
|
||||
titleInput.disabled = true;
|
||||
descInput.disabled = true;
|
||||
titleInput.placeholder = 'Sign in to submit feature requests';
|
||||
descInput.placeholder = 'Sign in to submit feature requests';
|
||||
} else {
|
||||
// Authenticated state
|
||||
authNotice.style.display = 'none';
|
||||
submitBtn.disabled = false;
|
||||
titleInput.disabled = false;
|
||||
descInput.disabled = false;
|
||||
titleInput.placeholder = 'Feature title (e.g., \'Add dark mode support\')';
|
||||
descInput.placeholder = 'Describe your feature request in detail. What problem would it solve? How would you use it?';
|
||||
}
|
||||
}
|
||||
|
||||
function updateNavigation() {
|
||||
const navAuthSection = document.getElementById('nav-auth-section');
|
||||
|
||||
if (!state.userId) {
|
||||
// Unauthenticated state - show sign in link
|
||||
navAuthSection.innerHTML = '<a href="/login?next=%2Ffeature-requests" class="nav-link" style="background: var(--shopify-green); color: white; padding: 8px 16px; border-radius: 8px; text-decoration: none;">Sign In</a>';
|
||||
} else {
|
||||
// Authenticated state - show user chip
|
||||
navAuthSection.innerHTML = `
|
||||
<div class="user-chip" id="fr-user-chip" title="Account & settings">
|
||||
<div class="user-chip-avatar" id="fr-user-avatar">${getUserInitials()}</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
function getUserInitials() {
|
||||
try {
|
||||
const keys = ['shopify_ai_user', 'wordpress_plugin_ai_user'];
|
||||
for (const key of keys) {
|
||||
const stored = localStorage.getItem(key);
|
||||
if (stored) {
|
||||
const parsed = JSON.parse(stored);
|
||||
if (parsed && parsed.email) {
|
||||
return parsed.email.charAt(0).toUpperCase();
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (_) { }
|
||||
return '?';
|
||||
}
|
||||
|
||||
updateFormState();
|
||||
updateNavigation();
|
||||
|
||||
function showToast(message, type = 'info') {
|
||||
toast.textContent = message;
|
||||
toast.className = 'toast ' + type;
|
||||
toast.classList.add('show');
|
||||
setTimeout(() => toast.classList.remove('show'), 3000);
|
||||
}
|
||||
|
||||
async function api(path, options = {}) {
|
||||
const headers = {
|
||||
'Content-Type': 'application/json',
|
||||
'X-User-Id': state.userId,
|
||||
...(options.headers || {}),
|
||||
};
|
||||
const res = await fetch(path, { headers, ...options });
|
||||
const text = await res.text();
|
||||
const json = text ? JSON.parse(text) : {};
|
||||
if (!res.ok) {
|
||||
throw new Error(json.error || res.statusText);
|
||||
}
|
||||
return json;
|
||||
}
|
||||
|
||||
function formatDate(timestamp) {
|
||||
if (!timestamp) return '';
|
||||
const date = new Date(timestamp);
|
||||
const now = new Date();
|
||||
const diffMs = now - date;
|
||||
const diffMins = Math.floor(diffMs / 60000);
|
||||
const diffHours = Math.floor(diffMs / 3600000);
|
||||
const diffDays = Math.floor(diffMs / 86400000);
|
||||
|
||||
if (diffMins < 1) return 'Just now';
|
||||
if (diffMins < 60) return `${diffMins}m ago`;
|
||||
if (diffHours < 24) return `${diffHours}h ago`;
|
||||
if (diffDays < 7) return `${diffDays}d ago`;
|
||||
|
||||
return date.toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' });
|
||||
}
|
||||
|
||||
function renderFeatureRequests() {
|
||||
if (state.featureRequests.length === 0) {
|
||||
frList.innerHTML = `
|
||||
<div class="fr-empty">
|
||||
<div class="fr-empty-icon">💡</div>
|
||||
<h3>No feature requests yet</h3>
|
||||
<p>Be the first to submit a feature request!</p>
|
||||
</div>
|
||||
`;
|
||||
return;
|
||||
}
|
||||
|
||||
const listHtml = state.featureRequests.map(fr => `
|
||||
<div class="fr-card" data-id="${fr.id}">
|
||||
<div class="fr-card-header">
|
||||
<div class="fr-vote">
|
||||
<button class="fr-vote-btn ${fr.hasVoted ? 'voted' : ''}" onclick="upvote('${fr.id}')" ${!state.userId ? 'disabled' : ''} title="${!state.userId ? 'Sign in to vote' : ''}">
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="${fr.hasVoted ? 'currentColor' : 'none'}" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<polyline points="18 15 12 9 6 15"></polyline>
|
||||
</svg>
|
||||
</button>
|
||||
<span class="fr-vote-count">${fr.votes}</span>
|
||||
${!state.userId ? '<span style="font-size: 10px; color: #adb5bd; text-align: center;">Sign in to vote</span>' : ''}
|
||||
</div>
|
||||
<div class="fr-content">
|
||||
<h3 class="fr-title">${escapeHtml(fr.title)}</h3>
|
||||
<p class="fr-description">${escapeHtml(fr.description)}</p>
|
||||
<div class="fr-meta">
|
||||
<span>Submitted by ${escapeHtml(fr.authorEmail || 'Anonymous')}</span>
|
||||
<span>•</span>
|
||||
<span>${formatDate(fr.createdAt)}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`).join('');
|
||||
|
||||
frList.innerHTML = `<div class="fr-list">${listHtml}</div>`;
|
||||
}
|
||||
|
||||
function escapeHtml(str) {
|
||||
if (!str) return '';
|
||||
return String(str)
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"')
|
||||
.replace(/'/g, ''');
|
||||
}
|
||||
|
||||
async function loadFeatureRequests() {
|
||||
try {
|
||||
const data = await api('/api/feature-requests');
|
||||
state.featureRequests = data.featureRequests || [];
|
||||
renderFeatureRequests();
|
||||
} catch (error) {
|
||||
frList.innerHTML = `
|
||||
<div class="fr-empty">
|
||||
<div class="fr-empty-icon">⚠️</div>
|
||||
<h3>Failed to load</h3>
|
||||
<p>${escapeHtml(error.message)}</p>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
async function submitFeatureRequest() {
|
||||
if (!state.userId) {
|
||||
showToast('Please sign in to submit feature requests', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
const title = frTitle.value.trim();
|
||||
const description = frDescription.value.trim();
|
||||
|
||||
if (!title || title.length < 3) {
|
||||
showToast('Title must be at least 3 characters', 'error');
|
||||
frTitle.focus();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!description || description.length < 10) {
|
||||
showToast('Description must be at least 10 characters', 'error');
|
||||
frDescription.focus();
|
||||
return;
|
||||
}
|
||||
|
||||
frSubmit.disabled = true;
|
||||
frSubmit.innerHTML = '<span class="spinner" style="width:18px;height:18px;border-width:2px;margin:0;"></span> Submitting...';
|
||||
|
||||
try {
|
||||
const data = await api('/api/feature-requests', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ title, description }),
|
||||
});
|
||||
|
||||
if (data.featureRequest) {
|
||||
state.featureRequests.unshift(data.featureRequest);
|
||||
renderFeatureRequests();
|
||||
frTitle.value = '';
|
||||
frDescription.value = '';
|
||||
showToast('Feature request submitted!', 'success');
|
||||
}
|
||||
} catch (error) {
|
||||
showToast(error.message || 'Failed to submit', 'error');
|
||||
} finally {
|
||||
frSubmit.disabled = false;
|
||||
frSubmit.innerHTML = `
|
||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<line x1="12" y1="5" x2="12" y2="19"></line>
|
||||
<line x1="5" y1="12" x2="19" y2="12"></line>
|
||||
</svg>
|
||||
Submit Feature Request
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
async function upvote(id) {
|
||||
if (!state.userId) {
|
||||
showToast('Please sign in to vote', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const data = await api(`/api/feature-requests/${id}/upvote`, {
|
||||
method: 'POST',
|
||||
});
|
||||
|
||||
const fr = state.featureRequests.find(f => f.id === id);
|
||||
if (fr) {
|
||||
fr.votes = data.votes;
|
||||
fr.hasVoted = data.hasVoted;
|
||||
}
|
||||
|
||||
renderFeatureRequests();
|
||||
} catch (error) {
|
||||
showToast(error.message || 'Failed to vote', 'error');
|
||||
}
|
||||
}
|
||||
|
||||
function sortFeatureRequests() {
|
||||
if (state.sortBy === 'votes') {
|
||||
state.featureRequests.sort((a, b) => {
|
||||
if (b.votes !== a.votes) return b.votes - a.votes;
|
||||
return new Date(b.createdAt) - new Date(a.createdAt);
|
||||
});
|
||||
} else {
|
||||
state.featureRequests.sort((a, b) => new Date(b.createdAt) - new Date(a.createdAt));
|
||||
}
|
||||
renderFeatureRequests();
|
||||
}
|
||||
|
||||
// Event listeners
|
||||
frSubmit.addEventListener('click', submitFeatureRequest);
|
||||
|
||||
document.querySelectorAll('.fr-sort-btn').forEach(btn => {
|
||||
btn.addEventListener('click', () => {
|
||||
document.querySelectorAll('.fr-sort-btn').forEach(b => b.classList.remove('active'));
|
||||
btn.classList.add('active');
|
||||
state.sortBy = btn.dataset.sort;
|
||||
sortFeatureRequests();
|
||||
});
|
||||
});
|
||||
|
||||
frTitle.addEventListener('keydown', (e) => {
|
||||
if (e.key === 'Enter') {
|
||||
e.preventDefault();
|
||||
frDescription.focus();
|
||||
}
|
||||
});
|
||||
|
||||
frDescription.addEventListener('keydown', (e) => {
|
||||
if (e.key === 'Enter' && e.ctrlKey) {
|
||||
submitFeatureRequest();
|
||||
}
|
||||
});
|
||||
|
||||
// Initial load
|
||||
loadFeatureRequests();
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
731
chat/public/features.html
Normal file
@@ -0,0 +1,731 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" class="scroll-smooth">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="description"
|
||||
content="Discover Plugin Compass AI builder features. Build custom WordPress plugins with AI to replace expensive subscriptions.">
|
||||
<meta name="keywords"
|
||||
content="WordPress plugin builder features, AI plugin generator features, custom WordPress plugins, replace expensive plugins, WordPress automation features">
|
||||
<meta name="robots" content="index, follow">
|
||||
<meta property="og:title" content="Plugin Compass Features - AI WordPress Plugin Builder">
|
||||
<meta property="og:description"
|
||||
content="Discover how Plugin Compass AI builder helps you create custom WordPress plugins to replace expensive subscriptions.">
|
||||
<meta property="og:type" content="website">
|
||||
<meta property="og:url" content="">
|
||||
<meta property="og:site_name" content="Plugin Compass">
|
||||
<meta name="twitter:card" content="summary_large_image">
|
||||
<meta name="twitter:title" content="Plugin Compass Features - AI WordPress Plugin Builder">
|
||||
<meta name="twitter:description"
|
||||
content="Discover how Plugin Compass AI builder helps you create custom WordPress plugins to replace expensive subscriptions.">
|
||||
<link rel="canonical" href="">
|
||||
<title>Plugin Compass Features - AI WordPress Plugin Builder</title>
|
||||
<link rel="icon" type="image/png" href="/assets/Plugin.png">
|
||||
<script>
|
||||
(function () {
|
||||
var url = window.location.origin + '/features';
|
||||
document.querySelector('link[rel="canonical"]').setAttribute('href', url);
|
||||
document.querySelector('meta[property="og:url"]').setAttribute('content', url);
|
||||
})();
|
||||
</script>
|
||||
<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">
|
||||
<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=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
|
||||
|
||||
<script>
|
||||
tailwind.config = {
|
||||
theme: {
|
||||
extend: {
|
||||
fontFamily: {
|
||||
sans: ['Inter', 'sans-serif'],
|
||||
},
|
||||
colors: {
|
||||
brand: {
|
||||
50: '#eef2ff',
|
||||
100: '#e0e7ff',
|
||||
200: '#c7d2fe',
|
||||
300: '#a5b4fc',
|
||||
400: '#818cf8',
|
||||
500: '#6366f1',
|
||||
600: '#4f46e5',
|
||||
700: '#4338ca',
|
||||
800: '#3730a3',
|
||||
900: '#312e81',
|
||||
950: '#1e1b4b',
|
||||
}
|
||||
},
|
||||
animation: {
|
||||
'blob': 'blob 7s infinite',
|
||||
},
|
||||
keyframes: {
|
||||
blob: {
|
||||
'0%': { transform: 'translate(0px, 0px) scale(1)' },
|
||||
'33%': { transform: 'translate(30px, -50px) scale(1.1)' },
|
||||
'66%': { transform: 'translate(-20px, 20px) scale(0.9)' },
|
||||
'100%': { transform: 'translate(0px, 0px) scale(1)' },
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style>
|
||||
::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
background: #fdf6ed;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: #d1d5db;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background: #9ca3af;
|
||||
}
|
||||
|
||||
.glass-nav {
|
||||
background: rgba(251, 246, 239, 0.9);
|
||||
backdrop-filter: blur(12px);
|
||||
-webkit-backdrop-filter: blur(12px);
|
||||
border-bottom: 1px solid rgba(0, 66, 37, 0.1);
|
||||
}
|
||||
|
||||
.hero-gradient-text {
|
||||
background: linear-gradient(to right, #004225, #006B3D, #057857);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
}
|
||||
|
||||
.feature-card {
|
||||
transition: transform 0.3s ease, box-shadow 0.3s ease;
|
||||
}
|
||||
|
||||
.feature-card:hover {
|
||||
transform: translateY(-4px);
|
||||
box-shadow: 0 20px 40px rgba(0, 66, 37, 0.1);
|
||||
}
|
||||
|
||||
.gradient-bg {
|
||||
background: linear-gradient(135deg, #004225, #006B3D);
|
||||
}
|
||||
</style>
|
||||
|
||||
<!-- PostHog Analytics -->
|
||||
<script src="/posthog.js"></script>
|
||||
</head>
|
||||
|
||||
<body class="bg-amber-50 text-gray-900 font-sans antialiased overflow-x-hidden">
|
||||
|
||||
<!-- Navigation -->
|
||||
<nav class="fixed w-full z-50 glass-nav transition-all duration-300" id="navbar">
|
||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div class="flex justify-between items-center h-20">
|
||||
<!-- Logo -->
|
||||
<a href="/" class="flex-shrink-0 flex items-center gap-2 cursor-pointer">
|
||||
<img src="/assets/Plugin.png" alt="Plugin Compass" class="w-8 h-8">
|
||||
<span class="font-bold text-xl tracking-tight text-gray-800">Plugin<span
|
||||
class="text-green-700">Compass</span></span>
|
||||
</a>
|
||||
|
||||
<!-- Desktop Menu -->
|
||||
<div class="hidden md:flex items-center space-x-8">
|
||||
<a href="/features"
|
||||
class="text-green-700 hover:text-gray-900 transition-colors text-sm font-medium">Features</a>
|
||||
<a href="/pricing"
|
||||
class="text-gray-700 hover:text-gray-900 transition-colors text-sm font-medium">Pricing</a>
|
||||
<a href="/docs"
|
||||
class="text-gray-700 hover:text-gray-900 transition-colors text-sm font-medium">Docs</a>
|
||||
<a href="/faq"
|
||||
class="text-gray-700 hover:text-gray-900 transition-colors text-sm font-medium">FAQ</a>
|
||||
</div>
|
||||
|
||||
<!-- CTA Buttons -->
|
||||
<div class="hidden md:flex items-center gap-4">
|
||||
<a href="/login"
|
||||
class="text-gray-700 hover:text-gray-900 font-medium text-sm transition-colors">Sign In</a>
|
||||
<a href="/signup"
|
||||
class="bg-green-700 hover:bg-green-600 text-white px-5 py-2.5 rounded-full font-medium text-sm transition-all shadow-lg shadow-green-700/20 hover:shadow-green-700/40 transform hover:-translate-y-0.5">
|
||||
Get Started
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<!-- Mobile Menu Button -->
|
||||
<div class="md:hidden flex items-center">
|
||||
<button id="mobile-menu-btn" class="text-gray-700 hover:text-gray-900 focus:outline-none">
|
||||
<i class="fa-solid fa-bars text-xl"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Mobile Menu Panel -->
|
||||
<div id="mobile-menu" class="hidden md:hidden bg-amber-50 border-b border-amber-200">
|
||||
<div class="px-4 pt-2 pb-6 space-y-1">
|
||||
<a href="/features"
|
||||
class="block px-3 py-3 text-base font-medium text-green-700 hover:text-gray-900 hover:bg-amber-100 rounded-md">Features</a>
|
||||
<a href="/pricing"
|
||||
class="block px-3 py-3 text-base font-medium text-gray-700 hover:text-gray-900 hover:bg-amber-100 rounded-md">Pricing</a>
|
||||
<a href="/docs"
|
||||
class="block px-3 py-3 text-base font-medium text-gray-700 hover:text-gray-900 hover:bg-amber-100 rounded-md">Docs</a>
|
||||
<a href="/faq"
|
||||
class="block px-3 py-3 text-base font-medium text-gray-700 hover:text-gray-900 hover:bg-amber-100 rounded-md">FAQ</a>
|
||||
<div class="pt-4 flex flex-col gap-3">
|
||||
<a href="/login" class="w-full text-center py-2 text-gray-700 font-medium">Sign In</a>
|
||||
<a href="/signup" class="w-full bg-green-700 text-white text-center py-3 rounded-lg font-medium">Get
|
||||
Started</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<!-- Hero Section -->
|
||||
<section class="relative pt-32 pb-20 lg:pt-48 lg:pb-32 overflow-hidden">
|
||||
<!-- Background Blobs -->
|
||||
<div
|
||||
class="absolute top-0 left-0 -translate-x-1/2 -translate-y-1/2 w-96 h-96 bg-green-600/20 rounded-full mix-blend-multiply filter blur-[100px] opacity-40 animate-blob">
|
||||
</div>
|
||||
<div
|
||||
class="absolute top-0 right-0 translate-x-1/2 -translate-y-1/4 w-96 h-96 bg-green-500/20 rounded-full mix-blend-multiply filter blur-[100px] opacity-40 animate-blob animation-delay-2000">
|
||||
</div>
|
||||
|
||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 relative z-10 text-center">
|
||||
<h1 class="text-5xl md:text-7xl font-bold tracking-tight mb-6 leading-tight text-gray-900">
|
||||
AI-Powered Plugin Builder
|
||||
</h1>
|
||||
|
||||
<p class="mt-4 text-xl text-gray-700 max-w-3xl mx-auto mb-10 leading-relaxed">
|
||||
Discover how Plugin Compass AI builder creates custom WordPress plugins that replace expensive
|
||||
subscriptions. Build exactly what your business needs and own it forever.
|
||||
</p>
|
||||
|
||||
<div class="flex flex-col sm:flex-row justify-center items-center gap-4 mb-16">
|
||||
<a href="/signup"
|
||||
class="w-full sm:w-auto px-8 py-4 bg-green-700 text-white rounded-full font-bold hover:bg-green-600 transition-colors shadow-[0_0_20px_rgba(22,163,74,0.3)]">
|
||||
Start Building Free
|
||||
</a>
|
||||
<a href="/apps"
|
||||
class="w-full sm:w-auto px-8 py-4 border-2 border-green-700 text-green-700 rounded-full font-bold hover:bg-green-50 transition-colors">
|
||||
View Dashboard
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- AI Builder Features -->
|
||||
<section class="py-24 bg-amber-50 relative">
|
||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div class="text-center mb-16">
|
||||
<p class="text-sm font-medium text-gray-600 mb-3 uppercase tracking-wider">AI Builder</p>
|
||||
<h2 class="text-3xl md:text-4xl font-bold mb-4 text-gray-900">Build Custom Plugins with AI</h2>
|
||||
<p class="text-gray-700 max-w-3xl mx-auto">Our AI-powered builder creates production-ready WordPress
|
||||
plugins tailored to your exact needs. Replace expensive subscriptions with custom solutions you own
|
||||
forever.</p>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-8">
|
||||
<!-- Feature 1 -->
|
||||
<div
|
||||
class="p-8 rounded-2xl bg-white border border-green-200 hover:border-green-400 transition-colors feature-card">
|
||||
<div class="w-12 h-12 rounded-lg bg-green-100 flex items-center justify-center text-green-700 mb-6">
|
||||
<i class="fa-solid fa-robot text-xl"></i>
|
||||
</div>
|
||||
<h3 class="text-xl font-semibold mb-3 text-gray-900">AI-Powered Generation</h3>
|
||||
<p class="text-gray-700 leading-relaxed">Describe your plugin in plain English and watch as our AI
|
||||
generates production-ready code. No coding skills required.</p>
|
||||
</div>
|
||||
|
||||
<!-- Feature 2 -->
|
||||
<div
|
||||
class="p-8 rounded-2xl bg-white border border-green-200 hover:border-green-400 transition-colors feature-card">
|
||||
<div class="w-12 h-12 rounded-lg bg-green-100 flex items-center justify-center text-green-700 mb-6">
|
||||
<i class="fa-solid fa-code text-xl"></i>
|
||||
</div>
|
||||
<h3 class="text-xl font-semibold mb-3 text-gray-900">Production-Ready Code</h3>
|
||||
<p class="text-gray-700 leading-relaxed">Get clean, well-structured PHP and JavaScript code that
|
||||
follows WordPress best practices. Ready to install and use immediately.</p>
|
||||
</div>
|
||||
|
||||
<!-- Feature 3 -->
|
||||
<div
|
||||
class="p-8 rounded-2xl bg-white border border-green-200 hover:border-green-400 transition-colors feature-card">
|
||||
<div class="w-12 h-12 rounded-lg bg-green-100 flex items-center justify-center text-green-700 mb-6">
|
||||
<i class="fa-solid fa-download text-xl"></i>
|
||||
</div>
|
||||
<h3 class="text-xl font-semibold mb-3 text-gray-900">One-Click Export</h3>
|
||||
<p class="text-gray-700 leading-relaxed">Download your complete plugin as a ZIP file. Install it on
|
||||
any WordPress site and keep full control of your code.</p>
|
||||
</div>
|
||||
|
||||
<!-- Feature 4 -->
|
||||
<div
|
||||
class="p-8 rounded-2xl bg-white border border-green-200 hover:border-green-400 transition-colors feature-card">
|
||||
<div class="w-12 h-12 rounded-lg bg-green-100 flex items-center justify-center text-green-700 mb-6">
|
||||
<i class="fa-solid fa-cogs text-xl"></i>
|
||||
</div>
|
||||
<h3 class="text-xl font-semibold mb-3 text-gray-900">Custom Functionality</h3>
|
||||
<p class="text-gray-700 leading-relaxed">Build plugins with custom post types, admin interfaces,
|
||||
REST API endpoints, and integrations tailored to your workflow.</p>
|
||||
</div>
|
||||
|
||||
<!-- Feature 5 -->
|
||||
<div
|
||||
class="p-8 rounded-2xl bg-white border border-green-200 hover:border-green-400 transition-colors feature-card">
|
||||
<div class="w-12 h-12 rounded-lg bg-green-100 flex items-center justify-center text-green-700 mb-6">
|
||||
<i class="fa-solid fa-palette text-xl"></i>
|
||||
</div>
|
||||
<h3 class="text-xl font-semibold mb-3 text-gray-900">Custom UI Components</h3>
|
||||
<p class="text-gray-700 leading-relaxed">Create custom admin interfaces, settings pages, and
|
||||
frontend components that match your brand and workflow.</p>
|
||||
</div>
|
||||
|
||||
<!-- Feature 6 -->
|
||||
<div
|
||||
class="p-8 rounded-2xl bg-white border border-green-200 hover:border-green-400 transition-colors feature-card">
|
||||
<div class="w-12 h-12 rounded-lg bg-green-100 flex items-center justify-center text-green-700 mb-6">
|
||||
<i class="fa-solid fa-database text-xl"></i>
|
||||
</div>
|
||||
<h3 class="text-xl font-semibold mb-3 text-gray-900">Database Integration</h3>
|
||||
<p class="text-gray-700 leading-relaxed">Automatically generate custom database tables,
|
||||
relationships, and data structures for your plugin's specific needs.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Cost Savings Section -->
|
||||
<section class="py-20 bg-white">
|
||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div class="grid grid-cols-1 lg:grid-cols-2 gap-12 items-center">
|
||||
<div>
|
||||
<h2 class="text-3xl md:text-4xl font-bold mb-6 text-gray-900">Save Thousands Monthly</h2>
|
||||
<p class="text-xl text-gray-700 mb-8">Replace multiple expensive plugin subscriptions with a single
|
||||
custom solution.</p>
|
||||
|
||||
<div class="space-y-6">
|
||||
<div class="flex items-start gap-4">
|
||||
<div
|
||||
class="w-8 h-8 rounded-full bg-green-100 flex items-center justify-center flex-shrink-0 mt-1">
|
||||
<i class="fa-solid fa-dollar-sign text-green-700"></i>
|
||||
</div>
|
||||
<div>
|
||||
<h4 class="font-semibold text-gray-900 mb-1">Eliminate Recurring Costs</h4>
|
||||
<p class="text-gray-700">Stop paying $50-$500/month for off-the-shelf plugins. Build
|
||||
once, own forever.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex items-start gap-4">
|
||||
<div
|
||||
class="w-8 h-8 rounded-full bg-green-100 flex items-center justify-center flex-shrink-0 mt-1">
|
||||
<i class="fa-solid fa-chart-line text-green-700"></i>
|
||||
</div>
|
||||
<div>
|
||||
<h4 class="font-semibold text-gray-900 mb-1">Consolidate Multiple Plugins</h4>
|
||||
<p class="text-gray-700">Combine functionality from multiple plugins into one
|
||||
streamlined solution.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex items-start gap-4">
|
||||
<div
|
||||
class="w-8 h-8 rounded-full bg-green-100 flex items-center justify-center flex-shrink-0 mt-1">
|
||||
<i class="fa-solid fa-infinity text-green-700"></i>
|
||||
</div>
|
||||
<div>
|
||||
<h4 class="font-semibold text-gray-900 mb-1">No Vendor Lock-in</h4>
|
||||
<p class="text-gray-700">Own your code completely. No licensing fees, no usage limits,
|
||||
no restrictions.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bg-green-50 rounded-2xl p-8 border border-green-200">
|
||||
<h3 class="text-2xl font-bold text-gray-900 mb-6">Cost Comparison</h3>
|
||||
<div class="space-y-4">
|
||||
<div class="flex justify-between items-center p-4 bg-white rounded-lg border border-green-100">
|
||||
<span class="font-medium text-gray-900">Premium Plugin Subscriptions</span>
|
||||
<span class="font-bold text-red-600">$200-$1000/month</span>
|
||||
</div>
|
||||
<div class="flex justify-between items-center p-4 bg-white rounded-lg border border-green-100">
|
||||
<span class="font-medium text-gray-900">Custom Development (Agency)</span>
|
||||
<span class="font-bold text-red-600">$5000-$20000+</span>
|
||||
</div>
|
||||
<div
|
||||
class="flex justify-between items-center p-4 bg-green-700 text-white rounded-lg border border-green-700">
|
||||
<span class="font-medium">Plugin Compass (Business Plan)</span>
|
||||
<span class="font-bold">$29/month</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-8 text-center">
|
||||
<p class="text-sm text-gray-600 mb-4">Build unlimited custom plugins</p>
|
||||
<a href="/signup"
|
||||
class="w-full bg-green-700 text-white py-3 px-6 rounded-lg font-semibold hover:bg-green-600 transition-colors">
|
||||
Start Saving Now
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- How It Works -->
|
||||
<section class="py-16 border-y border-green-300 bg-amber-100/50">
|
||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div class="text-center mb-12">
|
||||
<p class="text-sm font-medium text-gray-600 mb-3 uppercase tracking-wider">How it works</p>
|
||||
<h2 class="text-3xl md:text-4xl font-bold text-gray-900">Build a custom plugin in three simple steps
|
||||
</h2>
|
||||
<p class="mt-4 text-gray-700 max-w-2xl mx-auto">
|
||||
Describe what you want, iterate with AI, then download a ready-to-install WordPress plugin you can
|
||||
own and modify.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-8">
|
||||
<div class="p-8 rounded-2xl bg-white border border-green-200 hover:border-green-400 transition-colors">
|
||||
<div class="w-12 h-12 rounded-lg bg-green-100 flex items-center justify-center text-green-700 mb-6">
|
||||
<i class="fa-solid fa-pen-to-square text-xl"></i>
|
||||
</div>
|
||||
<h3 class="text-xl font-semibold mb-3 text-gray-900">1) Describe your idea</h3>
|
||||
<p class="text-gray-700 leading-relaxed">
|
||||
Explain the workflow you need in plain English—pages, forms, admin screens, rules, permissions,
|
||||
and integrations.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="p-8 rounded-2xl bg-white border border-green-200 hover:border-green-400 transition-colors">
|
||||
<div class="w-12 h-12 rounded-lg bg-green-100 flex items-center justify-center text-green-700 mb-6">
|
||||
<i class="fa-solid fa-wand-magic-sparkles text-xl"></i>
|
||||
</div>
|
||||
<h3 class="text-xl font-semibold mb-3 text-gray-900">2) Build & refine</h3>
|
||||
<p class="text-gray-700 leading-relaxed">
|
||||
Add features, adjust behavior, and fix edge cases by chatting. The builder updates the code as
|
||||
you go.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="p-8 rounded-2xl bg-white border border-green-200 hover:border-green-400 transition-colors">
|
||||
<div class="w-12 h-12 rounded-lg bg-green-100 flex items-center justify-center text-green-700 mb-6">
|
||||
<i class="fa-solid fa-download text-xl"></i>
|
||||
</div>
|
||||
<h3 class="text-xl font-semibold mb-3 text-gray-900">3) Download & install</h3>
|
||||
<p class="text-gray-700 leading-relaxed">
|
||||
Export your plugin as a ZIP, install it on your WordPress site, and keep full control of the
|
||||
source code.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Advanced Features -->
|
||||
<section class="py-24 bg-white">
|
||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div class="text-center mb-16">
|
||||
<p class="text-sm font-medium text-gray-600 mb-3 uppercase tracking-wider">Advanced Capabilities</p>
|
||||
<h2 class="text-3xl md:text-4xl font-bold mb-4 text-gray-900">Powerful Features for Professional
|
||||
Developers</h2>
|
||||
<p class="text-gray-700 max-w-3xl mx-auto">Plugin Compass isn't just for beginners. Advanced users can
|
||||
leverage powerful features to build complex, production-ready applications.</p>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-8">
|
||||
<div class="p-8 rounded-2xl bg-green-50 border border-green-200">
|
||||
<div class="w-12 h-12 rounded-lg bg-green-100 flex items-center justify-center text-green-700 mb-6">
|
||||
<i class="fa-solid fa-shield-alt text-xl"></i>
|
||||
</div>
|
||||
<h3 class="text-xl font-semibold mb-3 text-gray-900">Security Best Practices</h3>
|
||||
<p class="text-gray-700 leading-relaxed mb-4">Built-in security features to protect your plugins and
|
||||
user data.</p>
|
||||
<ul class="space-y-2 text-sm text-gray-600">
|
||||
<li class="flex items-center gap-2">
|
||||
<i class="fa-solid fa-check text-green-600"></i>
|
||||
CSRF protection
|
||||
</li>
|
||||
<li class="flex items-center gap-2">
|
||||
<i class="fa-solid fa-check text-green-600"></i>
|
||||
Input validation
|
||||
</li>
|
||||
<li class="flex items-center gap-2">
|
||||
<i class="fa-solid fa-check text-green-600"></i>
|
||||
Secure authentication
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="p-8 rounded-2xl bg-green-50 border border-green-200">
|
||||
<div class="w-12 h-12 rounded-lg bg-green-100 flex items-center justify-center text-green-700 mb-6">
|
||||
<i class="fa-solid fa-cubes text-xl"></i>
|
||||
</div>
|
||||
<h3 class="text-xl font-semibold mb-3 text-gray-900">Modular Architecture</h3>
|
||||
<p class="text-gray-700 leading-relaxed mb-4">Build plugins with clean, modular architecture for
|
||||
easy maintenance and scalability.</p>
|
||||
<ul class="space-y-2 text-sm text-gray-600">
|
||||
<li class="flex items-center gap-2">
|
||||
<i class="fa-solid fa-check text-green-600"></i>
|
||||
Separation of concerns
|
||||
</li>
|
||||
<li class="flex items-center gap-2">
|
||||
<i class="fa-solid fa-check text-green-600"></i>
|
||||
Reusable components
|
||||
</li>
|
||||
<li class="flex items-center gap-2">
|
||||
<i class="fa-solid fa-check text-green-600"></i>
|
||||
Easy to extend
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Testimonials -->
|
||||
<section class="py-20 bg-amber-50">
|
||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div class="text-center mb-12">
|
||||
<p class="text-sm font-medium text-gray-600 mb-3 uppercase tracking-wider">Success Stories</p>
|
||||
<h2 class="text-3xl md:text-4xl font-bold mb-4 text-gray-900">What Our Users Are Building</h2>
|
||||
<p class="text-gray-700 max-w-3xl mx-auto">See how businesses are using Plugin Compass to replace
|
||||
expensive subscriptions and build custom solutions.</p>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-1 gap-8 max-w-2xl mx-auto">
|
||||
<div class="bg-white p-8 rounded-2xl border border-green-200 shadow-sm">
|
||||
<div class="flex items-center gap-4 mb-6">
|
||||
<div
|
||||
class="w-12 h-12 rounded-full bg-green-100 flex items-center justify-center text-green-700 font-bold">
|
||||
EC
|
||||
</div>
|
||||
<div>
|
||||
<h4 class="font-semibold text-gray-900">E-commerce Store</h4>
|
||||
<p class="text-sm text-gray-600">Replaced 5 premium plugins</p>
|
||||
</div>
|
||||
</div>
|
||||
<p class="text-gray-700 mb-6">"We replaced $450/month in plugin subscriptions with a single custom
|
||||
plugin that does exactly what we need. The AI builder made it incredibly easy."</p>
|
||||
<div class="flex items-center gap-2 text-sm text-gray-600">
|
||||
<i class="fa-solid fa-check text-green-600"></i>
|
||||
<span>Saved $5,400/year</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Final CTA -->
|
||||
<section class="py-24 bg-green-900 text-white relative overflow-hidden">
|
||||
<!-- Background Blobs -->
|
||||
<div
|
||||
class="absolute bottom-0 left-0 -translate-x-1/2 translate-y-1/2 w-96 h-96 bg-green-600/20 rounded-full mix-blend-multiply filter blur-[100px] opacity-40 animate-blob">
|
||||
</div>
|
||||
<div
|
||||
class="absolute bottom-0 right-0 translate-x-1/2 translate-y-1/4 w-96 h-96 bg-green-500/20 rounded-full mix-blend-multiply filter blur-[100px] opacity-40 animate-blob animation-delay-2000">
|
||||
</div>
|
||||
|
||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 relative z-10 text-center">
|
||||
<h2 class="text-4xl md:text-5xl font-bold mb-6">Ready to Build Your Custom Plugin?</h2>
|
||||
<p class="text-xl text-green-100 mb-10 max-w-3xl mx-auto">
|
||||
Start building custom WordPress plugins with AI today. Replace expensive subscriptions and take control
|
||||
of your website's functionality.
|
||||
</p>
|
||||
|
||||
<div class="flex flex-col sm:flex-row justify-center items-center gap-4 mb-12">
|
||||
<a href="/signup"
|
||||
class="w-full sm:w-auto px-8 py-4 bg-white text-green-900 rounded-full font-bold hover:bg-green-50 transition-colors shadow-lg">
|
||||
Start Building Free
|
||||
</a>
|
||||
<a href="/apps"
|
||||
class="w-full sm:w-auto px-8 py-4 border-2 border-white text-white rounded-full font-bold hover:bg-white/10 transition-colors">
|
||||
View Dashboard
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-8 text-left">
|
||||
<div class="flex items-start gap-4">
|
||||
<div class="w-8 h-8 rounded-full bg-white/10 flex items-center justify-center flex-shrink-0 mt-1">
|
||||
<i class="fa-solid fa-rocket text-white"></i>
|
||||
</div>
|
||||
<div>
|
||||
<h4 class="font-semibold mb-1">Launch in Hours</h4>
|
||||
<p class="text-green-200 text-sm">Build and deploy custom plugins faster than ever before.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex items-start gap-4">
|
||||
<div class="w-8 h-8 rounded-full bg-white/10 flex items-center justify-center flex-shrink-0 mt-1">
|
||||
<i class="fa-solid fa-dollar-sign text-white"></i>
|
||||
</div>
|
||||
<div>
|
||||
<h4 class="font-semibold mb-1">Save Thousands</h4>
|
||||
<p class="text-green-200 text-sm">Replace expensive plugin subscriptions with affordable custom
|
||||
solutions.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex items-start gap-4">
|
||||
<div class="w-8 h-8 rounded-full bg-white/10 flex items-center justify-center flex-shrink-0 mt-1">
|
||||
<i class="fa-solid fa-lock text-white"></i>
|
||||
</div>
|
||||
<div>
|
||||
<h4 class="font-semibold mb-1">Own Your Code</h4>
|
||||
<p class="text-green-200 text-sm">Full control over your plugins with no vendor lock-in or
|
||||
restrictions.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Footer -->
|
||||
<footer class="bg-white border-t border-green-200 pt-16 pb-8">
|
||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div class="grid grid-cols-2 lg:grid-cols-5 gap-12 mb-16">
|
||||
<div class="col-span-2 lg:col-span-1">
|
||||
<div class="flex items-center gap-2 mb-6">
|
||||
<img src="/assets/Plugin.png" alt="Plugin Compass" class="w-8 h-8">
|
||||
<span class="font-bold text-xl tracking-tight text-gray-800">Plugin<span
|
||||
class="text-green-700">Compass</span></span>
|
||||
</div>
|
||||
<p class="text-gray-600 text-sm leading-relaxed">
|
||||
The smart way for WordPress site owners to replace expensive plugin subscriptions with custom
|
||||
solutions. Save thousands monthly.
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<h4 class="font-bold text-gray-900 mb-6">Product</h4>
|
||||
<ul class="space-y-4 text-sm">
|
||||
<li><a href="/features" class="text-gray-600 hover:text-green-700">Features</a></li>
|
||||
<li><a href="/pricing" class="text-gray-600 hover:text-green-700">Pricing</a></li>
|
||||
<li><a href="#" class="text-gray-600 hover:text-green-700">Templates</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div>
|
||||
<h4 class="font-bold text-gray-900 mb-6">Resources</h4>
|
||||
<ul class="space-y-4 text-sm">
|
||||
<li><a href="/docs" class="text-gray-600 hover:text-green-700">Documentation</a></li>
|
||||
<li><a href="/faq" class="text-gray-600 hover:text-green-700">FAQ</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div>
|
||||
<h4 class="font-bold text-gray-900 mb-6">Legal</h4>
|
||||
<ul class="space-y-4 text-sm">
|
||||
<li><a href="/privacy.html" class="text-gray-600 hover:text-green-700">Privacy Policy</a></li>
|
||||
<li><a href="/terms" class="text-gray-600 hover:text-green-700">Terms of Service</a></li>
|
||||
<li><a href="/contact" class="text-gray-600 hover:text-green-700">Contact Us</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="col-span-2 lg:col-span-1">
|
||||
<h4 class="font-bold text-gray-900 mb-6">Stay Updated</h4>
|
||||
<p class="text-gray-600 text-sm mb-4">Get the latest updates and WordPress tips.</p>
|
||||
<form id="footer-signup-form" class="flex flex-col gap-2">
|
||||
<input type="email" name="email" placeholder="Your email" required
|
||||
class="px-4 py-2 border border-green-200 rounded-lg focus:outline-none focus:ring-2 focus:ring-green-700/20 text-sm">
|
||||
<button type="submit"
|
||||
class="bg-green-700 hover:bg-green-600 text-white px-4 py-2 rounded-lg font-medium text-sm transition-colors shadow-lg shadow-green-700/10">
|
||||
Subscribe
|
||||
</button>
|
||||
</form>
|
||||
<div id="signup-message" class="mt-2 text-xs hidden"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="border-t border-gray-100 pt-8 flex justify-center">
|
||||
<p class="text-gray-500 text-xs text-center">© 2026 Plugin Compass. All rights reserved. Built for
|
||||
WordPress.</p>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<script>
|
||||
const mobileMenuBtn = document.getElementById('mobile-menu-btn');
|
||||
const mobileMenu = document.getElementById('mobile-menu');
|
||||
|
||||
mobileMenuBtn.addEventListener('click', () => {
|
||||
mobileMenu.classList.toggle('hidden');
|
||||
});
|
||||
|
||||
// Smooth scroll for anchor links
|
||||
document.querySelectorAll('a[href^="#"]').forEach(anchor => {
|
||||
anchor.addEventListener('click', function (e) {
|
||||
e.preventDefault();
|
||||
const target = document.querySelector(this.getAttribute('href'));
|
||||
if (target) {
|
||||
target.scrollIntoView({
|
||||
behavior: 'smooth'
|
||||
});
|
||||
// Close mobile menu if open
|
||||
mobileMenu.classList.add('hidden');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Navbar scroll effect
|
||||
window.addEventListener('scroll', () => {
|
||||
const navbar = document.getElementById('navbar');
|
||||
if (window.scrollY > 20) {
|
||||
navbar.classList.add('shadow-md', 'h-16');
|
||||
navbar.classList.remove('h-20');
|
||||
} else {
|
||||
navbar.classList.remove('shadow-md', 'h-16');
|
||||
navbar.classList.add('h-20');
|
||||
}
|
||||
});
|
||||
|
||||
// Email Signup Form Handler
|
||||
const signupForm = document.getElementById('footer-signup-form');
|
||||
const signupMessage = document.getElementById('signup-message');
|
||||
|
||||
if (signupForm) {
|
||||
signupForm.addEventListener('submit', async (e) => {
|
||||
e.preventDefault();
|
||||
const email = signupForm.querySelector('input[name="email"]').value;
|
||||
const button = signupForm.querySelector('button');
|
||||
|
||||
button.disabled = true;
|
||||
button.textContent = 'Subscribing...';
|
||||
|
||||
try {
|
||||
const response = await fetch('https://emailmarketing.modelrailway3d.co.uk/api/webhooks/incoming/wh_0Z49zi_DGj4-lKJMOPO8-g', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
email: email,
|
||||
source: 'plugin_compass_footer',
|
||||
timestamp: new Date().toISOString()
|
||||
})
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
signupMessage.textContent = 'Successfully subscribed!';
|
||||
signupMessage.className = 'mt-2 text-xs text-green-600';
|
||||
signupForm.reset();
|
||||
} else {
|
||||
throw new Error('Failed to subscribe');
|
||||
}
|
||||
} catch (error) {
|
||||
signupMessage.textContent = 'Failed to subscribe. Please try again.';
|
||||
signupMessage.className = 'mt-2 text-xs text-red-600';
|
||||
} finally {
|
||||
signupMessage.classList.remove('hidden');
|
||||
button.disabled = false;
|
||||
button.textContent = 'Subscribe';
|
||||
|
||||
setTimeout(() => {
|
||||
signupMessage.classList.add('hidden');
|
||||
}, 5000);
|
||||
}
|
||||
});
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
1101
chat/public/home.html
Normal file
634
chat/public/index.html
Normal file
@@ -0,0 +1,634 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Chat with OpenCode</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&family=Inter:wght@400;600&display=swap"
|
||||
rel="stylesheet">
|
||||
<link rel="stylesheet" href="/chat/styles.css">
|
||||
|
||||
<!-- PostHog Analytics -->
|
||||
<script src="/posthog.js"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="app-shell">
|
||||
<aside class="sidebar">
|
||||
<div class="brand">
|
||||
<img src="/assets/Plugin.png" alt="OC" style="width: 32px; height: 32px; border-radius: 8px;">
|
||||
<div>
|
||||
<div class="brand-title">Plugin Compass</div>
|
||||
<div class="brand-sub">Terminal + Chat</div>
|
||||
</div>
|
||||
</div>
|
||||
<button class="primary" id="new-chat">+ New chat</button>
|
||||
<div class="sidebar-section">
|
||||
<div class="section-heading">Sessions</div>
|
||||
<div id="session-list" class="session-list"></div>
|
||||
</div>
|
||||
<div class="sidebar-section slim">
|
||||
<div class="section-heading">Version control</div>
|
||||
<div style="display:flex; flex-direction:column; gap:8px;">
|
||||
<button id="github-button" class="primary">GitHub</button>
|
||||
<button id="diagnostics-button" class="ghost" style="margin-left:8px;">Diagnostics</button>
|
||||
<div id="git-output" class="git-output"></div>
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
<main class="main">
|
||||
<header class="topbar">
|
||||
<div class="topbar-left">
|
||||
<div class="crumb">plugincompass.com</div>
|
||||
<div class="title" id="chat-title">Chat</div>
|
||||
</div>
|
||||
<div class="topbar-actions">
|
||||
<div class="queue-indicator" id="queue-indicator">Idle</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="panel" id="session-meta">
|
||||
<div>
|
||||
<div class="label">Session ID</div>
|
||||
<div id="session-id" class="value">-</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="label">Active model</div>
|
||||
<div id="session-model" class="value">-</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="label">Pending</div>
|
||||
<div id="session-pending" class="value">0</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<section class="chat-area" id="chat-area"></section>
|
||||
|
||||
<div class="composer">
|
||||
<div class="input-row">
|
||||
<textarea id="message-input" rows="3" placeholder="Type your message to OpenCode..."></textarea>
|
||||
<div class="composer-actions">
|
||||
<button id="upload-media-btn" class="ghost" title="Attach images" style="display: none; align-items: center; justify-content: center; width: 40px; height: 40px; padding: 0; border-radius: 8px;">
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M21.44 11.05l-9.19 9.19a6 6 0 0 1-8.49-8.49l9.19-9.19a4 4 0 0 1 5.66 5.66l-9.2 9.19a2 2 0 0 1-2.83-2.83l8.49-8.48"></path>
|
||||
</svg>
|
||||
</button>
|
||||
<input id="upload-media-input" type="file" accept="image/*" multiple style="display:none" />
|
||||
<button id="send-btn" class="primary">Send</button>
|
||||
</div>
|
||||
</div>
|
||||
<div id="attachment-preview" class="attachment-preview" style="display:none"></div>
|
||||
<div style="margin-top:10px; display:flex; gap:8px; align-items:center;">
|
||||
<label class="model-select" style="background:transparent; border:1px solid var(--border);">
|
||||
<span style="color:var(--muted); margin-right:6px;">CLI</span>
|
||||
<select id="cli-select"
|
||||
style="background: #fff; color: var(--text); border: none; padding:4px 8px; border-radius:6px;"></select>
|
||||
</label>
|
||||
<label class="model-select" style="background:transparent; border:1px solid var(--border);">
|
||||
<img id="model-icon" src="" alt=""
|
||||
style="width:18px; height:18px; border-radius:4px; margin-right:8px; display:none;" />
|
||||
<span style="color:var(--muted); margin-right:6px;">Model</span>
|
||||
<select id="model-select"
|
||||
style="background: #fff; color: var(--text); border: none; padding:4px 8px; border-radius:6px;"></select>
|
||||
</label>
|
||||
<label id="custom-model-label" class="model-select"
|
||||
style="display:none; margin-left:8px; background:transparent; border:1px solid var(--border);">
|
||||
<span style="color:var(--muted); margin-right:6px;">Custom</span>
|
||||
<input id="custom-model-input" type="text" placeholder="provider/model or model-name"
|
||||
style="background:transparent; border:none; color:var(--text); padding:0 8px;" />
|
||||
</label>
|
||||
</div>
|
||||
<div class="status-line" id="status-line"></div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
<div id="github-modal" class="modal"
|
||||
style="display:none; position:fixed; inset:0; background:rgba(0,0,0,0.4); align-items:center; justify-content:center; z-index:10000;">
|
||||
<div
|
||||
style="background:var(--panel); padding:20px; border-radius:10px; width:420px; box-shadow:0 10px 40px rgba(0,0,0,0.2);">
|
||||
<div style="display:flex; justify-content:space-between; align-items:center; margin-bottom:8px;">
|
||||
<strong style="color:var(--text);">GitHub</strong>
|
||||
<button id="github-close"
|
||||
style="border:none; background:transparent; color:var(--muted); cursor:pointer; display:flex; align-items:center; justify-content:center; width:24px; height:24px; padding:4px;">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
|
||||
stroke-linecap="round" stroke-linejoin="round">
|
||||
<line x1="18" y1="6" x2="6" y2="18"></line>
|
||||
<line x1="6" y1="6" x2="18" y2="18"></line>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<div style="display:flex; flex-direction:column; gap:8px;">
|
||||
<div style="display:flex; gap:8px; flex-wrap:wrap; align-items:center;">
|
||||
<button data-git="pull" class="ghost">Pull</button>
|
||||
<button data-git="fetch" class="ghost">Fetch</button>
|
||||
<button data-git="status" class="ghost">Status</button>
|
||||
<button data-git="log" class="ghost">Log</button>
|
||||
</div>
|
||||
<div style="display:flex; gap:8px;">
|
||||
<button data-git="push" class="ghost">Commit & Push</button>
|
||||
<button data-git="sync" class="ghost">Sync</button>
|
||||
</div>
|
||||
<input id="modal-commit-message" type="text" placeholder="Commit message" value="Update from chat UI"
|
||||
style="padding:8px; border-radius:6px; border:1px solid var(--border);" />
|
||||
</div>
|
||||
<details style="margin-top:8px; padding:8px; border-radius:6px; background:var(--panel);">
|
||||
<summary style="cursor:pointer; font-weight:600;">What commands will run</summary>
|
||||
<div style="margin-top:8px; color:var(--muted);">
|
||||
<ul>
|
||||
<li><strong>Pull</strong> — git pull</li>
|
||||
<li><strong>Fetch</strong> — git fetch --all</li>
|
||||
<li><strong>Status</strong> — git status --short</li>
|
||||
<li><strong>Log</strong> — git log --oneline -n 20</li>
|
||||
<li><strong>Commit & Push</strong> — git add .; git commit -m "message"; git push origin main</li>
|
||||
<li><strong>Sync</strong> — git pull; git add .; git commit -m "message"; git push origin main</li>
|
||||
</ul>
|
||||
<div style="font-size:12px; color:var(--muted);">Note: Commit will run with the provided message. The server
|
||||
runs git in the workspace root (configurable via the CHAT_REPO_ROOT env var)</div>
|
||||
</div>
|
||||
</details>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Onboarding Modal -->
|
||||
<div id="onboarding-modal" class="onboarding-modal"
|
||||
style="display:none; position:fixed; inset:0; background:rgba(0,0,0,0.5); align-items:center; justify-content:center; z-index:10001;">
|
||||
<div class="onboarding-container"
|
||||
style="background:#fff; border-radius:20px; width:100%; max-width:520px; box-shadow:0 25px 80px rgba(0,0,0,0.2); position:relative; overflow:hidden;">
|
||||
<button id="onboarding-exit"
|
||||
style="position:absolute; top:16px; right:16px; border:none; background:transparent; color:#9ca3af; cursor:pointer; font-size:18px; width:32px; height:32px; border-radius:8px; display:flex; align-items:center; justify-content:center; z-index:10; transition:all 0.2s ease;">
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
|
||||
stroke-linecap="round" stroke-linejoin="round">
|
||||
<line x1="18" y1="6" x2="6" y2="18"></line>
|
||||
<line x1="6" y1="6" x2="18" y2="18"></line>
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<div class="onboarding-progress" style="height:4px; background:#f3f4f6; width:100%;">
|
||||
<div id="onboarding-progress-bar" class="onboarding-progress-bar"
|
||||
style="height:100%; background:linear-gradient(90deg, #008060, #004c3f); width:20%; transition:width 0.3s ease;"></div>
|
||||
</div>
|
||||
|
||||
<div id="onboarding-content" class="onboarding-content" style="padding:32px 32px 24px;">
|
||||
<div id="onboarding-step-1" class="onboarding-step">
|
||||
<div style="width:64px; height:64px; border-radius:16px; background:linear-gradient(135deg, #008060, #004c3f); display:flex; align-items:center; justify-content:center; margin:0 auto 20px;">
|
||||
<svg width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="white" stroke-width="2"
|
||||
stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M12 2L2 7l10 5 10-5-10-5z"></path>
|
||||
<path d="M2 17l10 5 10-5"></path>
|
||||
<path d="M2 12l10 5 10-5"></path>
|
||||
</svg>
|
||||
</div>
|
||||
<h2 style="font-size:24px; font-weight:700; color:#0f172a; margin-bottom:12px; text-align:center;">Welcome to Plugin Compass</h2>
|
||||
<p style="color:#6b7280; font-size:15px; line-height:1.6; text-align:center; margin-bottom:8px;">
|
||||
Build custom WordPress plugins with AI. Replace expensive subscriptions with tailored solutions you can own and customize.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div id="onboarding-step-2" class="onboarding-step" style="display:none;">
|
||||
<div style="width:64px; height:64px; border-radius:16px; background:linear-gradient(135deg, #5A31F4, #8B5CF6); display:flex; align-items:center; justify-content:center; margin:0 auto 20px;">
|
||||
<svg width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="white" stroke-width="2"
|
||||
stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"></path>
|
||||
</svg>
|
||||
</div>
|
||||
<h2 style="font-size:24px; font-weight:700; color:#0f172a; margin-bottom:12px; text-align:center;">Chat with AI</h2>
|
||||
<p style="color:#6b7280; font-size:15px; line-height:1.6; text-align:center; margin-bottom:8px;">
|
||||
Describe the plugin you want to build. Our AI understands WordPress development and creates complete, functional plugins.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div id="onboarding-step-3" class="onboarding-step" style="display:none;">
|
||||
<div style="width:64px; height:64px; border-radius:16px; background:linear-gradient(135deg, #F59E0B, #D97706); display:flex; align-items:center; justify-content:center; margin:0 auto 20px;">
|
||||
<svg width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="white" stroke-width="2"
|
||||
stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M9 19c-5 1.5-5-2.5-7-3m14 6v-3.87a3.37 3.37 0 0 0-.94-2.61c3.14-.35 6.44-1.54 6.44-7A5.44 5.44 0 0 0 20 4.77 5.07 5.07 0 0 0 19.91 1S18.73.65 16 2.48a13.38 13.38 0 0 0-7 0C6.27.65 5.09 1 5.09 1A5.07 5.07 0 0 0 5 4.77a5.44 5.44 0 0 0-1.5 3.78c0 5.42 3.3 6.61 6.44 7A3.37 3.37 0 0 0 9 18.13V22"></path>
|
||||
</svg>
|
||||
</div>
|
||||
<h2 style="font-size:24px; font-weight:700; color:#0f172a; margin-bottom:12px; text-align:center;">Version Control</h2>
|
||||
<p style="color:#6b7280; font-size:15px; line-height:1.6; text-align:center; margin-bottom:8px;">
|
||||
Your plugins are automatically version-controlled. Track changes, collaborate, and deploy with confidence using GitHub integration.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div id="onboarding-step-4" class="onboarding-step" style="display:none;">
|
||||
<div style="width:64px; height:64px; border-radius:16px; background:linear-gradient(135deg, #10B981, #059669); display:flex; align-items:center; justify-content:center; margin:0 auto 20px;">
|
||||
<svg width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="white" stroke-width="2"
|
||||
stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z"></path>
|
||||
<polyline points="3.27 6.96 12 12.01 20.73 6.96"></polyline>
|
||||
<line x1="12" y1="22.08" x2="12" y2="12"></line>
|
||||
</svg>
|
||||
</div>
|
||||
<h2 style="font-size:24px; font-weight:700; color:#0f172a; margin-bottom:12px; text-align:center;">Download & Deploy</h2>
|
||||
<p style="color:#6b7280; font-size:15px; line-height:1.6; text-align:center; margin-bottom:8px;">
|
||||
Download your finished plugin as a ZIP file or connect directly to WordPress. Your code, your way.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div id="onboarding-step-5" class="onboarding-step" style="display:none;">
|
||||
<div style="width:64px; height:64px; border-radius:16px; background:linear-gradient(135deg, #EC4899, #DB2777); display:flex; align-items:center; justify-content:center; margin:0 auto 20px;">
|
||||
<svg width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="white" stroke-width="2"
|
||||
stroke-linecap="round" stroke-linejoin="round">
|
||||
<circle cx="12" cy="12" r="10"></circle>
|
||||
<path d="M8 14s1.5 2 4 2 4-2 4-2"></path>
|
||||
<line x1="9" y1="9" x2="9.01" y2="9"></line>
|
||||
<line x1="15" y1="9" x2="15.01" y2="9"></line>
|
||||
</svg>
|
||||
</div>
|
||||
<h2 style="font-size:24px; font-weight:700; color:#0f172a; margin-bottom:12px; text-align:center;">Ready to Build!</h2>
|
||||
<p style="color:#6b7280; font-size:15px; line-height:1.6; text-align:center; margin-bottom:8px;">
|
||||
Start building your first plugin today. Need help? Check our docs or reach out to support.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="onboarding-footer" style="padding:0 32px 28px; display:flex; align-items:center; justify-content:space-between;">
|
||||
<div class="onboarding-dots" id="onboarding-dots" style="display:flex; gap:8px;">
|
||||
<span class="onboarding-dot active" data-step="1"
|
||||
style="width:8px; height:8px; border-radius:50%; background:#008060; transition:all 0.3s ease; cursor:pointer;"></span>
|
||||
<span class="onboarding-dot" data-step="2"
|
||||
style="width:8px; height:8px; border-radius:50%; background:#d1d5db; transition:all 0.3s ease; cursor:pointer;"></span>
|
||||
<span class="onboarding-dot" data-step="3"
|
||||
style="width:8px; height:8px; border-radius:50%; background:#d1d5db; transition:all 0.3s ease; cursor:pointer;"></span>
|
||||
<span class="onboarding-dot" data-step="4"
|
||||
style="width:8px; height:8px; border-radius:50%; background:#d1d5db; transition:all 0.3s ease; cursor:pointer;"></span>
|
||||
<span class="onboarding-dot" data-step="5"
|
||||
style="width:8px; height:8px; border-radius:50%; background:#d1d5db; transition:all 0.3s ease; cursor:pointer;"></span>
|
||||
</div>
|
||||
<div class="onboarding-buttons" style="display:flex; gap:12px;">
|
||||
<button id="onboarding-back" class="onboarding-btn-secondary"
|
||||
style="padding:10px 20px; font-size:14px; font-weight:600; border-radius:10px; border:1px solid #e5e7eb; background:#fff; color:#374151; cursor:pointer; transition:all 0.2s ease; display:none;">
|
||||
Back
|
||||
</button>
|
||||
<button id="onboarding-next" class="onboarding-btn-primary"
|
||||
style="padding:10px 24px; font-size:14px; font-weight:600; border-radius:10px; border:none; background:linear-gradient(135deg, #008060, #004c3f); color:#fff; cursor:pointer; transition:all 0.2s ease;">
|
||||
Next
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Upgrade Modal for Free Plan Upload Media -->
|
||||
<div id="upgrade-modal" class="modal"
|
||||
style="display:none; position:fixed; inset:0; background:rgba(0,0,0,0.4); align-items:center; justify-content:center; z-index:10000;">
|
||||
<div
|
||||
style="background:#fff; padding:28px; border-radius:16px; width:100%; max-width:480px; box-shadow:0 20px 60px rgba(0,0,0,0.15); border:1px solid var(--border); position:relative;">
|
||||
<div style="display:flex; justify-content:space-between; align-items:center; margin-bottom:20px;">
|
||||
<div style="display:flex; align-items:center; gap:10px;">
|
||||
<div
|
||||
style="width:32px; height:32px; border-radius:10px; background:linear-gradient(135deg, var(--shopify-green), var(--shopify-green-dark)); display:flex; align-items:center; justify-content:center;">
|
||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="white" stroke-width="2"
|
||||
stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z">
|
||||
</path>
|
||||
</svg>
|
||||
</div>
|
||||
<strong style="color:var(--ink); font-size:20px; font-weight:700;">Premium Feature</strong>
|
||||
</div>
|
||||
<button id="upgrade-close"
|
||||
style="border:none; background:transparent; color:var(--muted); cursor:pointer; font-size:20px; display:flex; align-items:center; justify-content:center; width:32px; height:32px; border-radius:8px; transition:all 0.2s ease;">
|
||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
|
||||
stroke-linecap="round" stroke-linejoin="round">
|
||||
<line x1="18" y1="6" x2="6" y2="18"></line>
|
||||
<line x1="6" y1="6" x2="18" y2="18"></line>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<div style="text-align:center; margin-bottom:24px;">
|
||||
<div style="font-size:48px; margin-bottom:16px;">📷</div>
|
||||
<h3 style="font-size:24px; font-weight:700; color:var(--ink); margin-bottom:8px;">Upload Media</h3>
|
||||
<p style="color:var(--muted); margin-bottom:24px; line-height:1.6; font-size:15px;">Upload and attach images to
|
||||
your conversations with AI. This feature is available on our Professional and Enterprise plans.</p>
|
||||
</div>
|
||||
<div
|
||||
style="background:linear-gradient(135deg, rgba(0, 128, 96, 0.1), rgba(0, 76, 63, 0.06)); border:1px solid rgba(0, 128, 96, 0.2); border-radius:12px; padding:16px; margin-bottom:24px;">
|
||||
<h4 style="font-weight:700; color:var(--shopify-green); margin-bottom:8px;">What's included:</h4>
|
||||
<ul style="color:var(--muted); font-size:14px; line-height:1.5; margin:0; padding-left:16px;">
|
||||
<li>Upload multiple images at once</li>
|
||||
<li>Drag & drop interface</li>
|
||||
<li>Automatic image optimization</li>
|
||||
<li>Paste images directly into chat</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div style="display:flex; flex-direction:column; gap:12px;">
|
||||
<button id="upgrade-btn" class="upgrade-btn"
|
||||
style="display:flex; align-items:center; justify-content:center; gap:10px; padding:14px 20px; font-size:16px; font-weight:700; border-radius:12px; transition:all 0.2s ease; background:linear-gradient(135deg, var(--shopify-green), var(--shopify-green-dark)); color:white; border:none;">
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="white" stroke-width="2"
|
||||
stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z">
|
||||
</path>
|
||||
</svg>
|
||||
Upgrade Now
|
||||
</button>
|
||||
<button id="upgrade-later" class="action-link" style="flex:1; justify-content:center;">Maybe Later</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
/* Upgrade Modal Styles */
|
||||
:root {
|
||||
--shopify-green: #008060;
|
||||
--shopify-green-dark: #004c3f;
|
||||
--ink: #0f172a;
|
||||
--muted: #6b7280;
|
||||
}
|
||||
|
||||
#upgrade-close:hover {
|
||||
background: rgba(0, 128, 96, 0.1);
|
||||
color: var(--shopify-green);
|
||||
}
|
||||
|
||||
#upgrade-btn {
|
||||
background: linear-gradient(135deg, var(--shopify-green), var(--shopify-green-dark));
|
||||
color: white;
|
||||
border: none;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
#upgrade-btn:hover {
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 10px 25px rgba(0, 128, 96, 0.25);
|
||||
}
|
||||
|
||||
#upgrade-btn:active {
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
.action-link {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
padding: 10px 12px;
|
||||
border-radius: 10px;
|
||||
font-weight: 600;
|
||||
border: 1px solid var(--border);
|
||||
background: #fff;
|
||||
color: var(--ink);
|
||||
text-decoration: none;
|
||||
transition: transform 0.15s ease, box-shadow 0.2s ease, border-color 0.2s ease;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* Onboarding Modal Styles */
|
||||
#onboarding-exit:hover {
|
||||
background: #f3f4f6;
|
||||
color: #374151;
|
||||
}
|
||||
|
||||
.onboarding-btn-primary:hover {
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 8px 20px rgba(0, 128, 96, 0.3);
|
||||
}
|
||||
|
||||
.onboarding-btn-primary:active {
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
.onboarding-btn-secondary:hover {
|
||||
background: #f9fafb;
|
||||
border-color: #d1d5db;
|
||||
}
|
||||
|
||||
.onboarding-dot.active {
|
||||
background: #008060;
|
||||
transform: scale(1.2);
|
||||
}
|
||||
|
||||
.onboarding-dot:hover {
|
||||
background: #008060;
|
||||
opacity: 0.7;
|
||||
}
|
||||
</style>
|
||||
<script>
|
||||
// Upgrade Modal functionality for index.html
|
||||
window.showUpgradeModal = function () {
|
||||
if (typeof state !== 'undefined' && state.accountPlan === 'enterprise') {
|
||||
alert('You are already on the Enterprise plan with full access.');
|
||||
return;
|
||||
}
|
||||
const modal = document.getElementById('upgrade-modal');
|
||||
if (modal) {
|
||||
modal.style.display = 'flex';
|
||||
}
|
||||
};
|
||||
|
||||
function hideUpgradeModal() {
|
||||
const modal = document.getElementById('upgrade-modal');
|
||||
if (modal) {
|
||||
modal.style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
// Event listeners for upgrade modal
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
const upgradeClose = document.getElementById('upgrade-close');
|
||||
const upgradeBtn = document.getElementById('upgrade-btn');
|
||||
const upgradeLater = document.getElementById('upgrade-later');
|
||||
const upgradeModal = document.getElementById('upgrade-modal');
|
||||
|
||||
if (upgradeClose) {
|
||||
upgradeClose.addEventListener('click', hideUpgradeModal);
|
||||
}
|
||||
|
||||
if (upgradeBtn) {
|
||||
upgradeBtn.addEventListener('click', () => {
|
||||
hideUpgradeModal();
|
||||
window.location.href = '/select-plan';
|
||||
});
|
||||
}
|
||||
|
||||
if (upgradeLater) {
|
||||
upgradeLater.addEventListener('click', hideUpgradeModal);
|
||||
}
|
||||
|
||||
if (upgradeModal) {
|
||||
upgradeModal.addEventListener('click', (e) => {
|
||||
if (e.target === upgradeModal) {
|
||||
hideUpgradeModal();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
</script>
|
||||
<script>
|
||||
// Onboarding Modal functionality
|
||||
(function() {
|
||||
const ONBOARDING_COMPLETED_KEY = 'plugin_compass_onboarding_completed';
|
||||
let currentStep = 1;
|
||||
const totalSteps = 5;
|
||||
|
||||
function isOnboardingCompleted() {
|
||||
try {
|
||||
return localStorage.getItem(ONBOARDING_COMPLETED_KEY) === 'true';
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function markOnboardingCompleted() {
|
||||
try {
|
||||
localStorage.setItem(ONBOARDING_COMPLETED_KEY, 'true');
|
||||
} catch (e) {
|
||||
console.warn('Failed to save onboarding completion status');
|
||||
}
|
||||
}
|
||||
|
||||
function showStep(step) {
|
||||
for (let i = 1; i <= totalSteps; i++) {
|
||||
const stepEl = document.getElementById('onboarding-step-' + i);
|
||||
if (stepEl) {
|
||||
stepEl.style.display = i === step ? 'block' : 'none';
|
||||
}
|
||||
}
|
||||
|
||||
const dots = document.querySelectorAll('.onboarding-dot');
|
||||
dots.forEach((dot, index) => {
|
||||
if (index + 1 === step) {
|
||||
dot.classList.add('active');
|
||||
dot.style.background = '#008060';
|
||||
} else {
|
||||
dot.classList.remove('active');
|
||||
dot.style.background = '#d1d5db';
|
||||
}
|
||||
});
|
||||
|
||||
const progressPercent = (step / totalSteps) * 100;
|
||||
const progressBar = document.getElementById('onboarding-progress-bar');
|
||||
if (progressBar) {
|
||||
progressBar.style.width = progressPercent + '%';
|
||||
}
|
||||
|
||||
const backBtn = document.getElementById('onboarding-back');
|
||||
const nextBtn = document.getElementById('onboarding-next');
|
||||
|
||||
if (backBtn) {
|
||||
backBtn.style.display = step === 1 ? 'none' : 'block';
|
||||
}
|
||||
|
||||
if (nextBtn) {
|
||||
if (step === totalSteps) {
|
||||
nextBtn.textContent = 'Get Started';
|
||||
nextBtn.style.background = 'linear-gradient(135deg, #10B981, #059669)';
|
||||
} else {
|
||||
nextBtn.textContent = 'Next';
|
||||
nextBtn.style.background = 'linear-gradient(135deg, #008060, #004c3f)';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function nextStep() {
|
||||
if (currentStep < totalSteps) {
|
||||
currentStep++;
|
||||
showStep(currentStep);
|
||||
} else {
|
||||
completeOnboarding();
|
||||
}
|
||||
}
|
||||
|
||||
function prevStep() {
|
||||
if (currentStep > 1) {
|
||||
currentStep--;
|
||||
showStep(currentStep);
|
||||
}
|
||||
}
|
||||
|
||||
function goToStep(step) {
|
||||
if (step >= 1 && step <= totalSteps) {
|
||||
currentStep = step;
|
||||
showStep(currentStep);
|
||||
}
|
||||
}
|
||||
|
||||
function completeOnboarding() {
|
||||
markOnboardingCompleted();
|
||||
hideOnboarding();
|
||||
if (typeof posthog !== 'undefined') {
|
||||
posthog.capture('onboarding_completed');
|
||||
}
|
||||
}
|
||||
|
||||
function hideOnboarding() {
|
||||
const modal = document.getElementById('onboarding-modal');
|
||||
if (modal) {
|
||||
modal.style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
function showOnboarding() {
|
||||
const modal = document.getElementById('onboarding-modal');
|
||||
if (modal) {
|
||||
currentStep = 1;
|
||||
showStep(currentStep);
|
||||
modal.style.display = 'flex';
|
||||
}
|
||||
}
|
||||
|
||||
window.showOnboarding = showOnboarding;
|
||||
window.hideOnboarding = hideOnboarding;
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const exitBtn = document.getElementById('onboarding-exit');
|
||||
const backBtn = document.getElementById('onboarding-back');
|
||||
const nextBtn = document.getElementById('onboarding-next');
|
||||
const modal = document.getElementById('onboarding-modal');
|
||||
const dots = document.querySelectorAll('.onboarding-dot');
|
||||
|
||||
if (exitBtn) {
|
||||
exitBtn.addEventListener('click', completeOnboarding);
|
||||
}
|
||||
|
||||
if (backBtn) {
|
||||
backBtn.addEventListener('click', prevStep);
|
||||
}
|
||||
|
||||
if (nextBtn) {
|
||||
nextBtn.addEventListener('click', nextStep);
|
||||
}
|
||||
|
||||
dots.forEach(dot => {
|
||||
dot.addEventListener('click', function() {
|
||||
const step = parseInt(this.getAttribute('data-step'));
|
||||
goToStep(step);
|
||||
});
|
||||
});
|
||||
|
||||
if (modal) {
|
||||
modal.addEventListener('click', function(e) {
|
||||
if (e.target === modal) {
|
||||
completeOnboarding();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (typeof document !== 'undefined') {
|
||||
document.addEventListener('keydown', function(e) {
|
||||
const onboardingModal = document.getElementById('onboarding-modal');
|
||||
if (!onboardingModal || onboardingModal.style.display === 'none') return;
|
||||
|
||||
if (e.key === 'Escape') {
|
||||
completeOnboarding();
|
||||
} else if (e.key === 'ArrowRight') {
|
||||
nextStep();
|
||||
} else if (e.key === 'ArrowLeft') {
|
||||
prevStep();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (!isOnboardingCompleted()) {
|
||||
setTimeout(showOnboarding, 500);
|
||||
}
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
<script src="/chat/app.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
442
chat/public/login.html
Normal file
@@ -0,0 +1,442 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Sign In | Plugin Compass</title>
|
||||
<link rel="icon" type="image/png" href="/assets/Plugin.png">
|
||||
<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">
|
||||
<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=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
|
||||
|
||||
<script>
|
||||
tailwind.config = {
|
||||
theme: {
|
||||
extend: {
|
||||
fontFamily: {
|
||||
sans: ['Inter', 'sans-serif'],
|
||||
},
|
||||
colors: {
|
||||
brand: {
|
||||
50: '#eef2ff',
|
||||
100: '#e0e7ff',
|
||||
200: '#c7d2fe',
|
||||
300: '#a5b4fc',
|
||||
400: '#818cf8',
|
||||
500: '#6366f1',
|
||||
600: '#4f46e5',
|
||||
700: '#4338ca',
|
||||
800: '#3730a3',
|
||||
900: '#312e81',
|
||||
950: '#1e1b4b',
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style>
|
||||
::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
background: #fdf6ed;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: #d1d5db;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background: #9ca3af;
|
||||
}
|
||||
|
||||
.glass-nav {
|
||||
background: rgba(251, 246, 239, 0.9);
|
||||
backdrop-filter: blur(12px);
|
||||
-webkit-backdrop-filter: blur(12px);
|
||||
border-bottom: 1px solid rgba(0, 66, 37, 0.1);
|
||||
}
|
||||
|
||||
.glass-panel {
|
||||
background: rgba(255, 255, 255, 0.8);
|
||||
backdrop-filter: blur(12px);
|
||||
-webkit-backdrop-filter: blur(12px);
|
||||
border: 1px solid rgba(0, 66, 37, 0.1);
|
||||
}
|
||||
</style>
|
||||
|
||||
<!-- PostHog Analytics -->
|
||||
<script src="/posthog.js"></script>
|
||||
</head>
|
||||
|
||||
<body class="bg-amber-50 text-gray-900 font-sans antialiased min-h-screen flex flex-col">
|
||||
|
||||
<!-- Navigation -->
|
||||
<nav class="fixed w-full z-50 glass-nav transition-all duration-300" id="navbar">
|
||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div class="flex justify-between items-center h-20">
|
||||
<!-- Logo -->
|
||||
<a href="/" class="flex-shrink-0 flex items-center gap-2 cursor-pointer">
|
||||
<img src="/assets/Plugin.png" alt="Plugin Compass" class="w-8 h-8">
|
||||
<span class="font-bold text-xl tracking-tight text-gray-800">Plugin<span
|
||||
class="text-green-700">Compass</span></span>
|
||||
</a>
|
||||
|
||||
<!-- Desktop Menu -->
|
||||
<div class="hidden md:flex items-center space-x-8">
|
||||
<a href="/features"
|
||||
class="text-gray-700 hover:text-gray-900 transition-colors text-sm font-medium">Features</a>
|
||||
<a href="/pricing"
|
||||
class="text-gray-700 hover:text-gray-900 transition-colors text-sm font-medium">Pricing</a>
|
||||
<a href="/docs"
|
||||
class="text-gray-700 hover:text-gray-900 transition-colors text-sm font-medium">Docs</a>
|
||||
</div>
|
||||
|
||||
<!-- CTA Buttons -->
|
||||
<div class="hidden md:flex items-center gap-4">
|
||||
<a href="/login"
|
||||
class="text-gray-700 hover:text-gray-900 font-medium text-sm transition-colors">Sign In</a>
|
||||
<a href="/signup"
|
||||
class="bg-green-700 hover:bg-green-600 text-white px-5 py-2.5 rounded-full font-medium text-sm transition-all shadow-lg shadow-green-700/20 hover:shadow-green-700/40 transform hover:-translate-y-0.5">
|
||||
Get Started
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<!-- Mobile Menu Button -->
|
||||
<div class="md:hidden flex items-center">
|
||||
<button id="mobile-menu-btn" class="text-gray-700 hover:text-gray-900 focus:outline-none">
|
||||
<i class="fa-solid fa-bars text-xl"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Mobile Menu Panel -->
|
||||
<div id="mobile-menu" class="hidden md:hidden bg-amber-50 border-b border-amber-200">
|
||||
<div class="px-4 pt-2 pb-6 space-y-1">
|
||||
<a href="/features"
|
||||
class="block px-3 py-3 text-base font-medium text-gray-700 hover:text-gray-900 hover:bg-amber-100 rounded-md">Features</a>
|
||||
<a href="/pricing"
|
||||
class="block px-3 py-3 text-base font-medium text-gray-700 hover:text-gray-900 hover:bg-amber-100 rounded-md">Pricing</a>
|
||||
<a href="/docs"
|
||||
class="block px-3 py-3 text-base font-medium text-gray-700 hover:text-gray-900 hover:bg-amber-100 rounded-md">Docs</a>
|
||||
<div class="pt-4 flex flex-col gap-3">
|
||||
<a href="/signup" class="w-full bg-green-700 text-white text-center py-3 rounded-lg font-medium">Get
|
||||
Started</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<main class="flex-grow flex items-center justify-center px-4 pt-32 pb-12">
|
||||
<div class="max-w-md w-full">
|
||||
<div class="glass-panel p-8 rounded-3xl shadow-2xl shadow-green-900/10">
|
||||
<div class="text-center mb-8">
|
||||
<h1 class="text-3xl font-bold text-gray-900 mb-2">Welcome back</h1>
|
||||
<p class="text-gray-600">Enter your details to access your account.</p>
|
||||
</div>
|
||||
|
||||
<form id="login-form" class="space-y-6">
|
||||
<!-- Honeypot field - hidden from real users -->
|
||||
<input type="text" name="website" id="website" tabindex="-1" autocomplete="off"
|
||||
style="position: absolute; left: -9999px;">
|
||||
|
||||
<div>
|
||||
<label for="email" class="block text-sm font-medium text-gray-700 mb-1">Email Address</label>
|
||||
<input type="email" id="email"
|
||||
class="w-full px-4 py-3 rounded-xl border border-gray-200 focus:ring-2 focus:ring-green-700/20 focus:border-green-700 outline-none transition-all bg-white/50"
|
||||
placeholder="john@example.com">
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div class="flex justify-between mb-1">
|
||||
<label for="password" class="block text-sm font-medium text-gray-700">Password</label>
|
||||
<a href="/reset-password" class="text-xs text-green-700 hover:underline">Forgot
|
||||
password?</a>
|
||||
</div>
|
||||
<input type="password" id="password"
|
||||
class="w-full px-4 py-3 rounded-xl border border-gray-200 focus:ring-2 focus:ring-green-700/20 focus:border-green-700 outline-none transition-all bg-white/50"
|
||||
placeholder="••••••••">
|
||||
</div>
|
||||
|
||||
<div class="flex items-center">
|
||||
<input id="remember" name="remember" type="checkbox"
|
||||
class="h-4 w-4 text-green-700 focus:ring-green-600 border-gray-300 rounded">
|
||||
<label for="remember" class="ml-2 block text-sm text-gray-600">
|
||||
Remember me for 30 days
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<button type="submit"
|
||||
class="w-full bg-green-700 hover:bg-green-600 text-white font-bold py-4 rounded-xl transition-all shadow-lg shadow-green-700/20 transform hover:-translate-y-0.5">
|
||||
Sign In
|
||||
</button>
|
||||
|
||||
<div id="login-status" class="text-sm text-red-600 mt-2 min-h-[1.25rem]"></div>
|
||||
</form>
|
||||
|
||||
<div class="mt-8">
|
||||
<div class="relative">
|
||||
<div class="absolute inset-0 flex items-center">
|
||||
<div class="w-full border-t border-gray-200"></div>
|
||||
</div>
|
||||
<div class="relative flex justify-center text-sm">
|
||||
<span class="px-2 bg-amber-50 text-gray-500">Or continue with</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-6 grid grid-cols-2 gap-4">
|
||||
<button type="button" data-oauth-provider="google"
|
||||
class="flex items-center justify-center gap-2 px-4 py-3 border border-gray-200 rounded-xl hover:bg-gray-50 transition-all bg-white/50">
|
||||
<i class="fa-brands fa-google text-red-500"></i>
|
||||
<span class="text-sm font-medium text-gray-700">Google</span>
|
||||
</button>
|
||||
<button type="button" data-oauth-provider="github"
|
||||
class="flex items-center justify-center gap-2 px-4 py-3 border border-gray-200 rounded-xl hover:bg-gray-50 transition-all bg-white/50">
|
||||
<i class="fa-brands fa-github text-gray-900"></i>
|
||||
<span class="text-sm font-medium text-gray-700">GitHub</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<!-- Footer -->
|
||||
<footer class="bg-white border-t border-green-200 pt-16 pb-8">
|
||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div class="grid grid-cols-2 md:grid-cols-4 gap-12 mb-16">
|
||||
<div class="col-span-2 md:col-span-1">
|
||||
<div class="flex items-center gap-2 mb-6">
|
||||
<img src="/assets/Plugin.png" alt="Plugin Compass" class="w-8 h-8">
|
||||
<span class="font-bold text-xl tracking-tight text-gray-800">Plugin<span
|
||||
class="text-green-700">Compass</span></span>
|
||||
</div>
|
||||
<p class="text-gray-600 text-sm leading-relaxed">
|
||||
The smart way for WordPress site owners to replace expensive plugin subscriptions with custom
|
||||
solutions. Save thousands monthly.
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<h4 class="font-bold text-gray-900 mb-6">Product</h4>
|
||||
<ul class="space-y-4 text-sm">
|
||||
<li><a href="/features" class="text-gray-600 hover:text-green-700">Features</a></li>
|
||||
<li><a href="/pricing" class="text-gray-600 hover:text-green-700">Pricing</a></li>
|
||||
<li><a href="#" class="text-gray-600 hover:text-green-700">Templates</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div>
|
||||
<h4 class="font-bold text-gray-900 mb-6">Resources</h4>
|
||||
<ul class="space-y-4 text-sm">
|
||||
<li><a href="/docs" class="text-gray-600 hover:text-green-700">Documentation</a></li>
|
||||
<li><a href="/faq" class="text-gray-600 hover:text-green-700">FAQ</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div>
|
||||
<h4 class="font-bold text-gray-900 mb-6">Legal</h4>
|
||||
<ul class="space-y-4 text-sm">
|
||||
<li><a href="/privacy" class="text-gray-600 hover:text-green-700">Privacy Policy</a></li>
|
||||
<li><a href="/terms" class="text-gray-600 hover:text-green-700">Terms of Service</a></li>
|
||||
<li><a href="/contact" class="text-gray-600 hover:text-green-700">Contact Us</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="border-t border-gray-100 pt-8 flex justify-center">
|
||||
<p class="text-gray-500 text-xs text-center">© 2026 Plugin Compass. All rights reserved.</p>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
|
||||
<script>
|
||||
// Navigation functionality
|
||||
const mobileMenuBtn = document.getElementById('mobile-menu-btn');
|
||||
const mobileMenu = document.getElementById('mobile-menu');
|
||||
|
||||
if (mobileMenuBtn && mobileMenu) {
|
||||
mobileMenuBtn.addEventListener('click', () => {
|
||||
mobileMenu.classList.toggle('hidden');
|
||||
});
|
||||
}
|
||||
|
||||
// Navbar scroll effect
|
||||
window.addEventListener('scroll', () => {
|
||||
const navbar = document.getElementById('navbar');
|
||||
if (!navbar) return;
|
||||
if (window.scrollY > 20) {
|
||||
navbar.classList.add('shadow-md', 'h-16');
|
||||
navbar.classList.remove('h-20');
|
||||
} else {
|
||||
navbar.classList.remove('shadow-md', 'h-16');
|
||||
navbar.classList.add('h-20');
|
||||
}
|
||||
});
|
||||
function cyrb53(str, seed = 0) {
|
||||
let h1 = 0xdeadbeef ^ seed;
|
||||
let h2 = 0x41c6ce57 ^ seed;
|
||||
for (let i = 0, ch; i < str.length; i++) {
|
||||
ch = str.charCodeAt(i);
|
||||
h1 = Math.imul(h1 ^ ch, 2654435761);
|
||||
h2 = Math.imul(h2 ^ ch, 1597334677);
|
||||
}
|
||||
h1 = Math.imul(h1 ^ (h1 >>> 16), 2246822507) ^ Math.imul(h2 ^ (h2 >>> 13), 3266489909);
|
||||
h2 = Math.imul(h2 ^ (h2 >>> 16), 2246822507) ^ Math.imul(h1 ^ (h1 >>> 13), 3266489909);
|
||||
return 4294967296 * (2097151 & h2) + (h1 >>> 0);
|
||||
}
|
||||
|
||||
function computeAccountId(email) {
|
||||
const normalized = (email || '').trim().toLowerCase();
|
||||
if (!normalized) return '';
|
||||
const hash = cyrb53(normalized);
|
||||
return `acct-${hash.toString(16)}`;
|
||||
}
|
||||
|
||||
function getDeviceUserId() {
|
||||
try {
|
||||
const existing = localStorage.getItem('wordpress_plugin_ai_user_id');
|
||||
if (existing) return existing;
|
||||
const uuidPart = crypto.randomUUID ? crypto.randomUUID() : Math.random().toString(36).slice(2, 12);
|
||||
const generated = `user-${uuidPart}`;
|
||||
localStorage.setItem('wordpress_plugin_ai_user_id', generated);
|
||||
return generated;
|
||||
} catch (_) {
|
||||
const fallbackPart = (typeof crypto !== 'undefined' && crypto.randomUUID)
|
||||
? crypto.randomUUID()
|
||||
: `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 12)}`;
|
||||
return `user-${fallbackPart}`;
|
||||
}
|
||||
}
|
||||
|
||||
function getNextPath() {
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
const next = params.get('next');
|
||||
if (next && next.startsWith('/') && !next.startsWith('//')) return next;
|
||||
return '/apps';
|
||||
}
|
||||
|
||||
function startOAuth(provider) {
|
||||
if (!provider) return;
|
||||
const next = encodeURIComponent(getNextPath());
|
||||
window.location.href = `/auth/${provider}?next=${next}`;
|
||||
}
|
||||
|
||||
document.querySelectorAll('[data-oauth-provider]').forEach((btn) => {
|
||||
btn.addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
const provider = btn.getAttribute('data-oauth-provider');
|
||||
startOAuth(provider);
|
||||
});
|
||||
});
|
||||
|
||||
async function claimDeviceApps(accountId, previousUserId) {
|
||||
try {
|
||||
await fetch('/api/account/claim', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-User-Id': accountId,
|
||||
},
|
||||
body: JSON.stringify({ previousUserId })
|
||||
});
|
||||
} catch (_) {
|
||||
// Best-effort; app data is not deleted.
|
||||
}
|
||||
}
|
||||
|
||||
document.getElementById('login-form').addEventListener('submit', async (e) => {
|
||||
e.preventDefault();
|
||||
const statusEl = document.getElementById('login-status');
|
||||
function setStatus(msg, isError = true) {
|
||||
if (!statusEl) return;
|
||||
statusEl.textContent = msg || '';
|
||||
statusEl.style.color = isError ? 'rgb(185 28 28)' : '';
|
||||
}
|
||||
const emailEl = document.getElementById('email');
|
||||
const email = (emailEl && emailEl.value || '').trim().toLowerCase();
|
||||
if (!email) {
|
||||
if (emailEl) emailEl.focus();
|
||||
return;
|
||||
}
|
||||
const passwordEl = document.getElementById('password');
|
||||
const password = (passwordEl && passwordEl.value) || '';
|
||||
|
||||
const remember = document.getElementById('remember').checked;
|
||||
|
||||
// If a password was provided, try server-side authentication first.
|
||||
if (password) {
|
||||
try {
|
||||
const resp = await fetch('/api/login', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ email, password, remember }),
|
||||
});
|
||||
const data = await resp.json().catch(() => ({}));
|
||||
if (!resp.ok) {
|
||||
setStatus(data.error || 'Login failed', true);
|
||||
return;
|
||||
}
|
||||
|
||||
// Server authentication successful
|
||||
if (data.ok && data.user && data.token) {
|
||||
// Store session information
|
||||
try {
|
||||
localStorage.setItem('wordpress_plugin_ai_user', JSON.stringify({
|
||||
email: data.user.email,
|
||||
accountId: data.user.id,
|
||||
sessionToken: data.token
|
||||
}));
|
||||
} catch (_) { }
|
||||
|
||||
// Continue with account claiming
|
||||
const deviceUserId = getDeviceUserId();
|
||||
await claimDeviceApps(data.user.id, deviceUserId);
|
||||
|
||||
// Redirect to appropriate page based on plan selection
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
let next = params.get('next') || '/apps';
|
||||
if (!next || !next.startsWith('/') || next.startsWith('//')) next = '/apps';
|
||||
|
||||
// Check if user has selected a plan
|
||||
// If data includes plan info, use it; otherwise fetch from /api/me
|
||||
const hasPlan = data.user.plan && ['hobby', 'starter', 'business', 'enterprise'].includes(data.user.plan.toLowerCase());
|
||||
if (!hasPlan && next === '/apps') {
|
||||
next = '/select-plan';
|
||||
}
|
||||
|
||||
window.location.href = next;
|
||||
return;
|
||||
}
|
||||
|
||||
} catch (err) {
|
||||
setStatus('Login request failed, please try again', true);
|
||||
console.warn('Login request failed:', String(err));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback to old behavior if no password or server auth not available
|
||||
const deviceUserId = getDeviceUserId();
|
||||
const accountId = computeAccountId(email);
|
||||
try {
|
||||
localStorage.setItem('wordpress_plugin_ai_user', JSON.stringify({ email, accountId }));
|
||||
} catch (_) { }
|
||||
try {
|
||||
document.cookie = `chat_user=${encodeURIComponent(accountId)}; path=/; SameSite=Lax`;
|
||||
} catch (_) { }
|
||||
|
||||
await claimDeviceApps(accountId, deviceUserId);
|
||||
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
let next = params.get('next') || '/apps';
|
||||
if (!next || !next.startsWith('/') || next.startsWith('//')) next = '/apps';
|
||||
window.location.href = next;
|
||||
});
|
||||
</script>
|
||||
151
chat/public/openrouter-plan-prompt.txt
Normal file
@@ -0,0 +1,151 @@
|
||||
You are the Planning Specialist for the WordPress Plugin Builder. Your role is to collaborate with the user to create a complete, crystal-clear plan before any code is written. This plugin is for the user's site, so ensure the plan fits that. The plugin must be fully compatible with WordPress and follow WP best practices.
|
||||
|
||||
|
||||
## USER REQUEST:
|
||||
{{USER_REQUEST}}
|
||||
## YOUR PLANNING PROCESS:
|
||||
|
||||
### 1. UNDERSTAND & CLARIFY
|
||||
- Read the user's request carefully and identify any ambiguities
|
||||
- Ask targeted clarifying questions about:
|
||||
- Missing features or edge cases
|
||||
- User experience expectations
|
||||
- Data requirements and integrations
|
||||
- Admin vs. customer-facing functionality
|
||||
- Any technical constraints or preferences
|
||||
|
||||
### 2. CREATE A COMPREHENSIVE PLAN
|
||||
Your plan must include:
|
||||
|
||||
**A. Plain-Language Overview**
|
||||
- Summarize what the app does in 2-3 sentences that a non-technical user can understand
|
||||
- Explain the main user benefit
|
||||
|
||||
**B. Complete Feature Checklist**
|
||||
List EVERY feature the user requested, grouped logically:
|
||||
- ✅ Feature 1: [Clear description of what it does]
|
||||
- ✅ Feature 2: [Clear description of what it does]
|
||||
- ✅ Feature 3: [Clear description of what it does]
|
||||
|
||||
**C. User Experience Flow**
|
||||
Describe how users will interact with the plugin:
|
||||
- What do site administrators see in the WordPress admin?
|
||||
- What do visitors see (if applicable)?
|
||||
- What actions can they take at each step?
|
||||
|
||||
**D. Key Functionality Breakdown**
|
||||
For each major component, explain:
|
||||
- What it does in plain language
|
||||
- Why it's needed for the user's request
|
||||
|
||||
|
||||
### 3. VERIFICATION CHECKLIST
|
||||
Before presenting your plan, ensure:
|
||||
- [ ] Every feature from the user's request is explicitly addressed
|
||||
- [ ] No technical jargon without plain-language explanation
|
||||
- [ ] The plan is actionable and specific (not vague)
|
||||
- [ ] Edge cases and potential issues are considered
|
||||
- [ ] The user could explain this plan to someone else
|
||||
|
||||
### 4. PRESENT & CONFIRM
|
||||
End every response with:
|
||||
- A clear summary of what you're proposing
|
||||
- One of these prompts:
|
||||
- "Does this plan capture everything you need? Click the proceed button to proceed to development, or let me know what to adjust."
|
||||
- "I have a few questions before finalizing the plan: [questions]"
|
||||
|
||||
## CRITICAL RULES:
|
||||
- ❌ NEVER write code or technical implementation details or include any file names or function and variable names
|
||||
- ❌ NEVER skip features mentioned in the user's request
|
||||
- ❌ NEVER assume—always clarify ambiguities
|
||||
- ✅ ALWAYS use plain language a non-developer can understand
|
||||
- ✅ ALWAYS get explicit approval before finishing
|
||||
- ✅ Keep responses concise but complete
|
||||
|
||||
---
|
||||
|
||||
## REQUIRED OUTPUT FORMAT
|
||||
|
||||
You must respond in **exactly one** of these two ways:
|
||||
|
||||
---
|
||||
|
||||
### **OPTION 1: If You Need More Information**
|
||||
|
||||
Use this format when the user's request has ambiguities or missing details:
|
||||
|
||||
```
|
||||
To create a complete plan, I need clarification on a few points:
|
||||
|
||||
1. [Specific question about feature X]
|
||||
2. [Specific question about user flow Y]
|
||||
3. [Specific question about data/integration Z]
|
||||
|
||||
Once you answer these, I'll provide a full plan for your approval.
|
||||
```
|
||||
|
||||
**Guidelines:**
|
||||
- Make questions specific and easy to answer
|
||||
- Explain *why* you're asking if it's not obvious
|
||||
|
||||
---
|
||||
|
||||
### **OPTION 2: If You Can Create a Full Plan**
|
||||
|
||||
Use this structured format:
|
||||
|
||||
```
|
||||
## YOUR APP PLAN
|
||||
|
||||
### What This App Does
|
||||
[2-3 sentence plain-language summary that anyone can understand]
|
||||
|
||||
---
|
||||
|
||||
### Core Features
|
||||
Every feature from your request, clearly listed:
|
||||
|
||||
Feature Name – What it does and why it matters
|
||||
**Feature Name – What it does and why it matters
|
||||
Feature Name – What it does and why it matters
|
||||
|
||||
[Continue for all features]
|
||||
|
||||
---
|
||||
|
||||
### How Users Will Interact With It
|
||||
|
||||
For WordPress Site Admins:
|
||||
1. [Step-by-step description of what site administrators see and do in the WordPress admin panel]
|
||||
2. [Include settings, configurations, dashboard views, etc.]
|
||||
|
||||
For Website Visitors/Customers:
|
||||
1. [Step-by-step description of what visitors see on the site]
|
||||
2. [Include user actions, visual elements, and other frontend interactions]
|
||||
|
||||
*(Note: If only admin-facing or only customer-facing, include just the relevant section)*
|
||||
|
||||
---
|
||||
|
||||
### Key Details & Considerations
|
||||
- **Important Note 1:** [Explain any significant technical limitations or requirements]
|
||||
|
||||
|
||||
Does this plan capture everything you need?**
|
||||
Click the proceed button to move to development
|
||||
Or let me know what to improve.
|
||||
|
||||
**Guidelines for this format:**
|
||||
- Keep bullets concise (1-2 sentences each)
|
||||
- Separate admin vs. customer experiences clearly
|
||||
- Number sequential steps, bullet parallel features
|
||||
- End with explicit approval request
|
||||
|
||||
---
|
||||
|
||||
### **Quality Checklist** (Internal - Don't Output)
|
||||
Before sending Option 2, verify:
|
||||
- [ ] Every requested feature is listed with ✅
|
||||
- [ ] User flows are step-by-step and concrete
|
||||
- [ ] No technical terms
|
||||
- [ ] Admin and customer experiences both covered (if applicable)
|
||||
21
chat/public/posthog.js
Normal file
@@ -0,0 +1,21 @@
|
||||
!function(t,e){var o,n,p,r;e.__SV||(window.posthog=e,e._i=[],e.init=function(i,s,a){function g(t,e){var o=e.split(".");2==o.length&&(t=t[o[0]],e=o[1]),t[e]=function(){t.push([e].concat(Array.prototype.slice.call(arguments,0)))}}(p=t.createElement("script")).type="text/javascript",p.crossOrigin="anonymous",p.async=!0,p.src=s.api_host+"/static/array.js",(r=t.getElementsByTagName("script")[0]).parentNode.insertBefore(p,r);var u=e;for(void 0!==a?u=e[a]=[]:a="posthog",u.people=u.people||[],u.toString=function(t){var e="posthog";return"posthog"!==a&&(e+="."+a),t||(e+=" (stub)"),e},u.people.toString=function(){return u.toString(1)+".people (stub)"},o="capture identify alias people.set people.set_once set_config register register_once unregister opt_out_capturing has_opted_out_capturing opt_in_capturing reset isFeatureEnabled onFeatureFlags getFeatureFlagPayload getActiveSurveys getActiveSurveysIds startSessionRecording stopSessionRecording".split(" "),n=0;n<o.length;n++)g(u,o[n]);e._i.push([i,s,a])},window.__SV=1)}(document,window.posthog||[]);
|
||||
|
||||
(function() {
|
||||
var script = document.createElement('script');
|
||||
script.src = '/api/posthog-config.js';
|
||||
script.async = true;
|
||||
script.onload = function() {
|
||||
if (window.posthogConfig && window.posthogConfig.apiKey) {
|
||||
window.posthog.init(window.posthogConfig.apiKey, {
|
||||
api_host: window.posthogConfig.apiHost,
|
||||
loaded: function(ph) {
|
||||
console.log('PostHog loaded successfully');
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
script.onerror = function() {
|
||||
console.error('Failed to load PostHog config');
|
||||
};
|
||||
document.head.appendChild(script);
|
||||
})();
|
||||
910
chat/public/pricing.html
Normal file
@@ -0,0 +1,910 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" class="scroll-smooth">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="description"
|
||||
content="Plugin Compass pricing plans. Affordable AI WordPress plugin builder to replace expensive subscriptions.">
|
||||
<meta name="keywords"
|
||||
content="WordPress plugin builder pricing, AI plugin generator cost, custom WordPress plugins pricing, affordable plugin builder">
|
||||
<meta name="robots" content="index, follow">
|
||||
<meta property="og:title" content="Plugin Compass Pricing - Affordable AI WordPress Plugin Builder">
|
||||
<meta property="og:description"
|
||||
content="Choose from flexible pricing plans to build custom WordPress plugins with AI and save thousands monthly.">
|
||||
<meta property="og:type" content="website">
|
||||
<meta property="og:url" content="">
|
||||
<meta property="og:site_name" content="Plugin Compass">
|
||||
<meta name="twitter:card" content="summary_large_image">
|
||||
<meta name="twitter:title" content="Plugin Compass Pricing - Affordable AI WordPress Plugin Builder">
|
||||
<meta name="twitter:description"
|
||||
content="Choose from flexible pricing plans to build custom WordPress plugins with AI and save thousands monthly.">
|
||||
<link rel="canonical" href="">
|
||||
<title>Pricing | Plugin Compass</title>
|
||||
<link rel="icon" type="image/png" href="/assets/Plugin.png">
|
||||
<script>
|
||||
(function () {
|
||||
var url = window.location.origin + '/pricing';
|
||||
document.querySelector('link[rel="canonical"]').setAttribute('href', url);
|
||||
document.querySelector('meta[property="og:url"]').setAttribute('content', url);
|
||||
})();
|
||||
</script>
|
||||
<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">
|
||||
<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=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
|
||||
|
||||
<script>
|
||||
tailwind.config = {
|
||||
theme: {
|
||||
extend: {
|
||||
fontFamily: {
|
||||
sans: ['Inter', 'sans-serif'],
|
||||
},
|
||||
colors: {
|
||||
brand: {
|
||||
50: '#eef2ff',
|
||||
100: '#e0e7ff',
|
||||
200: '#c7d2fe',
|
||||
300: '#a5b4fc',
|
||||
400: '#818cf8',
|
||||
500: '#6366f1',
|
||||
600: '#4f46e5',
|
||||
700: '#4338ca',
|
||||
800: '#3730a3',
|
||||
900: '#312e81',
|
||||
950: '#1e1b4b',
|
||||
}
|
||||
},
|
||||
animation: {
|
||||
'blob': 'blob 7s infinite',
|
||||
},
|
||||
keyframes: {
|
||||
blob: {
|
||||
'0%': { transform: 'translate(0px, 0px) scale(1)' },
|
||||
'33%': { transform: 'translate(30px, -50px) scale(1.1)' },
|
||||
'66%': { transform: 'translate(-20px, 20px) scale(0.9)' },
|
||||
'100%': { transform: 'translate(0px, 0px) scale(1)' },
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style>
|
||||
::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
background: #fdf6ed;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: #d1d5db;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background: #9ca3af;
|
||||
}
|
||||
|
||||
.glass-nav {
|
||||
background: rgba(251, 246, 239, 0.9);
|
||||
backdrop-filter: blur(12px);
|
||||
-webkit-backdrop-filter: blur(12px);
|
||||
border-bottom: 1px solid rgba(0, 66, 37, 0.1);
|
||||
}
|
||||
|
||||
.hero-gradient-text {
|
||||
background: linear-gradient(to right, #004225, #006B3D, #057857);
|
||||
-webkit-background-clip: text;
|
||||
background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
}
|
||||
|
||||
.feature-card {
|
||||
transition: transform 0.3s ease, box-shadow 0.3s ease;
|
||||
}
|
||||
|
||||
.feature-card:hover {
|
||||
transform: translateY(-4px);
|
||||
box-shadow: 0 20px 40px rgba(0, 66, 37, 0.1);
|
||||
}
|
||||
|
||||
.gradient-bg {
|
||||
background: linear-gradient(135deg, #004225, #006B3D);
|
||||
}
|
||||
|
||||
.currency-dropdown.open #currency-btn {
|
||||
border-color: #057857;
|
||||
box-shadow: 0 0 0 3px rgba(5, 120, 87, 0.1);
|
||||
}
|
||||
|
||||
.currency-option {
|
||||
transition: all 0.15s ease;
|
||||
}
|
||||
|
||||
.currency-option:hover {
|
||||
background-color: #d1fae5;
|
||||
color: #065f46;
|
||||
}
|
||||
</style>
|
||||
|
||||
<!-- PostHog Analytics -->
|
||||
<script src="/posthog.js"></script>
|
||||
</head>
|
||||
|
||||
<body class="bg-amber-50 text-gray-900 font-sans antialiased overflow-x-hidden">
|
||||
|
||||
<!-- Navigation -->
|
||||
<nav class="fixed w-full z-50 glass-nav transition-all duration-300" id="navbar">
|
||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div class="flex justify-between items-center h-20">
|
||||
<!-- Logo -->
|
||||
<a href="/" class="flex-shrink-0 flex items-center gap-2 cursor-pointer">
|
||||
<img src="/assets/Plugin.png" alt="Plugin Compass" class="w-8 h-8">
|
||||
<span class="font-bold text-xl tracking-tight text-gray-800">Plugin<span
|
||||
class="text-green-700">Compass</span></span>
|
||||
</a>
|
||||
|
||||
<!-- Desktop Menu -->
|
||||
<div class="hidden md:flex items-center space-x-8">
|
||||
<a href="/features"
|
||||
class="text-gray-700 hover:text-gray-900 transition-colors text-sm font-medium">Features</a>
|
||||
<a href="/pricing" class="text-green-700 font-bold text-sm">Pricing</a>
|
||||
<a href="/docs"
|
||||
class="text-gray-700 hover:text-gray-900 transition-colors text-sm font-medium">Docs</a>
|
||||
<a href="/faq"
|
||||
class="text-gray-700 hover:text-gray-900 transition-colors text-sm font-medium">FAQ</a>
|
||||
</div>
|
||||
|
||||
<!-- CTA Buttons -->
|
||||
<div class="hidden md:flex items-center gap-4">
|
||||
<a href="/login"
|
||||
class="text-gray-700 hover:text-gray-900 font-medium text-sm transition-colors">Sign In</a>
|
||||
<a href="/signup"
|
||||
class="bg-green-700 hover:bg-green-600 text-white px-5 py-2.5 rounded-full font-medium text-sm transition-all shadow-lg shadow-green-700/20 hover:shadow-green-700/40 transform hover:-translate-y-0.5">
|
||||
Get Started
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<!-- Mobile Menu Button -->
|
||||
<div class="md:hidden flex items-center">
|
||||
<button id="mobile-menu-btn" class="text-gray-700 hover:text-gray-900 focus:outline-none">
|
||||
<i class="fa-solid fa-bars text-xl"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Mobile Menu Panel -->
|
||||
<div id="mobile-menu" class="hidden md:hidden bg-amber-50 border-b border-amber-200">
|
||||
<div class="px-4 pt-2 pb-6 space-y-1">
|
||||
<a href="/features"
|
||||
class="block px-3 py-3 text-base font-medium text-gray-700 hover:text-gray-900 hover:bg-amber-100 rounded-md">Features</a>
|
||||
<a href="/pricing"
|
||||
class="block px-3 py-3 text-base font-medium text-green-700 font-bold hover:bg-amber-100 rounded-md">Pricing</a>
|
||||
<a href="/docs"
|
||||
class="block px-3 py-3 text-base font-medium text-gray-700 hover:text-gray-900 hover:bg-amber-100 rounded-md">Docs</a>
|
||||
<a href="/faq"
|
||||
class="block px-3 py-3 text-base font-medium text-gray-700 hover:text-gray-900 hover:bg-amber-100 rounded-md">FAQ</a>
|
||||
<div class="pt-4 flex flex-col gap-3">
|
||||
<a href="/login" class="w-full text-center py-2 text-gray-700 font-medium">Sign In</a>
|
||||
<a href="/signup" class="w-full bg-green-700 text-white text-center py-3 rounded-lg font-medium">Get
|
||||
Started</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<!-- Pricing Plans -->
|
||||
<section class="py-24 bg-amber-50 relative">
|
||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div class="text-center mb-16">
|
||||
<h1 class="text-4xl md:text-5xl font-bold text-gray-900 mb-4">Simple, transparent pricing</h1>
|
||||
<p class="text-xl text-gray-700">Choose the plan that's right for your business.</p>
|
||||
|
||||
<div class="mt-8 flex flex-col sm:flex-row items-center justify-center gap-4">
|
||||
<div class="inline-flex items-center bg-white p-1 rounded-full border border-gray-200">
|
||||
<button id="monthly-toggle"
|
||||
class="px-6 py-2 rounded-full text-sm font-medium bg-green-700 text-white shadow-sm transition-all">Monthly</button>
|
||||
<button id="yearly-toggle"
|
||||
class="px-6 py-2 rounded-full text-sm font-medium text-gray-600 hover:text-gray-900 transition-all">Yearly
|
||||
<span class="text-green-600 font-bold">2 months free</span></button>
|
||||
</div>
|
||||
|
||||
<div class="relative currency-dropdown" 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>
|
||||
</button>
|
||||
<div id="currency-options" class="hidden absolute top-full left-0 mt-1 w-full bg-white border border-gray-200 rounded-xl shadow-lg z-50 overflow-hidden">
|
||||
<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" 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" 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" data-value="EUR">
|
||||
🇪🇺 €<span class="ml-1">EUR</span>
|
||||
</button>
|
||||
</div>
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-8 max-w-7xl mx-auto">
|
||||
<!-- Hobby Plan -->
|
||||
<div class="bg-white rounded-3xl p-8 border border-gray-100 shadow-xl shadow-gray-200/50 flex flex-col">
|
||||
<h3 class="text-xl font-bold text-gray-900 mb-2">Hobby</h3>
|
||||
<p class="text-gray-600 text-sm mb-6">For personal projects and exploration.</p>
|
||||
<div class="mb-6">
|
||||
<span id="hobby-price" class="text-4xl font-bold text-gray-900">$0</span>
|
||||
<span class="price-duration text-gray-500">/mo</span>
|
||||
</div>
|
||||
<ul class="space-y-4 mb-8 flex-grow">
|
||||
<li class="flex items-center text-sm text-gray-700">
|
||||
<i class="fa-solid fa-check text-green-600 mr-3"></i> Up to 3 apps
|
||||
</li>
|
||||
<li class="flex items-center text-sm text-gray-700">
|
||||
<i class="fa-solid fa-check text-green-600 mr-3"></i> No choice of ai models
|
||||
</li>
|
||||
<li class="flex items-center text-sm text-gray-700">
|
||||
<a href="/credits" target="_blank" class="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>
|
||||
<i class="fa-solid fa-check text-green-600 mr-3"></i> 50,000 monthly AI credits
|
||||
</li>
|
||||
<li class="flex items-center text-sm text-gray-700">
|
||||
<i class="fa-solid fa-check text-green-600 mr-3"></i> Standard queue
|
||||
</li>
|
||||
</ul>
|
||||
<a href="/signup"
|
||||
class="w-full py-3 px-6 rounded-xl border-2 border-green-700 text-green-700 font-bold hover:bg-green-50 transition-colors text-center">
|
||||
Start for Free
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<!-- Starter Plan -->
|
||||
<div class="bg-white rounded-3xl p-8 border border-gray-100 shadow-xl shadow-gray-200/50 flex flex-col">
|
||||
<h3 class="text-xl font-bold text-gray-900 mb-2">Starter</h3>
|
||||
<p class="text-gray-600 text-sm mb-6">Great for small business needs.</p>
|
||||
<div class="mb-6">
|
||||
<span id="starter-price" class="text-4xl font-bold text-gray-900">$7.50</span>
|
||||
<span class="price-duration text-gray-500">/mo</span>
|
||||
</div>
|
||||
<ul class="space-y-4 mb-8 flex-grow">
|
||||
<li class="flex items-center text-sm text-gray-700">
|
||||
<i class="fa-solid fa-check text-green-600 mr-3"></i> Up to 10 apps
|
||||
</li>
|
||||
<li class="flex items-center text-sm text-gray-700">
|
||||
<a href="/credits" target="_blank" class="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>
|
||||
<i class="fa-solid fa-check text-green-600 mr-3"></i> 100,000 monthly AI credits
|
||||
</li>
|
||||
<li class="flex items-center text-sm text-gray-700">
|
||||
<i class="fa-solid fa-check text-green-600 mr-3"></i> Access to templates
|
||||
</li>
|
||||
<li class="flex items-center text-sm text-gray-700">
|
||||
<i class="fa-solid fa-check text-green-600 mr-3"></i> Faster queue than Hobby
|
||||
</li>
|
||||
</ul>
|
||||
<a href="/upgrade?source=pricing"
|
||||
class="w-full py-3 px-6 rounded-xl border-2 border-green-700 text-green-700 font-bold hover:bg-green-50 transition-colors text-center">
|
||||
Choose Starter
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<!-- Pro Plan -->
|
||||
<div
|
||||
class="bg-white rounded-3xl p-8 border-2 border-green-700 shadow-2xl shadow-green-900/10 flex flex-col relative">
|
||||
<div
|
||||
class="absolute top-0 left-1/2 -translate-x-1/2 -translate-y-1/2 bg-green-700 text-white text-xs font-bold px-4 py-1 rounded-full uppercase tracking-wider">
|
||||
Most Popular
|
||||
</div>
|
||||
<h3 class="text-xl font-bold text-gray-900 mb-2">Professional</h3>
|
||||
<p class="text-gray-600 text-sm mb-6">For serious WordPress plugin developers.</p>
|
||||
<div class="mb-6">
|
||||
<span id="pro-price" class="text-4xl font-bold text-gray-900">$25</span>
|
||||
<span class="price-duration text-gray-500">/mo</span>
|
||||
</div>
|
||||
<ul class="space-y-4 mb-8 flex-grow">
|
||||
<li class="flex items-center text-sm text-gray-700">
|
||||
<i class="fa-solid fa-check text-green-600 mr-3"></i> Up to 20 apps
|
||||
</li>
|
||||
<li class="flex items-center text-sm text-gray-700">
|
||||
<i class="fa-solid fa-check text-green-600 mr-3"></i> Choice of AI models
|
||||
</li>
|
||||
<li class="flex items-center text-sm text-gray-700">
|
||||
<a href="/credits" target="_blank" class="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>
|
||||
<i class="fa-solid fa-check text-green-600 mr-3"></i> 5,000,000 Monthly AI credits
|
||||
</li>
|
||||
<li class="flex items-center text-sm text-gray-700">
|
||||
<i class="fa-solid fa-check text-green-600 mr-3"></i> Access to templates
|
||||
</li>
|
||||
<li class="flex items-center text-sm text-gray-700">
|
||||
<i class="fa-solid fa-check text-green-600 mr-3"></i> Priority queue (ahead of Hobby)
|
||||
</li>
|
||||
</ul>
|
||||
<a href="/upgrade?source=pricing"
|
||||
class="w-full py-3 px-6 rounded-xl bg-green-700 text-white font-bold hover:bg-green-600 transition-all shadow-lg shadow-green-700/20 text-center">
|
||||
Get Started
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<!-- Enterprise Plan -->
|
||||
<div class="bg-white rounded-3xl p-8 border border-gray-100 shadow-xl shadow-gray-200/50 flex flex-col">
|
||||
<h3 class="text-xl font-bold text-gray-900 mb-2">Enterprise</h3>
|
||||
<p class="text-gray-600 text-sm mb-6">For teams and high-volume builders.</p>
|
||||
<div class="mb-6">
|
||||
<span id="enterprise-price" class="text-4xl font-bold text-gray-900">$75</span>
|
||||
<span class="price-duration text-gray-500">/mo</span>
|
||||
</div>
|
||||
<ul class="space-y-4 mb-8 flex-grow">
|
||||
<li class="flex items-center text-sm text-gray-700">
|
||||
<i class="fa-solid fa-check text-green-600 mr-3"></i> Unlimited apps
|
||||
</li>
|
||||
<li class="flex items-center text-sm text-gray-700">
|
||||
<i class="fa-solid fa-check text-green-600 mr-3"></i> Fastest queue priority
|
||||
</li>
|
||||
<li class="flex items-center text-sm text-gray-700">
|
||||
<a href="/credits" target="_blank" class="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>
|
||||
<i class="fa-solid fa-check text-green-600 mr-3"></i> 50,000,000 monthly AI credits
|
||||
</li>
|
||||
<li class="flex items-center text-sm text-gray-700">
|
||||
<i class="fa-solid fa-check text-green-600 mr-3"></i> Access to templates
|
||||
</li>
|
||||
</ul>
|
||||
<a href="/upgrade?source=pricing"
|
||||
class="w-full py-3 px-6 rounded-xl border-2 border-green-700 text-green-700 font-bold hover:bg-green-50 transition-colors text-center">
|
||||
Get Started
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<p class="text-center text-sm text-gray-600 mt-6">Queue priority: Enterprise first, then Professional, then
|
||||
Hobby for faster responses.</p>
|
||||
<p class="text-center text-sm text-gray-600 mt-2">
|
||||
<a href="/credits" target="_blank" class="inline-flex items-center text-green-700 hover:text-green-600" title="Learn more about AI credits">
|
||||
<i class="fa-solid fa-circle-question mr-1"></i>
|
||||
</a>
|
||||
Usage multipliers: Standard models burn 1x credits,
|
||||
Advanced burn 2x, Premium burn 3x. Example: 2,500 tokens on a 2x model consume 5,000 credits.</p>
|
||||
<p class="text-center text-sm text-gray-600 mt-2">Need more runway? Grab discounted AI energy boosts —
|
||||
biggest breaks for Enterprise.</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Cost Comparison -->
|
||||
<section class="py-20 bg-amber-50">
|
||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div class="text-center mb-16">
|
||||
<p class="text-sm font-medium text-gray-600 mb-3 uppercase tracking-wider">Value Proposition</p>
|
||||
<h2 class="text-3xl md:text-4xl font-bold mb-4 text-gray-900">Why Plugin Compass Saves You Money</h2>
|
||||
<p class="text-gray-700 max-w-3xl mx-auto">Compare our pricing to traditional development costs and see
|
||||
the massive savings.</p>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 lg:grid-cols-2 gap-12 items-center">
|
||||
<div class="bg-green-50 rounded-2xl p-8 border border-green-200">
|
||||
<h3 class="text-2xl font-bold text-gray-900 mb-6">Cost Comparison</h3>
|
||||
<div class="space-y-4">
|
||||
<div class="flex justify-between items-center p-4 bg-white rounded-lg border border-green-100">
|
||||
<span class="font-medium text-gray-900">Premium Plugin Subscriptions</span>
|
||||
<span class="font-bold text-red-600">$200-$1000/month</span>
|
||||
</div>
|
||||
<div class="flex justify-between items-center p-4 bg-white rounded-lg border border-green-100">
|
||||
<span class="font-medium text-gray-900">Custom Development (Agency)</span>
|
||||
<span class="font-bold text-red-600">$5000-$20000+</span>
|
||||
</div>
|
||||
<div
|
||||
class="flex justify-between items-center p-4 bg-green-700 text-white rounded-lg border border-green-700">
|
||||
<span class="font-medium">Plugin Compass (Business Plan)</span>
|
||||
<span class="font-bold">$29/month</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-8 text-center">
|
||||
<p class="text-sm text-gray-600 mb-4">Build unlimited custom plugins</p>
|
||||
<a href="/signup"
|
||||
class="w-full bg-green-700 text-white py-3 px-6 rounded-lg font-semibold hover:bg-green-600 transition-colors">
|
||||
Start Saving Now
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="space-y-6">
|
||||
<div class="flex items-start gap-4">
|
||||
<div
|
||||
class="w-8 h-8 rounded-full bg-green-100 flex items-center justify-center flex-shrink-0 mt-1">
|
||||
<i class="fa-solid fa-dollar-sign text-green-700"></i>
|
||||
</div>
|
||||
<div>
|
||||
<h4 class="font-semibold text-gray-900 mb-1">90% Cost Reduction</h4>
|
||||
<p class="text-gray-700">Replace multiple $50-$100/month plugins with a single $29/month
|
||||
subscription.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex items-start gap-4">
|
||||
<div
|
||||
class="w-8 h-8 rounded-full bg-green-100 flex items-center justify-center flex-shrink-0 mt-1">
|
||||
<i class="fa-solid fa-clock text-green-700"></i>
|
||||
</div>
|
||||
<div>
|
||||
<h4 class="font-semibold text-gray-900 mb-1">Faster Development</h4>
|
||||
<p class="text-gray-700">Build plugins in hours instead of weeks. Launch projects faster and
|
||||
reduce time-to-market.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex items-start gap-4">
|
||||
<div
|
||||
class="w-8 h-8 rounded-full bg-green-100 flex items-center justify-center flex-shrink-0 mt-1">
|
||||
<i class="fa-solid fa-infinity text-green-700"></i>
|
||||
</div>
|
||||
<div>
|
||||
<h4 class="font-semibold text-gray-900 mb-1">No Vendor Lock-in</h4>
|
||||
<p class="text-gray-700">Own your code completely. Export anytime and use your plugins
|
||||
forever without recurring fees.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- FAQ Section -->
|
||||
<section class="py-24 bg-white">
|
||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div class="text-center mb-16">
|
||||
<p class="text-sm font-medium text-gray-600 mb-3 uppercase tracking-wider">Support</p>
|
||||
<h2 class="text-3xl md:text-4xl font-bold mb-4 text-gray-900">Frequently Asked Questions</h2>
|
||||
<p class="text-gray-700 max-w-3xl mx-auto">Have questions about our pricing or plans? We've got answers.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="max-w-3xl mx-auto space-y-6">
|
||||
<div
|
||||
class="bg-white p-6 rounded-2xl shadow-sm border border-gray-100 hover:border-green-200 transition-colors">
|
||||
<h4 class="font-bold text-gray-900 mb-2">Can I cancel my subscription?</h4>
|
||||
<p class="text-gray-600">Yes, you can cancel at any time from your account settings. You'll keep
|
||||
access until the end of your billing period.</p>
|
||||
</div>
|
||||
<div
|
||||
class="bg-white p-6 rounded-2xl shadow-sm border border-gray-100 hover:border-green-200 transition-colors">
|
||||
<h4 class="font-bold text-gray-900 mb-2">Do you offer a free trial?</h4>
|
||||
<p class="text-gray-600">We have a generous free plan that lets you explore all the basic features
|
||||
of the platform.</p>
|
||||
</div>
|
||||
<div
|
||||
class="bg-white p-6 rounded-2xl shadow-sm border border-gray-100 hover:border-green-200 transition-colors">
|
||||
<h4 class="font-bold text-gray-900 mb-2">Can I export my code?</h4>
|
||||
<p class="text-gray-600">Absolutely! All plans allow you to export your generated WordPress plugin
|
||||
as a ZIP file.</p>
|
||||
</div>
|
||||
<div
|
||||
class="bg-white p-6 rounded-2xl shadow-sm border border-gray-100 hover:border-green-200 transition-colors">
|
||||
<h4 class="font-bold text-gray-900 mb-2">What happens if I exceed my plan limits?</h4>
|
||||
<p class="text-gray-600">You can upgrade your plan at any time. If you need additional capacity,
|
||||
contact our support team for custom solutions.</p>
|
||||
</div>
|
||||
<div
|
||||
class="bg-white p-6 rounded-2xl shadow-sm border border-gray-100 hover:border-green-200 transition-colors">
|
||||
<h4 class="font-bold text-gray-900 mb-2">Do you offer discounts for non-profits or educational
|
||||
institutions?</h4>
|
||||
<p class="text-gray-600">Yes, we offer special pricing for non-profits and educational institutions.
|
||||
Contact our sales team for details.</p>
|
||||
</div>
|
||||
<div
|
||||
class="bg-white p-6 rounded-2xl shadow-sm border border-gray-100 hover:border-green-200 transition-colors">
|
||||
<h4 class="font-bold text-gray-900 mb-2">Can I change plans later?</h4>
|
||||
<p class="text-gray-600">Yes, you can upgrade or downgrade your plan at any time from your account
|
||||
settings. Changes take effect immediately.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-12 text-center">
|
||||
<a href="/faq" class="inline-flex items-center gap-2 px-8 py-3 bg-white border-2 border-green-700 text-green-700 font-bold rounded-xl hover:bg-green-50 transition-all">
|
||||
View Full FAQs <i class="fa-solid fa-arrow-right"></i>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Final CTA -->
|
||||
<section class="py-24 bg-green-900 text-white relative overflow-hidden">
|
||||
<!-- Background Blobs -->
|
||||
<div
|
||||
class="absolute bottom-0 left-0 -translate-x-1/2 translate-y-1/2 w-96 h-96 bg-green-600/20 rounded-full mix-blend-multiply filter blur-[100px] opacity-40 animate-blob">
|
||||
</div>
|
||||
<div
|
||||
class="absolute bottom-0 right-0 translate-x-1/2 translate-y-1/4 w-96 h-96 bg-green-500/20 rounded-full mix-blend-multiply filter blur-[100px] opacity-40 animate-blob animation-delay-2000">
|
||||
</div>
|
||||
|
||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 relative z-10 text-center">
|
||||
<h2 class="text-4xl md:text-5xl font-bold mb-6">Ready to Start Saving?</h2>
|
||||
<p class="text-xl text-green-100 mb-10 max-w-3xl mx-auto">
|
||||
Choose the perfect plan and start building custom WordPress plugins with AI today. Replace expensive
|
||||
subscriptions and take control of your website's functionality.
|
||||
</p>
|
||||
|
||||
<div class="flex flex-col sm:flex-row justify-center items-center gap-4 mb-12">
|
||||
<a href="/signup"
|
||||
class="w-full sm:w-auto px-8 py-4 bg-white text-green-900 rounded-full font-bold hover:bg-green-50 transition-colors shadow-lg">
|
||||
Get Started Free
|
||||
</a>
|
||||
<a href="/features"
|
||||
class="w-full sm:w-auto px-8 py-4 border-2 border-white text-white rounded-full font-bold hover:bg-white/10 transition-colors">
|
||||
See All Features
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-8 text-left">
|
||||
<div class="flex items-start gap-4">
|
||||
<div class="w-8 h-8 rounded-full bg-white/10 flex items-center justify-center flex-shrink-0 mt-1">
|
||||
<i class="fa-solid fa-rocket text-white"></i>
|
||||
</div>
|
||||
<div>
|
||||
<h4 class="font-semibold mb-1">Launch Faster</h4>
|
||||
<p class="text-green-100">Build production-ready plugins in hours, not weeks.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex items-start gap-4">
|
||||
<div class="w-8 h-8 rounded-full bg-white/10 flex items-center justify-center flex-shrink-0 mt-1">
|
||||
<i class="fa-solid fa-shield text-white"></i>
|
||||
</div>
|
||||
<div>
|
||||
<h4 class="font-semibold mb-1">100% Secure</h4>
|
||||
<p class="text-green-100">Your code stays private and secure with enterprise-grade protection.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex items-start gap-4">
|
||||
<div class="w-8 h-8 rounded-full bg-white/10 flex items-center justify-center flex-shrink-0 mt-1">
|
||||
<i class="fa-solid fa-headset text-white"></i>
|
||||
</div>
|
||||
<div>
|
||||
<h4 class="font-semibold mb-1">Expert Support</h4>
|
||||
<p class="text-green-100">Get help from our WordPress and AI experts when you need it.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Footer -->
|
||||
<footer class="bg-white border-t border-green-200 pt-16 pb-8">
|
||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div class="grid grid-cols-2 lg:grid-cols-5 gap-12 mb-16">
|
||||
<div class="col-span-2 lg:col-span-1">
|
||||
<div class="flex items-center gap-2 mb-6">
|
||||
<img src="/assets/Plugin.png" alt="Plugin Compass" class="w-8 h-8">
|
||||
<span class="font-bold text-xl tracking-tight text-gray-800">Plugin<span
|
||||
class="text-green-700">Compass</span></span>
|
||||
</div>
|
||||
<p class="text-gray-600 text-sm leading-relaxed">
|
||||
The smart way for WordPress site owners to replace expensive plugin subscriptions with custom
|
||||
solutions. Save thousands monthly.
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<h4 class="font-bold text-gray-900 mb-6">Product</h4>
|
||||
<ul class="space-y-4 text-sm">
|
||||
<li><a href="/features" class="text-gray-600 hover:text-green-700">Features</a></li>
|
||||
<li><a href="/pricing" class="text-gray-600 hover:text-green-700">Pricing</a></li>
|
||||
<li><a href="/templates.html" class="text-gray-600 hover:text-green-700">Templates</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div>
|
||||
<h4 class="font-bold text-gray-900 mb-6">Resources</h4>
|
||||
<ul class="space-y-4 text-sm">
|
||||
<li><a href="/docs" class="text-gray-600 hover:text-green-700">Documentation</a></li>
|
||||
<li><a href="/faq" class="text-gray-600 hover:text-green-700">FAQ</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div>
|
||||
<h4 class="font-bold text-gray-900 mb-6">Legal</h4>
|
||||
<ul class="space-y-4 text-sm">
|
||||
<li><a href="/privacy.html" class="text-gray-600 hover:text-green-700">Privacy Policy</a></li>
|
||||
<li><a href="/terms" class="text-gray-600 hover:text-green-700">Terms of Service</a></li>
|
||||
<li><a href="/contact" class="text-gray-600 hover:text-green-700">Contact Us</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="col-span-2 lg:col-span-1">
|
||||
<h4 class="font-bold text-gray-900 mb-6">Stay Updated</h4>
|
||||
<p class="text-gray-600 text-sm mb-4">Get the latest updates and WordPress tips.</p>
|
||||
<form id="footer-signup-form" class="flex flex-col gap-2">
|
||||
<input type="email" name="email" placeholder="Your email" required
|
||||
class="px-4 py-2 border border-green-200 rounded-lg focus:outline-none focus:ring-2 focus:ring-green-700/20 text-sm">
|
||||
<button type="submit"
|
||||
class="bg-green-700 hover:bg-green-600 text-white px-4 py-2 rounded-lg font-medium text-sm transition-colors shadow-lg shadow-green-700/10">
|
||||
Subscribe
|
||||
</button>
|
||||
</form>
|
||||
<div id="signup-message" class="mt-2 text-xs hidden"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="border-t border-gray-100 pt-8 flex justify-center">
|
||||
<p class="text-gray-500 text-xs text-center">© 2026 Plugin Compass. All rights reserved.</p>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<script>
|
||||
// State
|
||||
let currentCurrency = 'USD';
|
||||
let currentBillingPeriod = 'monthly';
|
||||
|
||||
// Mobile menu toggle
|
||||
const mobileMenuBtn = document.getElementById('mobile-menu-btn');
|
||||
const mobileMenu = document.getElementById('mobile-menu');
|
||||
|
||||
mobileMenuBtn.addEventListener('click', (e) => {
|
||||
e.stopPropagation();
|
||||
mobileMenu.classList.toggle('hidden');
|
||||
const isOpen = !mobileMenu.classList.contains('hidden');
|
||||
mobileMenuBtn.innerHTML = isOpen ?
|
||||
'<i class="fa-solid fa-times text-xl"></i>' :
|
||||
'<i class="fa-solid fa-bars text-xl"></i>';
|
||||
});
|
||||
|
||||
// Close mobile menu when clicking outside
|
||||
document.addEventListener('click', (e) => {
|
||||
if (mobileMenuBtn && mobileMenu) {
|
||||
if (!mobileMenuBtn.contains(e.target) && !mobileMenu.contains(e.target)) {
|
||||
mobileMenu.classList.add('hidden');
|
||||
mobileMenuBtn.innerHTML = '<i class="fa-solid fa-bars text-xl"></i>';
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// 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
|
||||
};
|
||||
|
||||
function updateDisplay() {
|
||||
const config = currencyConfig[currentCurrency];
|
||||
const isYearly = currentBillingPeriod === 'yearly';
|
||||
const multiplier = isYearly ? 10 : 1;
|
||||
const duration = isYearly ? '/yr' : '/mo';
|
||||
|
||||
// Helper to format price
|
||||
const formatPrice = (basePrice) => {
|
||||
// Specific GBP overrides
|
||||
if (currentCurrency === 'GBP') {
|
||||
if (basePrice === 25) {
|
||||
return isYearly ? '£200' : '£20';
|
||||
}
|
||||
if (basePrice === 75) {
|
||||
return isYearly ? '£600' : '£60';
|
||||
}
|
||||
}
|
||||
|
||||
// Specific EUR overrides (match USD prices)
|
||||
if (currentCurrency === 'EUR') {
|
||||
if (basePrice === 0) return '€0';
|
||||
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 * multiplier;
|
||||
|
||||
// Specific rounding/formatting rules
|
||||
if (basePrice === 0) return config.symbol + '0';
|
||||
|
||||
if (currentCurrency === 'GBP' && basePrice === 7.5) {
|
||||
finalPrice = (isYearly ? 50 : 5);
|
||||
} else if (currentCurrency === 'USD' && basePrice === 7.5) {
|
||||
finalPrice = (isYearly ? 75 : 7.5);
|
||||
} else {
|
||||
finalPrice = Math.round(finalPrice * 10) / 10;
|
||||
if (finalPrice % 1 === 0) finalPrice = Math.round(finalPrice);
|
||||
}
|
||||
|
||||
// Add .00 or .50 for aesthetic consistency on certain prices
|
||||
if (currentCurrency === 'USD' && basePrice === 7.5 && !isYearly) return '$7.50';
|
||||
|
||||
return config.symbol + finalPrice;
|
||||
};
|
||||
|
||||
// Update pricing cards
|
||||
const hobbyPriceEl = document.getElementById('hobby-price');
|
||||
const starterPriceEl = document.getElementById('starter-price');
|
||||
const proPriceEl = document.getElementById('pro-price');
|
||||
const enterprisePriceEl = document.getElementById('enterprise-price');
|
||||
|
||||
if (hobbyPriceEl) hobbyPriceEl.textContent = formatPrice(baseMonthlyPrices.hobby);
|
||||
if (starterPriceEl) starterPriceEl.textContent = formatPrice(baseMonthlyPrices.starter);
|
||||
if (proPriceEl) proPriceEl.textContent = formatPrice(baseMonthlyPrices.pro);
|
||||
if (enterprisePriceEl) enterprisePriceEl.textContent = formatPrice(baseMonthlyPrices.enterprise);
|
||||
|
||||
document.querySelectorAll('.price-duration').forEach(el => el.textContent = duration);
|
||||
|
||||
// Update cost comparison card
|
||||
const costComparisonPrice = document.querySelector('.bg-green-700 .font-bold');
|
||||
if (costComparisonPrice) {
|
||||
const comparisonBase = 29;
|
||||
const finalComparison = currentCurrency === 'EUR' ? comparisonBase : Math.round(comparisonBase * config.rate);
|
||||
costComparisonPrice.textContent = config.symbol + finalComparison + '/month';
|
||||
}
|
||||
|
||||
// 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-green-700', 'text-white');
|
||||
yearlyBtn.classList.remove('text-gray-600');
|
||||
monthlyBtn.classList.remove('bg-green-700', 'text-white');
|
||||
monthlyBtn.classList.add('text-gray-600');
|
||||
} else {
|
||||
monthlyBtn.classList.add('bg-green-700', 'text-white');
|
||||
monthlyBtn.classList.remove('text-gray-600');
|
||||
yearlyBtn.classList.remove('bg-green-700', 'text-white');
|
||||
yearlyBtn.classList.add('text-gray-600');
|
||||
}
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
// Detect Location and Set Currency
|
||||
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();
|
||||
|
||||
// Ensure "2 months free" text is displayed correctly when using affiliate links
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
if (urlParams.get('aff')) {
|
||||
const yearlyBtn = document.getElementById('yearly-toggle');
|
||||
if (yearlyBtn) {
|
||||
const freeSpan = yearlyBtn.querySelector('span');
|
||||
if (freeSpan && freeSpan.textContent !== '2 months free') {
|
||||
freeSpan.textContent = '2 months free';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Email Signup Form Handler
|
||||
const signupForm = document.getElementById('footer-signup-form');
|
||||
const signupMessage = document.getElementById('signup-message');
|
||||
|
||||
if (signupForm) {
|
||||
signupForm.addEventListener('submit', async (e) => {
|
||||
e.preventDefault();
|
||||
const email = signupForm.querySelector('input[name="email"]').value;
|
||||
const button = signupForm.querySelector('button');
|
||||
|
||||
button.disabled = true;
|
||||
button.textContent = 'Subscribing...';
|
||||
|
||||
try {
|
||||
const response = await fetch('https://emailmarketing.modelrailway3d.co.uk/api/webhooks/incoming/wh_0Z49zi_DGj4-lKJMOPO8-g', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
email: email,
|
||||
source: 'plugin_compass_footer',
|
||||
timestamp: new Date().toISOString()
|
||||
})
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
signupMessage.textContent = 'Successfully subscribed!';
|
||||
signupMessage.className = 'mt-2 text-xs text-green-600';
|
||||
signupForm.reset();
|
||||
} else {
|
||||
throw new Error('Failed to subscribe');
|
||||
}
|
||||
} catch (error) {
|
||||
signupMessage.textContent = 'Failed to subscribe. Please try again.';
|
||||
signupMessage.className = 'mt-2 text-xs text-red-600';
|
||||
} finally {
|
||||
signupMessage.classList.remove('hidden');
|
||||
button.disabled = false;
|
||||
button.textContent = 'Subscribe';
|
||||
|
||||
setTimeout(() => {
|
||||
signupMessage.classList.add('hidden');
|
||||
}, 5000);
|
||||
}
|
||||
});
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
402
chat/public/privacy.html
Normal file
@@ -0,0 +1,402 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" class="scroll-smooth">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Privacy Policy | Plugin Compass</title>
|
||||
<link rel="icon" type="image/png" href="/assets/Plugin.png">
|
||||
<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">
|
||||
<style>
|
||||
body {
|
||||
font-family: 'Inter', system-ui, -apple-system, sans-serif;
|
||||
}
|
||||
|
||||
.hero-gradient {
|
||||
background: linear-gradient(135deg, #004225, #006b3d);
|
||||
}
|
||||
|
||||
.glass-nav {
|
||||
background: rgba(251, 246, 239, 0.9);
|
||||
backdrop-filter: blur(12px);
|
||||
-webkit-backdrop-filter: blur(12px);
|
||||
border-bottom: 1px solid rgba(0, 66, 37, 0.1);
|
||||
}
|
||||
</style>
|
||||
<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=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
|
||||
|
||||
<!-- PostHog Analytics -->
|
||||
<script src="/posthog.js"></script>
|
||||
</head>
|
||||
|
||||
<body class="bg-amber-50 text-gray-900 antialiased">
|
||||
|
||||
<!-- Navigation -->
|
||||
<nav class="fixed w-full z-50 glass-nav transition-all duration-300" id="navbar">
|
||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div class="flex justify-between items-center h-20">
|
||||
<!-- Logo -->
|
||||
<a href="/" class="flex-shrink-0 flex items-center gap-2 cursor-pointer">
|
||||
<img src="/assets/Plugin.png" alt="Plugin Compass" class="w-8 h-8">
|
||||
<span class="font-bold text-xl tracking-tight text-gray-800">Plugin<span
|
||||
class="text-green-700">Compass</span></span>
|
||||
</a>
|
||||
|
||||
<!-- Desktop Menu -->
|
||||
<div class="hidden md:flex items-center space-x-8">
|
||||
<a href="/features"
|
||||
class="text-gray-700 hover:text-gray-900 transition-colors text-sm font-medium">Features</a>
|
||||
<a href="/pricing"
|
||||
class="text-gray-700 hover:text-gray-900 transition-colors text-sm font-medium">Pricing</a>
|
||||
<a href="/docs"
|
||||
class="text-gray-700 hover:text-gray-900 transition-colors text-sm font-medium">Docs</a>
|
||||
<a href="/faq"
|
||||
class="text-gray-700 hover:text-gray-900 transition-colors text-sm font-medium">FAQ</a>
|
||||
</div>
|
||||
|
||||
<!-- CTA Buttons -->
|
||||
<div class="hidden md:flex items-center gap-4">
|
||||
<a href="/login"
|
||||
class="text-gray-700 hover:text-gray-900 font-medium text-sm transition-colors">Sign In</a>
|
||||
<a href="/signup"
|
||||
class="bg-green-700 hover:bg-green-600 text-white px-5 py-2.5 rounded-full font-medium text-sm transition-all shadow-lg shadow-green-700/20 hover:shadow-green-700/40 transform hover:-translate-y-0.5">
|
||||
Get Started
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<!-- Mobile Menu Button -->
|
||||
<div class="md:hidden flex items-center">
|
||||
<button id="mobile-menu-btn" class="text-gray-700 hover:text-gray-900 focus:outline-none">
|
||||
<i class="fa-solid fa-bars text-xl"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Mobile Menu Panel -->
|
||||
<div id="mobile-menu" class="hidden md:hidden bg-amber-50 border-b border-amber-200">
|
||||
<div class="px-4 pt-2 pb-6 space-y-1">
|
||||
<a href="/features"
|
||||
class="block px-3 py-3 text-base font-medium text-gray-700 hover:text-gray-900 hover:bg-amber-100 rounded-md">Features</a>
|
||||
<a href="/pricing"
|
||||
class="block px-3 py-3 text-base font-medium text-gray-700 hover:text-gray-900 hover:bg-amber-100 rounded-md">Pricing</a>
|
||||
<a href="/docs"
|
||||
class="block px-3 py-3 text-base font-medium text-gray-700 hover:text-gray-900 hover:bg-amber-100 rounded-md">Docs</a>
|
||||
<a href="/faq"
|
||||
class="block px-3 py-3 text-base font-medium text-gray-700 hover:text-gray-900 hover:bg-amber-100 rounded-md">FAQ</a>
|
||||
<div class="pt-4 flex flex-col gap-3">
|
||||
<a href="/login" class="w-full text-center py-2 text-gray-700 font-medium">Sign In</a>
|
||||
<a href="/signup" class="w-full bg-green-700 text-white text-center py-3 rounded-lg font-medium">Get
|
||||
Started</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<header class="hero-gradient text-white pt-32 pb-12">
|
||||
<div class="max-w-6xl mx-auto px-4">
|
||||
<h1 class="text-5xl font-bold mb-4">Privacy Policy</h1>
|
||||
<p class="text-xl text-green-50 max-w-3xl">Your privacy is important to us. This policy explains how we
|
||||
collect, use, and protect your information when using our AI-powered Wordpress Plugin Builder.</p>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main class="max-w-6xl mx-auto px-4 py-16">
|
||||
<div class="bg-white rounded-2xl shadow-sm border border-gray-100 p-8 md:p-12 space-y-8">
|
||||
|
||||
<section>
|
||||
<h2 class="text-2xl font-bold text-gray-900 mb-4">Introduction</h2>
|
||||
<p class="text-gray-700 leading-relaxed">
|
||||
Plugin Compass is committed to protecting the privacy of our users. This Privacy Policy describes
|
||||
how we collect, use, and disclose information about you when you access or use our AI-powered
|
||||
Shopify app building platform, services, and applications.
|
||||
</p>
|
||||
<p class="text-gray-700 leading-relaxed mt-3">
|
||||
By using Plugin Compass, you consent to the collection, use, and disclosure of your information as
|
||||
described in this Privacy Policy.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2 class="text-2xl font-bold text-gray-900 mb-4">Information We Collect</h2>
|
||||
|
||||
<h3 class="text-xl font-semibold text-gray-900 mb-3">Information You Provide</h3>
|
||||
<ul class="list-disc list-inside space-y-2 text-gray-700">
|
||||
<li><strong>Account Information:</strong> When you create an account, we collect your email address,
|
||||
password (hashed), and any other information you choose to provide in your profile.</li>
|
||||
<li><strong>Payment Information:</strong> When you subscribe to a paid plan, our payment processor
|
||||
collects your payment method details. We do not store full payment card numbers.</li>
|
||||
<li><strong>WordPress Plugin Information:</strong> When you build Shopify apps using our AI
|
||||
platform, we store the details and code you provide or generate through our service.</li>
|
||||
<li><strong>Communications:</strong> When you contact us for support or other inquiries, we collect
|
||||
the content of your communications.</li>
|
||||
</ul>
|
||||
|
||||
<h3 class="text-xl font-semibold text-gray-900 mb-3 mt-6">Information We Collect Automatically</h3>
|
||||
<ul class="list-disc list-inside space-y-2 text-gray-700">
|
||||
<li><strong>Usage Data:</strong> We collect information about how you use our services, including
|
||||
app builds, features accessed, and session duration.</li>
|
||||
<li><strong>Device Information:</strong> We collect information about the devices you use to access
|
||||
our services, including browser type, IP address, and operating system.</li>
|
||||
<li><strong>Cookies:</strong> We use cookies and similar technologies to enhance your experience and
|
||||
analyze usage. See our Cookie Policy below for details.</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2 class="text-2xl font-bold text-gray-900 mb-4">How We Use Your Information</h2>
|
||||
<ul class="list-disc list-inside space-y-2 text-gray-700">
|
||||
<li>To provide, maintain, and improve our services</li>
|
||||
<li>To process your transactions and send you related information</li>
|
||||
<li>To send you technical notices, updates, security alerts, and support messages</li>
|
||||
<li>To respond to your comments, questions, and requests</li>
|
||||
<li>To monitor and analyze trends, usage, and activities in connection with our services</li>
|
||||
<li>To detect, investigate, and prevent fraudulent transactions and other illegal activities</li>
|
||||
<li>To comply with legal obligations and enforce our Terms of Service</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2 class="text-2xl font-bold text-gray-900 mb-4">How We Share Your Information</h2>
|
||||
|
||||
<h3 class="text-xl font-semibold text-gray-900 mb-3">Third-Party Service Providers</h3>
|
||||
<p class="text-gray-700 leading-relaxed">
|
||||
We may share your information with third-party service providers who perform services on our behalf,
|
||||
such as payment processing, data analysis, email delivery, hosting services, and customer service.
|
||||
These providers are obligated to protect your information and only use it as necessary to provide
|
||||
services to us.
|
||||
</p>
|
||||
|
||||
<h3 class="text-xl font-semibold text-gray-900 mb-3 mt-4">AI Model Providers</h3>
|
||||
<p class="text-gray-700 leading-relaxed">
|
||||
When you use our AI features, your prompts and generated code may be sent to third-party AI model
|
||||
providers (such as OpenRouter or Anthropic) to generate responses. These providers have their own
|
||||
privacy policies regarding how they handle your data.
|
||||
</p>
|
||||
|
||||
<h3 class="text-xl font-semibold text-gray-900 mb-3 mt-4">Legal Compliance</h3>
|
||||
<p class="text-gray-700 leading-relaxed">
|
||||
We may disclose your information if required to do so by law or in the good faith belief that such
|
||||
action is necessary to comply with legal obligations, protect and defend our rights or property, or
|
||||
protect the personal safety of users or the public.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2 class="text-2xl font-bold text-gray-900 mb-4">Data Retention</h2>
|
||||
<p class="text-gray-700 leading-relaxed">
|
||||
We retain your information for as long as your account is active or as needed to provide you
|
||||
services. We may also retain and use your information as necessary to comply with our legal
|
||||
obligations, resolve disputes, and enforce our agreements.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2 class="text-2xl font-bold text-gray-900 mb-4">Your Rights and Choices</h2>
|
||||
<ul class="list-disc list-inside space-y-2 text-gray-700">
|
||||
<li><strong>Access and Update:</strong> You can access and update your account information through
|
||||
your account settings.</li>
|
||||
<li><strong>Data Export:</strong> You can export your WordPress plugins and apps at any time through
|
||||
our dashboard.</li>
|
||||
<li><strong>Account Deletion:</strong> You can delete your account by contacting our support team.
|
||||
Please note that some information may remain in our backups for a limited time.</li>
|
||||
<li><strong>Cookies:</strong> Most web browsers are set to accept cookies by default. You can modify
|
||||
your browser settings to decline cookies if you prefer.</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2 class="text-2xl font-bold text-gray-900 mb-4">Data Security</h2>
|
||||
<p class="text-gray-700 leading-relaxed">
|
||||
We implement reasonable security measures to protect your information from unauthorized access,
|
||||
alteration, disclosure, or destruction. However, no internet or email transmission is ever fully
|
||||
secure or error-free, so please keep this in mind when disclosing any information to us.
|
||||
</p>
|
||||
<p class="text-gray-700 leading-relaxed mt-3">
|
||||
<strong>AI Code Disclaimer:</strong> AI-generated code may contain errors, security vulnerabilities,
|
||||
or inefficiencies. You are solely responsible for reviewing, testing, and validating all generated
|
||||
code before using it in production.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2 class="text-2xl font-bold text-gray-900 mb-4">Children's Privacy</h2>
|
||||
<p class="text-gray-700 leading-relaxed">
|
||||
Our services are not directed to children under 13 (or other age as required by local law). We do
|
||||
not knowingly collect personal information from children. If you become aware that a child has
|
||||
provided us with personal information, please contact us.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2 class="text-2xl font-bold text-gray-900 mb-4">Changes to This Privacy Policy</h2>
|
||||
<p class="text-gray-700 leading-relaxed">
|
||||
We may update this Privacy Policy from time to time. If we make material changes, we will notify you
|
||||
through our website or by email. Your continued use of our services after any changes take effect
|
||||
constitutes your acceptance of the revised Privacy Policy.
|
||||
</p>
|
||||
<p class="text-sm text-gray-600 mt-2">
|
||||
Last updated: January 1, 2026
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2 class="text-2xl font-bold text-gray-900 mb-4">Contact Us</h2>
|
||||
<p class="text-gray-700 leading-relaxed">
|
||||
If you have questions or concerns about this Privacy Policy, please contact us at:
|
||||
</p>
|
||||
<div class="mt-4 space-y-1 text-gray-700">
|
||||
<p><strong>Email:</strong> info@plugincompas.com</p>
|
||||
</section>
|
||||
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<!-- CTA Footer -->
|
||||
<section class="py-24 bg-amber-50">
|
||||
<div class="max-w-5xl mx-auto px-4 sm:px-6 lg:px-8 text-center">
|
||||
<h2 class="text-4xl font-bold mb-8 text-gray-900">Build Your Custom Plugin Today</h2>
|
||||
<p class="text-xl text-gray-700 mb-10">Start building WordPress plugins that fit your exact needs. No coding
|
||||
experience required.</p>
|
||||
<a href="/signup"
|
||||
class="inline-flex items-center justify-center px-8 py-4 bg-green-700 text-white rounded-full font-bold hover:bg-green-600 transition-all shadow-xl shadow-green-700/20">
|
||||
Get Started Free <i class="fa-solid fa-arrow-right ml-2"></i>
|
||||
</a>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Footer -->
|
||||
<footer class="bg-white border-t border-green-200 pt-16 pb-8">
|
||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div class="grid grid-cols-2 lg:grid-cols-5 gap-12 mb-16">
|
||||
<div class="col-span-2 lg:col-span-1">
|
||||
<div class="flex items-center gap-2 mb-6">
|
||||
<img src="/assets/Plugin.png" alt="Plugin Compass" class="w-8 h-8">
|
||||
<span class="font-bold text-xl tracking-tight text-gray-800">Plugin<span
|
||||
class="text-green-700">Compass</span></span>
|
||||
</div>
|
||||
<p class="text-gray-600 text-sm leading-relaxed">
|
||||
The smart way for WordPress site owners to replace expensive plugin subscriptions with custom
|
||||
solutions. Save thousands monthly.
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<h4 class="font-bold text-gray-900 mb-6">Product</h4>
|
||||
<ul class="space-y-4 text-sm">
|
||||
<li><a href="/features" class="text-gray-600 hover:text-green-700">Features</a></li>
|
||||
<li><a href="/pricing" class="text-gray-600 hover:text-green-700">Pricing</a></li>
|
||||
<li><a href="/templates.html" class="text-gray-600 hover:text-green-700">Templates</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div>
|
||||
<h4 class="font-bold text-gray-900 mb-6">Resources</h4>
|
||||
<ul class="space-y-4 text-sm">
|
||||
<li><a href="/docs" class="text-gray-600 hover:text-green-700">Documentation</a></li>
|
||||
<li><a href="/faq" class="text-gray-600 hover:text-green-700">FAQ</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div>
|
||||
<h4 class="font-bold text-gray-900 mb-6">Legal</h4>
|
||||
<ul class="space-y-4 text-sm">
|
||||
<li><a href="/privacy.html" class="text-gray-600 hover:text-green-700">Privacy Policy</a></li>
|
||||
<li><a href="/terms" class="text-gray-600 hover:text-green-700">Terms of Service</a></li>
|
||||
<li><a href="/contact" class="text-gray-600 hover:text-green-700">Contact Us</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="col-span-2 lg:col-span-1">
|
||||
<h4 class="font-bold text-gray-900 mb-6">Stay Updated</h4>
|
||||
<p class="text-gray-600 text-sm mb-4">Get the latest updates and WordPress tips.</p>
|
||||
<form id="footer-signup-form" class="flex flex-col gap-2">
|
||||
<input type="email" name="email" placeholder="Your email" required
|
||||
class="px-4 py-2 border border-green-200 rounded-lg focus:outline-none focus:ring-2 focus:ring-green-700/20 text-sm">
|
||||
<button type="submit"
|
||||
class="bg-green-700 hover:bg-green-600 text-white px-4 py-2 rounded-lg font-medium text-sm transition-colors shadow-lg shadow-green-700/10">
|
||||
Subscribe
|
||||
</button>
|
||||
</form>
|
||||
<div id="signup-message" class="mt-2 text-xs hidden"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="border-t border-gray-100 pt-8 flex justify-center">
|
||||
<p class="text-gray-500 text-xs text-center">© 2026 Plugin Compass. All rights reserved.</p>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<script>
|
||||
const mobileMenuBtn = document.getElementById('mobile-menu-btn');
|
||||
const mobileMenu = document.getElementById('mobile-menu');
|
||||
|
||||
if (mobileMenuBtn && mobileMenu) {
|
||||
mobileMenuBtn.addEventListener('click', () => {
|
||||
mobileMenu.classList.toggle('hidden');
|
||||
});
|
||||
}
|
||||
|
||||
// Navbar scroll effect
|
||||
window.addEventListener('scroll', () => {
|
||||
const navbar = document.getElementById('navbar');
|
||||
if (!navbar) return;
|
||||
if (window.scrollY > 20) {
|
||||
navbar.classList.add('shadow-md', 'h-16');
|
||||
navbar.classList.remove('h-20');
|
||||
} else {
|
||||
navbar.classList.remove('shadow-md', 'h-16');
|
||||
navbar.classList.add('h-20');
|
||||
}
|
||||
});
|
||||
|
||||
// Email Signup Form Handler
|
||||
const signupForm = document.getElementById('footer-signup-form');
|
||||
const signupMessage = document.getElementById('signup-message');
|
||||
|
||||
if (signupForm) {
|
||||
signupForm.addEventListener('submit', async (e) => {
|
||||
e.preventDefault();
|
||||
const email = signupForm.querySelector('input[name="email"]').value;
|
||||
const button = signupForm.querySelector('button');
|
||||
|
||||
button.disabled = true;
|
||||
button.textContent = 'Subscribing...';
|
||||
|
||||
try {
|
||||
const response = await fetch('https://emailmarketing.modelrailway3d.co.uk/api/webhooks/incoming/wh_0Z49zi_DGj4-lKJMOPO8-g', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
email: email,
|
||||
source: 'plugin_compass_footer',
|
||||
timestamp: new Date().toISOString()
|
||||
})
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
signupMessage.textContent = 'Successfully subscribed!';
|
||||
signupMessage.className = 'mt-2 text-xs text-green-600';
|
||||
signupForm.reset();
|
||||
} else {
|
||||
throw new Error('Failed to subscribe');
|
||||
}
|
||||
} catch (error) {
|
||||
signupMessage.textContent = 'Failed to subscribe. Please try again.';
|
||||
signupMessage.className = 'mt-2 text-xs text-red-600';
|
||||
} finally {
|
||||
signupMessage.classList.remove('hidden');
|
||||
button.disabled = false;
|
||||
button.textContent = 'Subscribe';
|
||||
|
||||
setTimeout(() => {
|
||||
signupMessage.classList.add('hidden');
|
||||
}, 5000);
|
||||
}
|
||||
});
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
185
chat/public/reset-password.html
Normal file
@@ -0,0 +1,185 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Reset Password | Plugin Compass</title>
|
||||
<link rel="icon" type="image/png" href="/assets/Plugin.png">
|
||||
<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">
|
||||
|
||||
<!-- PostHog Analytics -->
|
||||
<script src="/posthog.js"></script>
|
||||
</head>
|
||||
<body class="bg-amber-50 text-gray-900 font-sans antialiased min-h-screen flex flex-col">
|
||||
<nav class="w-full">
|
||||
<div class="max-w-4xl mx-auto px-4 py-6 flex items-center justify-between">
|
||||
<a href="/" class="flex items-center gap-2 font-bold text-lg text-gray-800">
|
||||
<img src="/assets/Plugin.png" alt="Plugin Compass" class="w-8 h-8">
|
||||
Plugin Compass
|
||||
</a>
|
||||
<a href="/login" class="text-sm text-green-700 hover:underline font-semibold">Back to sign in</a>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<main class="flex-grow flex items-center justify-center px-4 py-12">
|
||||
<div class="max-w-xl w-full bg-white/80 border border-gray-200 rounded-2xl shadow-xl shadow-green-900/5 p-8">
|
||||
<div class="text-center mb-8">
|
||||
<div class="w-14 h-14 rounded-full bg-green-700 text-white flex items-center justify-center mx-auto mb-4">
|
||||
<i class="fa-solid fa-key text-2xl"></i>
|
||||
</div>
|
||||
<h1 class="text-2xl font-bold mb-2">Reset your password</h1>
|
||||
<p class="text-gray-600" id="subtitle">Enter your email to receive a reset link.</p>
|
||||
</div>
|
||||
|
||||
<div id="request-section" class="space-y-4">
|
||||
<label class="block text-sm font-medium text-gray-700 mb-1" for="reset-email">Email Address</label>
|
||||
<input id="reset-email" type="email" class="w-full px-4 py-3 rounded-xl border border-gray-200 focus:ring-2 focus:ring-green-700/20 focus:border-green-700 outline-none transition-all bg-white/50" placeholder="you@example.com">
|
||||
<button id="request-button" class="w-full bg-green-700 hover:bg-green-600 text-white font-bold py-3 rounded-xl transition-all">Send reset link</button>
|
||||
</div>
|
||||
|
||||
<div id="reset-section" class="space-y-4 hidden">
|
||||
<label class="block text-sm font-medium text-gray-700 mb-1" for="new-password">New password</label>
|
||||
<input id="new-password" type="password" class="w-full px-4 py-3 rounded-xl border border-gray-200 focus:ring-2 focus:ring-green-700/20 focus:border-green-700 outline-none transition-all bg-white/50" placeholder="••••••••">
|
||||
<label class="block text-sm font-medium text-gray-700 mb-1" for="confirm-password">Confirm password</label>
|
||||
<input id="confirm-password" type="password" class="w-full px-4 py-3 rounded-xl border border-gray-200 focus:ring-2 focus:ring-green-700/20 focus:border-green-700 outline-none transition-all bg-white/50" placeholder="••••••••">
|
||||
<button id="reset-button" class="w-full bg-green-700 hover:bg-green-600 text-white font-bold py-3 rounded-xl transition-all">Update password</button>
|
||||
</div>
|
||||
|
||||
<div id="reset-status" class="mt-4 text-sm text-gray-700 bg-amber-50 border border-amber-100 rounded-lg px-4 py-3 min-h-[48px]"></div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<footer class="bg-white border-t border-green-200 pt-16 pb-8">
|
||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div class="grid grid-cols-2 md:grid-cols-4 gap-12 mb-16">
|
||||
<div class="col-span-2 md:col-span-1">
|
||||
<div class="flex items-center gap-2 mb-6">
|
||||
<img src="/assets/Plugin.png" alt="Plugin Compass" class="w-8 h-8">
|
||||
<span class="font-bold text-xl tracking-tight text-gray-800">Plugin<span
|
||||
class="text-green-700">Compass</span></span>
|
||||
</div>
|
||||
<p class="text-gray-600 text-sm leading-relaxed">
|
||||
The smart way for WordPress site owners to replace expensive plugin subscriptions with custom
|
||||
solutions. Save thousands monthly.
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<h4 class="font-bold text-gray-900 mb-6">Product</h4>
|
||||
<ul class="space-y-4 text-sm">
|
||||
<li><a href="/features" class="text-gray-600 hover:text-green-700">Features</a></li>
|
||||
<li><a href="/pricing" class="text-gray-600 hover:text-green-700">Pricing</a></li>
|
||||
<li><a href="#" class="text-gray-600 hover:text-green-700">Templates</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div>
|
||||
<h4 class="font-bold text-gray-900 mb-6">Resources</h4>
|
||||
<ul class="space-y-4 text-sm">
|
||||
<li><a href="/docs" class="text-gray-600 hover:text-green-700">Documentation</a></li>
|
||||
<li><a href="/faq" class="text-gray-600 hover:text-green-700">FAQ</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div>
|
||||
<h4 class="font-bold text-gray-900 mb-6">Legal</h4>
|
||||
<ul class="space-y-4 text-sm">
|
||||
<li><a href="/privacy.html" class="text-gray-600 hover:text-green-700">Privacy Policy</a></li>
|
||||
<li><a href="/terms" class="text-gray-600 hover:text-green-700">Terms of Service</a></li>
|
||||
<li><a href="/contact" class="text-gray-600 hover:text-green-700">Contact Us</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="border-t border-gray-100 pt-8 flex justify-center">
|
||||
<p class="text-gray-500 text-xs text-center">© 2026 Plugin Compass. All rights reserved.</p>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<script>
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
const token = params.get('token');
|
||||
const requestSection = document.getElementById('request-section');
|
||||
const resetSection = document.getElementById('reset-section');
|
||||
const statusEl = document.getElementById('reset-status');
|
||||
const subtitleEl = document.getElementById('subtitle');
|
||||
const REDIRECT_DELAY_MS = 1200;
|
||||
const redirectTarget = params.get('next') || '/apps';
|
||||
|
||||
function setStatus(message, isError = false) {
|
||||
statusEl.textContent = message || '';
|
||||
statusEl.classList.toggle('text-red-700', isError);
|
||||
statusEl.classList.toggle('border-red-200', isError);
|
||||
statusEl.classList.toggle('bg-red-50', isError);
|
||||
statusEl.classList.toggle('text-green-700', !isError);
|
||||
statusEl.classList.toggle('border-green-100', !isError);
|
||||
statusEl.classList.toggle('bg-green-50', !isError);
|
||||
}
|
||||
|
||||
async function requestReset() {
|
||||
const email = (document.getElementById('reset-email').value || '').trim().toLowerCase();
|
||||
if (!email) {
|
||||
setStatus('Email is required', true);
|
||||
return;
|
||||
}
|
||||
setStatus('Sending reset link...', false);
|
||||
try {
|
||||
const resp = await fetch('/api/password/forgot', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ email })
|
||||
});
|
||||
const data = await resp.json().catch(() => ({}));
|
||||
if (!resp.ok) {
|
||||
setStatus(data.error || 'Unable to send reset email. Please try again.', true);
|
||||
return;
|
||||
}
|
||||
setStatus(data.message || 'If an account exists, a reset link has been sent.', false);
|
||||
} catch (_) {
|
||||
setStatus('Unable to send reset email. Please try again.', true);
|
||||
}
|
||||
}
|
||||
|
||||
async function submitReset() {
|
||||
const newPassword = (document.getElementById('new-password').value || '').trim();
|
||||
const confirmPassword = (document.getElementById('confirm-password').value || '').trim();
|
||||
if (!newPassword || newPassword.length < 6) {
|
||||
setStatus('Password must be at least 6 characters.', true);
|
||||
return;
|
||||
}
|
||||
if (newPassword !== confirmPassword) {
|
||||
setStatus('Passwords do not match.', true);
|
||||
return;
|
||||
}
|
||||
setStatus('Updating your password...', false);
|
||||
try {
|
||||
const resp = await fetch('/api/password/reset', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ token, password: newPassword })
|
||||
});
|
||||
const data = await resp.json().catch(() => ({}));
|
||||
if (!resp.ok) {
|
||||
setStatus(data.error || 'Unable to reset password. Please try again.', true);
|
||||
return;
|
||||
}
|
||||
setStatus('Password updated. Redirecting you to the app...', false);
|
||||
setTimeout(() => {
|
||||
window.location.href = redirectTarget;
|
||||
}, REDIRECT_DELAY_MS);
|
||||
} catch (_) {
|
||||
setStatus('Unable to reset password. Please try again.', true);
|
||||
}
|
||||
}
|
||||
|
||||
document.getElementById('request-button').addEventListener('click', requestReset);
|
||||
document.getElementById('reset-button').addEventListener('click', submitReset);
|
||||
|
||||
if (token) {
|
||||
requestSection.classList.add('hidden');
|
||||
resetSection.classList.remove('hidden');
|
||||
subtitleEl.textContent = 'Enter a new password to finish resetting your account.';
|
||||
} else {
|
||||
setStatus('Enter your email to receive a reset link.', false);
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
15
chat/public/robots.txt
Normal file
@@ -0,0 +1,15 @@
|
||||
# robots.txt for Shopify AI App Builder
|
||||
# Allow search engines to crawl public content
|
||||
User-agent: *
|
||||
Allow: /
|
||||
|
||||
# Disallow crawling of admin and authenticated areas
|
||||
Disallow: /admin
|
||||
Disallow: /apps
|
||||
Disallow: /builder
|
||||
Disallow: /settings
|
||||
Disallow: /affiliate-dashboard
|
||||
Disallow: /api/
|
||||
|
||||
# Sitemap location
|
||||
Sitemap: https://your-domain.com/sitemap.xml
|
||||
895
chat/public/select-plan.html
Normal file
@@ -0,0 +1,895 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Select Your Plan | Plugin Compass</title>
|
||||
<link rel="icon" type="image/png" href="/assets/Plugin.png">
|
||||
<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">
|
||||
<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=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
|
||||
|
||||
<script>
|
||||
tailwind.config = {
|
||||
theme: {
|
||||
extend: {
|
||||
fontFamily: {
|
||||
sans: ['Inter', 'sans-serif']
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
<style>
|
||||
.glass-panel {
|
||||
background: rgba(255, 255, 255, 0.9);
|
||||
backdrop-filter: blur(12px);
|
||||
-webkit-backdrop-filter: blur(12px);
|
||||
border: 1px solid rgba(0, 66, 37, 0.1);
|
||||
}
|
||||
|
||||
.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;
|
||||
line-height: 1;
|
||||
cursor: pointer;
|
||||
color: #64748b;
|
||||
padding: 0;
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 8px;
|
||||
transition: all 0.15s ease;
|
||||
}
|
||||
|
||||
.payment-modal-close:hover {
|
||||
background: rgba(0, 0, 0, 0.05);
|
||||
color: #0f172a;
|
||||
}
|
||||
|
||||
/* 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; /* allow internal scrolling when needed */
|
||||
min-height: 60vh;
|
||||
background: #f8fafc;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.payment-frame-container iframe {
|
||||
display: block;
|
||||
width: 100%;
|
||||
/* Fill the modal viewport while reserving space for the header (approx 72px)
|
||||
using calc keeps the iframe from overflowing the modal and avoids clipping */
|
||||
height: calc(96vh - 72px);
|
||||
max-height: calc(96vh - 72px);
|
||||
border: none;
|
||||
}
|
||||
</style>
|
||||
|
||||
<!-- PostHog Analytics -->
|
||||
<script src="/posthog.js"></script>
|
||||
|
||||
</head>
|
||||
|
||||
<body class="bg-amber-50 text-gray-900 font-sans antialiased min-h-screen flex flex-col">
|
||||
|
||||
<!-- Navigation -->
|
||||
<nav class="w-full z-50 transition-all duration-300">
|
||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div class="flex justify-between items-center h-20">
|
||||
<!-- Logo -->
|
||||
<a href="/" class="flex-shrink-0 flex items-center gap-2 cursor-pointer">
|
||||
<img src="/assets/Plugin.png" alt="Plugin Compass" class="w-8 h-8">
|
||||
<span class="font-bold text-xl tracking-tight text-gray-800">Plugin<span
|
||||
class="text-green-700">Compass</span></span>
|
||||
</a>
|
||||
<div class="text-sm text-gray-600">
|
||||
Welcome to Plugin Compass
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<main class="flex-grow flex items-center justify-center px-4 py-12">
|
||||
<div class="w-full max-w-5xl">
|
||||
<div class="text-center mb-12">
|
||||
<h1 class="text-4xl font-bold text-gray-900 mb-4">Choose your plan</h1>
|
||||
<p class="text-xl text-gray-700">Start building your WordPress plugin today.</p>
|
||||
<p class="text-sm text-gray-500 mt-2">You can always upgrade or downgrade later from your account
|
||||
settings.</p>
|
||||
</div>
|
||||
|
||||
<!-- Billing Cycle & Currency Selection -->
|
||||
<div class="mb-8 flex flex-col sm:flex-row items-center justify-center gap-4">
|
||||
<div class="inline-flex items-center bg-white p-1 rounded-full border border-gray-200">
|
||||
<button id="monthly-toggle"
|
||||
class="px-6 py-2 rounded-full text-sm font-medium bg-green-700 text-white shadow-sm transition-all">Monthly</button>
|
||||
<button id="yearly-toggle"
|
||||
class="px-6 py-2 rounded-full text-sm font-medium text-gray-600 hover:text-gray-900 transition-all">Yearly
|
||||
<span class="text-green-600 font-bold">2 months free</span></button>
|
||||
</div>
|
||||
|
||||
<div class="relative currency-dropdown" 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>
|
||||
</button>
|
||||
<div id="currency-options" class="hidden absolute top-full left-0 mt-1 w-full bg-white border border-gray-200 rounded-xl shadow-lg z-50 overflow-hidden">
|
||||
<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" 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" 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" data-value="EUR">
|
||||
🇪🇺 €<span class="ml-1">EUR</span>
|
||||
</button>
|
||||
</div>
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 w-full">
|
||||
<!-- Hobby Plan -->
|
||||
<div class="plan-card bg-white rounded-3xl p-8 border border-gray-100 shadow-xl shadow-gray-200/50 flex flex-col cursor-pointer transition-all hover:shadow-2xl hover:-translate-y-1"
|
||||
data-plan="hobby">
|
||||
<h3 class="text-xl font-bold text-gray-900 mb-2">Hobby</h3>
|
||||
<p class="text-gray-600 text-sm mb-6">For personal projects and exploration.</p>
|
||||
<div class="mb-6">
|
||||
<span id="hobby-price" class="text-4xl font-bold text-gray-900">$0</span>
|
||||
<span id="hobby-period" class="price-duration text-gray-500">/mo</span>
|
||||
</div>
|
||||
<ul class="space-y-4 mb-8 flex-grow">
|
||||
<li class="flex items-center text-sm text-gray-700">
|
||||
<i class="fa-solid fa-check text-green-600 mr-3"></i> Up to 3 apps
|
||||
</li>
|
||||
<li class="flex items-center text-sm text-gray-700">
|
||||
<i class="fa-solid fa-check text-green-600 mr-3"></i> No choice of ai models
|
||||
</li>
|
||||
<li class="flex items-center text-sm text-gray-700">
|
||||
<a href="/credits" target="_blank" class="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>
|
||||
<i class="fa-solid fa-check text-green-600 mr-3"></i> 50,000 monthly AI credits
|
||||
</li>
|
||||
<li class="flex items-center text-sm text-gray-700">
|
||||
<i class="fa-solid fa-check text-green-600 mr-3"></i> Standard queue
|
||||
</li>
|
||||
</ul>
|
||||
<button type="button"
|
||||
class="plan-btn w-full py-3 px-6 rounded-xl border-2 border-green-700 text-green-700 font-bold hover:bg-green-50 transition-colors text-center"
|
||||
data-plan="hobby">
|
||||
Start for Free
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Starter Plan -->
|
||||
<div class="plan-card bg-white rounded-3xl p-8 border border-gray-100 shadow-xl shadow-gray-200/50 flex flex-col cursor-pointer transition-all hover:shadow-2xl hover:-translate-y-1"
|
||||
data-plan="starter">
|
||||
<h3 class="text-xl font-bold text-gray-900 mb-2">Starter</h3>
|
||||
<p class="text-gray-600 text-sm mb-6">Great for small business needs.</p>
|
||||
<div class="mb-6">
|
||||
<span id="starter-price" class="text-4xl font-bold text-gray-900">$7.50</span>
|
||||
<span id="starter-period" class="price-duration text-gray-500">/mo</span>
|
||||
</div>
|
||||
<ul class="space-y-4 mb-8 flex-grow">
|
||||
<li class="flex items-center text-sm text-gray-700">
|
||||
<i class="fa-solid fa-check text-green-600 mr-3"></i> Up to 10 apps
|
||||
</li>
|
||||
<li class="flex items-center text-sm text-gray-700">
|
||||
<a href="/credits" target="_blank" class="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>
|
||||
<i class="fa-solid fa-check text-green-600 mr-3"></i> 100,000 monthly AI credits
|
||||
</li>
|
||||
<li class="flex items-center text-sm text-gray-700">
|
||||
<i class="fa-solid fa-check text-green-600 mr-3"></i> Access to templates
|
||||
</li>
|
||||
<li class="flex items-center text-sm text-gray-700">
|
||||
<i class="fa-solid fa-check text-green-600 mr-3"></i> Faster queue than Hobby
|
||||
</li>
|
||||
</ul>
|
||||
<button type="button"
|
||||
class="plan-btn w-full py-3 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>
|
||||
|
||||
<!-- Professional Plan -->
|
||||
<div class="plan-card bg-white rounded-3xl p-8 border-2 border-green-700 shadow-2xl shadow-green-900/10 flex flex-col cursor-pointer transition-all hover:shadow-2xl hover:-translate-y-1 relative"
|
||||
data-plan="professional">
|
||||
<div
|
||||
class="absolute top-0 left-1/2 -translate-x-1/2 -translate-y-1/2 bg-green-700 text-white text-xs font-bold px-4 py-1 rounded-full uppercase tracking-wider">
|
||||
Most Popular
|
||||
</div>
|
||||
<h3 class="text-xl font-bold text-gray-900 mb-2">Professional</h3>
|
||||
<p class="text-gray-600 text-sm mb-6">For serious WordPress plugin developers.</p>
|
||||
<div class="mb-6">
|
||||
<span id="pro-price" class="text-4xl font-bold text-gray-900">$25</span>
|
||||
<span id="pro-period" class="price-duration text-gray-500">/mo</span>
|
||||
</div>
|
||||
<ul class="space-y-4 mb-8 flex-grow">
|
||||
<li class="flex items-center text-sm text-gray-700">
|
||||
<i class="fa-solid fa-check text-green-600 mr-3"></i> Up to 20 apps
|
||||
</li>
|
||||
<li class="flex items-center text-sm text-gray-700">
|
||||
<i class="fa-solid fa-check text-green-600 mr-3"></i> Choice of AI models
|
||||
</li>
|
||||
<li class="flex items-center text-sm text-gray-700">
|
||||
<a href="/credits" target="_blank" class="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>
|
||||
<i class="fa-solid fa-check text-green-600 mr-3"></i> 5,000,000 Monthly AI credits
|
||||
</li>
|
||||
<li class="flex items-center text-sm text-gray-700">
|
||||
<i class="fa-solid fa-check text-green-600 mr-3"></i> Access to templates
|
||||
</li>
|
||||
<li class="flex items-center text-sm text-gray-700">
|
||||
<i class="fa-solid fa-check text-green-600 mr-3"></i> Priority queue (ahead of Hobby)
|
||||
</li>
|
||||
</ul>
|
||||
<button type="button"
|
||||
class="plan-btn w-full py-3 px-6 rounded-xl bg-green-700 text-white font-bold hover:bg-green-600 transition-all shadow-lg shadow-green-700/20 text-center"
|
||||
data-plan="professional">
|
||||
Get Started
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Enterprise Plan -->
|
||||
<div class="plan-card bg-white rounded-3xl p-8 border border-gray-100 shadow-xl shadow-gray-200/50 flex flex-col cursor-pointer transition-all hover:shadow-2xl hover:-translate-y-1"
|
||||
data-plan="enterprise">
|
||||
<h3 class="text-xl font-bold text-gray-900 mb-2">Enterprise</h3>
|
||||
<p class="text-gray-600 text-sm mb-6">For teams and high-volume builders.</p>
|
||||
<div class="mb-6">
|
||||
<span id="enterprise-price" class="text-4xl font-bold text-gray-900">$75</span>
|
||||
<span id="enterprise-period" class="price-duration text-gray-500">/mo</span>
|
||||
</div>
|
||||
<ul class="space-y-4 mb-8 flex-grow">
|
||||
<li class="flex items-center text-sm text-gray-700">
|
||||
<i class="fa-solid fa-check text-green-600 mr-3"></i> Unlimited apps
|
||||
</li>
|
||||
<li class="flex items-center text-sm text-gray-700">
|
||||
<i class="fa-solid fa-check text-green-600 mr-3"></i> Fastest queue priority
|
||||
</li>
|
||||
<li class="flex items-center text-sm text-gray-700">
|
||||
<a href="/credits" target="_blank" class="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>
|
||||
<i class="fa-solid fa-check text-green-600 mr-3"></i> 50,000,000 monthly AI credits
|
||||
</li>
|
||||
<li class="flex items-center text-sm text-gray-700">
|
||||
<i class="fa-solid fa-check text-green-600 mr-3"></i> Access to templates
|
||||
</li>
|
||||
</ul>
|
||||
<button type="button"
|
||||
class="plan-btn w-full py-3 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>
|
||||
<p class="text-center text-sm text-gray-600 mt-6">Queue priority: Enterprise first, then Professional, then
|
||||
Hobby for faster responses.</p>
|
||||
<p class="text-center text-sm text-gray-600 mt-2">
|
||||
<a href="/credits" target="_blank" class="inline-flex items-center text-green-700 hover:text-green-600" title="Learn more about AI credits">
|
||||
<i class="fa-solid fa-circle-question mr-1"></i>
|
||||
</a>
|
||||
Usage multipliers: Standard models burn 1x credits,
|
||||
Advanced burn 2x, Premium burn 3x. Example: 2,500 tokens on a 2x model consume 5,000 credits.</p>
|
||||
<p class="text-center text-sm text-gray-600 mt-2">Need more runway? Grab discounted AI energy boosts —
|
||||
biggest breaks for Enterprise.</p>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<!-- Payment Modal -->
|
||||
<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>
|
||||
|
||||
<footer class="bg-white border-t border-green-200 pt-16 pb-8">
|
||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div class="grid grid-cols-2 lg:grid-cols-5 gap-12 mb-16">
|
||||
<div class="col-span-2 lg:col-span-1">
|
||||
<div class="flex items-center gap-2 mb-6">
|
||||
<img src="/assets/Plugin.png" alt="Plugin Compass" class="w-8 h-8">
|
||||
<span class="font-bold text-xl tracking-tight text-gray-800">Plugin<span
|
||||
class="text-green-700">Compass</span></span>
|
||||
</div>
|
||||
<p class="text-gray-600 text-sm leading-relaxed">
|
||||
The smart way for WordPress site owners to replace expensive plugin subscriptions with custom
|
||||
solutions. Save thousands monthly.
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<h4 class="font-bold text-gray-900 mb-6">Product</h4>
|
||||
<ul class="space-y-4 text-sm">
|
||||
<li><a href="/features" class="text-gray-600 hover:text-green-700">Features</a></li>
|
||||
<li><a href="/pricing" class="text-gray-600 hover:text-green-700">Pricing</a></li>
|
||||
<li><a href="/templates.html" class="text-gray-600 hover:text-green-700">Templates</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div>
|
||||
<h4 class="font-bold text-gray-900 mb-6">Resources</h4>
|
||||
<ul class="space-y-4 text-sm">
|
||||
<li><a href="/docs" class="text-gray-600 hover:text-green-700">Documentation</a></li>
|
||||
<li><a href="/faq" class="text-gray-600 hover:text-green-700">FAQ</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div>
|
||||
<h4 class="font-bold text-gray-900 mb-6">Legal</h4>
|
||||
<ul class="space-y-4 text-sm">
|
||||
<li><a href="/privacy.html" class="text-gray-600 hover:text-green-700">Privacy Policy</a></li>
|
||||
<li><a href="/terms" class="text-gray-600 hover:text-green-700">Terms of Service</a></li>
|
||||
<li><a href="/contact" class="text-gray-600 hover:text-green-700">Contact Us</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="col-span-2 lg:col-span-1">
|
||||
<h4 class="font-bold text-gray-900 mb-6">Stay Updated</h4>
|
||||
<p class="text-gray-600 text-sm mb-4">Get the latest updates and WordPress tips.</p>
|
||||
<form id="footer-signup-form" class="flex flex-col gap-2">
|
||||
<input type="email" name="email" placeholder="Your email" required
|
||||
class="px-4 py-2 border border-green-200 rounded-lg focus:outline-none focus:ring-2 focus:ring-green-700/20 text-sm">
|
||||
<button type="submit"
|
||||
class="bg-green-700 hover:bg-green-600 text-white px-4 py-2 rounded-lg font-medium text-sm transition-colors shadow-lg shadow-green-700/10">
|
||||
Subscribe
|
||||
</button>
|
||||
</form>
|
||||
<div id="signup-message" class="mt-2 text-xs hidden"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="border-t border-gray-100 pt-8 flex justify-center">
|
||||
<p class="text-gray-500 text-xs text-center">© 2026 Plugin Compass. All rights reserved.</p>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<script>
|
||||
// Current selection state
|
||||
let currentBillingCycle = 'monthly';
|
||||
let currentCurrency = 'USD';
|
||||
let isProcessing = false;
|
||||
|
||||
// Plan pricing configuration (matching pricing.html logic)
|
||||
const planPricing = {
|
||||
starter: {
|
||||
monthly: { USD: 7.5, GBP: 5, EUR: 7.5 },
|
||||
yearly: { USD: 75, GBP: 50, EUR: 75 }
|
||||
},
|
||||
professional: {
|
||||
monthly: { USD: 25, GBP: 20, EUR: 25 },
|
||||
yearly: { USD: 250, GBP: 200, EUR: 250 }
|
||||
},
|
||||
enterprise: {
|
||||
monthly: { USD: 75, GBP: 60, EUR: 75 },
|
||||
yearly: { USD: 750, GBP: 600, EUR: 750 }
|
||||
}
|
||||
};
|
||||
|
||||
// Currency symbols
|
||||
const currencySymbols = {
|
||||
USD: '$',
|
||||
GBP: '£',
|
||||
EUR: '€'
|
||||
};
|
||||
|
||||
// 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 {};
|
||||
}
|
||||
|
||||
// Initialize on page load
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
setupBillingCycleToggle();
|
||||
setupCurrencyDropdown();
|
||||
setupPlanSelection();
|
||||
updatePlanPrices();
|
||||
autoDetectCurrency();
|
||||
|
||||
// Check for pre-selected plan from URL query parameter
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
const preselectedPlan = urlParams.get('plan');
|
||||
if (preselectedPlan) {
|
||||
preselectPlan(preselectedPlan);
|
||||
}
|
||||
});
|
||||
|
||||
function preselectPlan(plan) {
|
||||
const card = document.querySelector(`.plan-card[data-plan="${plan}"]`);
|
||||
if (card) {
|
||||
// Add visual selection state
|
||||
const allCards = document.querySelectorAll('.plan-card');
|
||||
allCards.forEach(c => {
|
||||
c.classList.remove('ring-4', 'ring-green-700', 'ring-offset-2');
|
||||
});
|
||||
card.classList.add('ring-4', 'ring-green-700', 'ring-offset-2');
|
||||
|
||||
// Auto-trigger checkout for paid plans (hobby is free, no checkout needed)
|
||||
if (plan !== 'hobby') {
|
||||
// Small delay to ensure UI is ready
|
||||
setTimeout(() => {
|
||||
triggerPlanSelection(plan);
|
||||
}, 100);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Separate function to trigger plan selection (can be called from click or auto-trigger)
|
||||
async function triggerPlanSelection(plan) {
|
||||
// Don't process if already in progress
|
||||
if (isProcessing) return;
|
||||
|
||||
const card = document.querySelector(`.plan-card[data-plan="${plan}"]`);
|
||||
if (!card) return;
|
||||
|
||||
console.log('Plan selected:', plan);
|
||||
isProcessing = true;
|
||||
|
||||
// Update UI state
|
||||
const allCards = document.querySelectorAll('.plan-card');
|
||||
allCards.forEach(c => {
|
||||
c.classList.remove('ring-4', 'ring-green-700', 'ring-offset-2');
|
||||
});
|
||||
card.classList.add('ring-4', 'ring-green-700', 'ring-offset-2');
|
||||
|
||||
// Disable all buttons during selection
|
||||
const allButtons = document.querySelectorAll('.plan-btn');
|
||||
allButtons.forEach(btn => {
|
||||
btn.disabled = true;
|
||||
btn.textContent = 'Processing...';
|
||||
btn.classList.add('opacity-50', 'cursor-not-allowed');
|
||||
});
|
||||
|
||||
try {
|
||||
const authHeaders = getAuthHeaders();
|
||||
let data;
|
||||
|
||||
if (plan === 'hobby') {
|
||||
// Hobby plan is free, use original endpoint
|
||||
const response = await fetch('/api/select-plan', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
...authHeaders
|
||||
},
|
||||
body: JSON.stringify({ plan }),
|
||||
credentials: 'include',
|
||||
});
|
||||
|
||||
data = await response.json();
|
||||
|
||||
if (response.ok && data.ok) {
|
||||
window.location.href = '/apps';
|
||||
} else {
|
||||
throw new Error(data.error || 'Failed to select plan');
|
||||
}
|
||||
} else {
|
||||
// Paid plans use inline subscription checkout
|
||||
const response = await fetch('/api/subscription/checkout', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
...authHeaders
|
||||
},
|
||||
body: JSON.stringify({
|
||||
plan: plan,
|
||||
billingCycle: currentBillingCycle,
|
||||
currency: currentCurrency.toLowerCase(),
|
||||
inline: true,
|
||||
previousPlan: (typeof window.currentPlan !== 'undefined' ? window.currentPlan : '')
|
||||
}),
|
||||
credentials: 'include',
|
||||
});
|
||||
|
||||
data = await response.json();
|
||||
|
||||
if (response.ok && data.sessionId) {
|
||||
// Use Dodo inline checkout - prefer inlineCheckoutUrl if available, otherwise use checkoutUrl
|
||||
const checkoutUrl = data.inlineCheckoutUrl || data.checkoutUrl;
|
||||
if (checkoutUrl) {
|
||||
await openInlineCheckout(checkoutUrl, data.sessionId);
|
||||
} else {
|
||||
throw new Error('No checkout URL provided');
|
||||
}
|
||||
} else {
|
||||
throw new Error(data.error || 'Failed to start checkout');
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error selecting plan:', error);
|
||||
|
||||
alert(error.message || 'Failed to select plan. Please try again.');
|
||||
|
||||
// Re-enable buttons
|
||||
allButtons.forEach(btn => {
|
||||
btn.disabled = false;
|
||||
const planType = btn.getAttribute('data-plan');
|
||||
if (planType === 'hobby') {
|
||||
btn.textContent = 'Start for Free';
|
||||
} else if (planType === 'starter') {
|
||||
btn.textContent = 'Choose Starter';
|
||||
} else if (planType === 'professional') {
|
||||
btn.textContent = 'Get Started';
|
||||
} else if (planType === 'enterprise') {
|
||||
btn.textContent = 'Choose Enterprise';
|
||||
}
|
||||
btn.classList.remove('opacity-50', 'cursor-not-allowed');
|
||||
});
|
||||
isProcessing = false;
|
||||
}
|
||||
}
|
||||
|
||||
function setupBillingCycleToggle() {
|
||||
const monthlyBtn = document.getElementById('monthly-toggle');
|
||||
const yearlyBtn = document.getElementById('yearly-toggle');
|
||||
if (!monthlyBtn || !yearlyBtn) return;
|
||||
|
||||
monthlyBtn.addEventListener('click', () => {
|
||||
currentBillingCycle = 'monthly';
|
||||
monthlyBtn.className = 'px-6 py-2 rounded-full text-sm font-medium bg-green-700 text-white shadow-sm transition-all';
|
||||
yearlyBtn.className = 'px-6 py-2 rounded-full text-sm font-medium text-gray-600 hover:text-gray-900 transition-all';
|
||||
updatePlanPrices();
|
||||
});
|
||||
|
||||
yearlyBtn.addEventListener('click', () => {
|
||||
currentBillingCycle = 'yearly';
|
||||
yearlyBtn.className = 'px-6 py-2 rounded-full text-sm font-medium bg-green-700 text-white shadow-sm transition-all';
|
||||
monthlyBtn.className = 'px-6 py-2 rounded-full text-sm font-medium text-gray-600 hover:text-gray-900 transition-all';
|
||||
updatePlanPrices();
|
||||
});
|
||||
}
|
||||
|
||||
function setupCurrencyDropdown() {
|
||||
const dropdown = document.getElementById('currency-dropdown');
|
||||
const btn = document.getElementById('currency-btn');
|
||||
const options = document.getElementById('currency-options');
|
||||
const chevron = document.getElementById('currency-chevron');
|
||||
if (!dropdown || !btn || !options) return;
|
||||
|
||||
btn.addEventListener('click', (e) => {
|
||||
e.stopPropagation();
|
||||
const isOpen = !dropdown.classList.contains('open');
|
||||
|
||||
if (isOpen) {
|
||||
dropdown.classList.add('open');
|
||||
options.classList.remove('hidden');
|
||||
if (chevron) chevron.style.transform = 'rotate(180deg)';
|
||||
} else {
|
||||
dropdown.classList.remove('open');
|
||||
options.classList.add('hidden');
|
||||
if (chevron) chevron.style.transform = 'rotate(0deg)';
|
||||
}
|
||||
});
|
||||
|
||||
// Handle currency selection
|
||||
document.querySelectorAll('.currency-option').forEach(option => {
|
||||
option.addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
const currency = option.getAttribute('data-value');
|
||||
currentCurrency = currency;
|
||||
|
||||
// Update button display
|
||||
const flags = { USD: '🇺🇸', GBP: '🇬🇧', EUR: '🇪🇺' };
|
||||
const flagEl = document.getElementById('currency-flag');
|
||||
const codeEl = document.getElementById('currency-code');
|
||||
if (flagEl) flagEl.textContent = flags[currency];
|
||||
if (codeEl) codeEl.textContent = currency;
|
||||
|
||||
// Close dropdown
|
||||
dropdown.classList.remove('open');
|
||||
options.classList.add('hidden');
|
||||
if (chevron) chevron.style.transform = 'rotate(0deg)';
|
||||
|
||||
updatePlanPrices();
|
||||
});
|
||||
});
|
||||
|
||||
// Close dropdown when clicking outside
|
||||
document.addEventListener('click', () => {
|
||||
dropdown.classList.remove('open');
|
||||
options.classList.add('hidden');
|
||||
if (chevron) chevron.style.transform = 'rotate(0deg)';
|
||||
});
|
||||
}
|
||||
|
||||
function updatePlanPrices() {
|
||||
// Update Hobby plan price
|
||||
const hobbyPrice = document.getElementById('hobby-price');
|
||||
const hobbyPeriod = document.getElementById('hobby-period');
|
||||
if (hobbyPrice) hobbyPrice.textContent = currencySymbols[currentCurrency] + '0';
|
||||
if (hobbyPeriod) hobbyPeriod.textContent = currentBillingCycle === 'monthly' ? '/mo' : '/yr';
|
||||
|
||||
// Update Starter plan price
|
||||
const starterPrice = document.getElementById('starter-price');
|
||||
const starterPeriod = document.getElementById('starter-period');
|
||||
const starterAmount = planPricing.starter[currentBillingCycle][currentCurrency];
|
||||
if (starterPrice) starterPrice.textContent = currencySymbols[currentCurrency] + starterAmount.toFixed(starterAmount % 1 === 0 ? 0 : 2);
|
||||
if (starterPeriod) starterPeriod.textContent = currentBillingCycle === 'monthly' ? '/mo' : '/yr';
|
||||
|
||||
// Update Professional plan price
|
||||
const proPrice = document.getElementById('pro-price');
|
||||
const proPeriod = document.getElementById('pro-period');
|
||||
const proAmount = planPricing.professional[currentBillingCycle][currentCurrency];
|
||||
if (proPrice) proPrice.textContent = currencySymbols[currentCurrency] + proAmount.toFixed(proAmount % 1 === 0 ? 0 : 2);
|
||||
if (proPeriod) proPeriod.textContent = currentBillingCycle === 'monthly' ? '/mo' : '/yr';
|
||||
|
||||
// Update Enterprise plan price
|
||||
const enterprisePrice = document.getElementById('enterprise-price');
|
||||
const enterprisePeriod = document.getElementById('enterprise-period');
|
||||
const enterpriseAmount = planPricing.enterprise[currentBillingCycle][currentCurrency];
|
||||
if (enterprisePrice) enterprisePrice.textContent = currencySymbols[currentCurrency] + enterpriseAmount.toFixed(enterpriseAmount % 1 === 0 ? 0 : 2);
|
||||
if (enterprisePeriod) enterprisePeriod.textContent = currentBillingCycle === 'monthly' ? '/mo' : '/yr';
|
||||
}
|
||||
|
||||
function setupPlanSelection() {
|
||||
// Only handle card clicks to avoid double-triggering
|
||||
document.querySelectorAll('.plan-card').forEach(card => {
|
||||
card.addEventListener('click', async (e) => {
|
||||
// Let links within the card work normally
|
||||
if (e.target.closest('a')) return;
|
||||
|
||||
e.preventDefault();
|
||||
|
||||
const plan = card.getAttribute('data-plan');
|
||||
if (!plan) return;
|
||||
|
||||
// Use the shared triggerPlanSelection function
|
||||
triggerPlanSelection(plan);
|
||||
});
|
||||
});
|
||||
|
||||
// Prevent button clicks from bubbling to card (card listener handles everything)
|
||||
document.querySelectorAll('.plan-btn').forEach(btn => {
|
||||
btn.addEventListener('click', (e) => {
|
||||
// Just let it bubble to the card
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Detect Location and Set Currency
|
||||
async function autoDetectCurrency() {
|
||||
try {
|
||||
const response = await fetch('https://ipapi.co/json/');
|
||||
const data = await response.json();
|
||||
if (data.currency && currencySymbols[data.currency]) {
|
||||
currentCurrency = data.currency;
|
||||
|
||||
// Update button display
|
||||
const flags = { USD: '🇺🇸', GBP: '🇬🇧', EUR: '🇪🇺' };
|
||||
const flagEl = document.getElementById('currency-flag');
|
||||
const codeEl = document.getElementById('currency-code');
|
||||
if (flagEl) flagEl.textContent = flags[currentCurrency];
|
||||
if (codeEl) codeEl.textContent = currentCurrency;
|
||||
|
||||
updatePlanPrices();
|
||||
}
|
||||
} catch (err) {
|
||||
console.log('Currency detection failed, defaulting to USD');
|
||||
}
|
||||
}
|
||||
|
||||
async function openInlineCheckout(checkoutUrl, sessionId) {
|
||||
const modal = document.getElementById('payment-modal');
|
||||
const container = document.getElementById('payment-frame-container');
|
||||
|
||||
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: 'include'
|
||||
});
|
||||
|
||||
if (resp.ok) {
|
||||
const data = await resp.json();
|
||||
if (data.ok) {
|
||||
clearInterval(pollTimer);
|
||||
closeCheckoutModal();
|
||||
window.location.href = '/apps';
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (pollCount >= maxPolls) {
|
||||
clearInterval(pollTimer);
|
||||
closeCheckoutModal();
|
||||
alert('Payment confirmation timeout. Please check your account status.');
|
||||
reenableButtons();
|
||||
isProcessing = false;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error checking payment status:', error);
|
||||
}
|
||||
};
|
||||
|
||||
const pollTimer = setInterval(checkPaymentStatus, pollInterval);
|
||||
|
||||
function closeCheckoutModal() {
|
||||
clearInterval(pollTimer);
|
||||
modal.classList.remove('active');
|
||||
container.innerHTML = '';
|
||||
}
|
||||
|
||||
function reenableButtons() {
|
||||
const allButtons = document.querySelectorAll('.plan-btn');
|
||||
allButtons.forEach(btn => {
|
||||
btn.disabled = false;
|
||||
const planType = btn.getAttribute('data-plan');
|
||||
if (planType === 'hobby') {
|
||||
btn.textContent = 'Start for Free';
|
||||
} else if (planType === 'starter') {
|
||||
btn.textContent = 'Choose Starter';
|
||||
} else if (planType === 'professional') {
|
||||
btn.textContent = 'Get Started';
|
||||
} else if (planType === 'enterprise') {
|
||||
btn.textContent = 'Choose Enterprise';
|
||||
}
|
||||
btn.classList.remove('opacity-50', 'cursor-not-allowed');
|
||||
});
|
||||
}
|
||||
|
||||
document.getElementById('payment-modal-close').onclick = () => {
|
||||
closeCheckoutModal();
|
||||
reenableButtons();
|
||||
isProcessing = false;
|
||||
};
|
||||
|
||||
modal.onclick = (e) => {
|
||||
if (e.target === modal) {
|
||||
closeCheckoutModal();
|
||||
reenableButtons();
|
||||
isProcessing = false;
|
||||
}
|
||||
};
|
||||
|
||||
const handleEsc = (e) => {
|
||||
if (e.key === 'Escape') {
|
||||
closeCheckoutModal();
|
||||
reenableButtons();
|
||||
isProcessing = false;
|
||||
}
|
||||
};
|
||||
document.addEventListener('keydown', handleEsc);
|
||||
}
|
||||
|
||||
// Email Signup Form Handler
|
||||
const signupForm = document.getElementById('footer-signup-form');
|
||||
const signupMessage = document.getElementById('signup-message');
|
||||
|
||||
if (signupForm) {
|
||||
signupForm.addEventListener('submit', async (e) => {
|
||||
e.preventDefault();
|
||||
const email = signupForm.querySelector('input[name="email"]').value;
|
||||
const button = signupForm.querySelector('button');
|
||||
|
||||
button.disabled = true;
|
||||
button.textContent = 'Subscribing...';
|
||||
|
||||
try {
|
||||
const response = await fetch('https://emailmarketing.modelrailway3d.co.uk/api/webhooks/incoming/wh_0Z49zi_DGj4-lKJMOPO8-g', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
email: email,
|
||||
source: 'plugin_compass_select_plan',
|
||||
timestamp: new Date().toISOString()
|
||||
})
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
signupMessage.textContent = 'Successfully subscribed!';
|
||||
signupMessage.className = 'mt-2 text-xs text-green-600';
|
||||
signupForm.reset();
|
||||
} else {
|
||||
throw new Error('Failed to subscribe');
|
||||
}
|
||||
} catch (error) {
|
||||
signupMessage.textContent = 'Failed to subscribe. Please try again.';
|
||||
signupMessage.className = 'mt-2 text-xs text-red-600';
|
||||
} finally {
|
||||
signupMessage.classList.remove('hidden');
|
||||
button.disabled = false;
|
||||
button.textContent = 'Subscribe';
|
||||
|
||||
setTimeout(() => {
|
||||
signupMessage.classList.add('hidden');
|
||||
}, 5000);
|
||||
}
|
||||
});
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
2474
chat/public/settings.html
Normal file
119
chat/public/shopify-builder-prompt.txt
Normal file
@@ -0,0 +1,119 @@
|
||||
You are an expert WordPress plugin developer. When asked to build a WordPress plugin, you must create a COMPLETE, FULLY FUNCTIONAL PHP plugin with all necessary files and WordPress integrations. You must ensure that all of the features work exactly as the users request and plan specify and ensure that the plugin is completely valid and will not cause any critical errors.
|
||||
|
||||
Here is the user's request:
|
||||
{{USER_REQUEST}}
|
||||
|
||||
IMPORTANT REQUIREMENTS:
|
||||
1. Create a complete WordPress plugin in PHP following WordPress coding standards
|
||||
2. Include plugin header comment block and proper folder structure
|
||||
3. Register activation/deactivation/uninstall hooks and any required DB migrations
|
||||
4. Provide admin UI using WordPress admin pages, Settings API, or custom blocks as requested
|
||||
5. Add shortcodes or Gutenberg blocks for frontend features where applicable
|
||||
6. Include REST API endpoints or AJAX handlers if the plugin exposes APIs
|
||||
7. Ensure capability checks, nonce protection, sanitization, and escaping for security
|
||||
8. Ensure that all functionality will be initialsed when the plugin is installed and the user has to do as minimal setup as possible.
|
||||
|
||||
Critical Security Requirements
|
||||
|
||||
You must never try to or attempt to or ask the user for permission to edit files outside of the workspace you are editing in.
|
||||
|
||||
CRITICAL PHP SYNTAX CHECKING REQUIREMENTS:
|
||||
- You MUST always use PHP syntax checking commands after creating or modifying any PHP files to ensure that there are no errors with the syntax to ensure that the plugin will not cause a critical errors.
|
||||
- Use `php -l filename.php` to check syntax for each PHP file
|
||||
- Use `php -d display_errors=1 -l filename.php` for detailed syntax error reporting
|
||||
- Check for missing function definitions, undefined variables, and syntax errors
|
||||
- Fix all syntax errors and warnings before considering the code complete
|
||||
- Always verify the syntax of the main plugin file and all included PHP files
|
||||
- When using shortcodes, you must always ensure that they can be loaded properly
|
||||
- You must ensure that there are no missing methods or missing registrations
|
||||
- After generating the plugin, run `./scripts/validate-wordpress-plugin.sh <plugin-root-directory>` to lint every PHP file and verify the plugin header. Do NOT mark the work complete until this script exits successfully; if it reports errors, fix them and re-run the script before finishing.
|
||||
|
||||
CRITICAL RUNTIME ERROR DETECTION REQUIREMENTS:
|
||||
- Duplicate class/interface/trait/function/const declarations cause "Cannot redeclare" fatal errors at runtime
|
||||
- Missing include/require files cause "Failed opening required" fatal errors
|
||||
- Using undefined classes (new, instanceof, extends, implements) cause "Class not found" errors
|
||||
- After generating or modifying any PHP files, ALWAYS run `./scripts/check-duplicate-classes.php <plugin-root-directory>` to detect these issues
|
||||
- If duplicates are found, fix them by either:
|
||||
- Using `class_exists()` guards around class definitions
|
||||
- Removing duplicate declarations and consolidating into one file
|
||||
- Namespacing conflicting classes differently
|
||||
- If missing includes are found, fix the file paths
|
||||
- If missing classes are found, either declare them or remove the usage
|
||||
- Re-run the duplicate checker until no issues are reported
|
||||
- This is especially important when including files or using require/include statements
|
||||
|
||||
STYLING REQUIREMENTS (CRITICAL):
|
||||
9. **Admin Panel Styling:**
|
||||
- Enqueue custom admin CSS that uses WordPress admin color schemes
|
||||
- Follow WordPress admin design patterns (cards, notices, tables)
|
||||
- Use WordPress UI components: wp-admin classes (.wrap, .card, .notice, etc.)
|
||||
- Include responsive design for mobile admin
|
||||
- Add proper icons using Dashicons or custom SVG
|
||||
- Style forms with proper spacing, labels, and help text
|
||||
- Use WordPress color palette variables
|
||||
|
||||
10. **Public Page Styling:**
|
||||
- Enqueue separate frontend CSS with unique prefixed classes
|
||||
- Provide modern, responsive design with mobile-first approach
|
||||
- Include CSS Grid or Flexbox layouts where appropriate
|
||||
- Add hover states, transitions, and micro-interactions
|
||||
- Ensure accessibility (WCAG 2.1 AA compliance)
|
||||
- Avoid styling conflicts with themes (use specific class prefixes)
|
||||
|
||||
11. Compatibility
|
||||
|
||||
- You must ensure that all generated plugin code is compatible with the latest versions of wordpress, woocommerce and any other required intergrations
|
||||
- If the plugin includes WooCommerce functionality (products, orders, cart, checkout, payments, etc.), you MUST also run `./scripts/validate-woocommerce.sh <plugin-root-directory>` after validation to ensure WooCommerce compatibility. Do NOT mark work complete until both validation scripts pass.
|
||||
|
||||
STRUCTURE TO CREATE:
|
||||
- `{{PLUGIN_SLUG}}.php` (main plugin bootstrap file with header)
|
||||
- includes/ for helper classes and functions
|
||||
- admin/ for admin pages, settings, and menu registration
|
||||
- admin/css/admin-style.css (comprehensive admin styling)
|
||||
- admin/js/admin-script.js (if needed)
|
||||
- public/ for frontend templates, shortcodes, and assets
|
||||
- public/css/public-style.css (comprehensive frontend styling)
|
||||
- public/js/public-script.js (if needed)
|
||||
- assets/ for images, icons, and fonts
|
||||
- uninstall.php for cleanup
|
||||
|
||||
CRITICAL PLUGIN IDENTIFICATION:
|
||||
- Use the provided plugin slug: `{{PLUGIN_SLUG}}` (DO NOT generate a new random ID - this slug is stable across exports)
|
||||
- Main file MUST be named: `{{PLUGIN_SLUG}}.php`
|
||||
- Plugin Name should use: `{{PLUGIN_NAME}}` (can be descriptive, but the slug MUST stay constant)
|
||||
- Plugin header MUST include:
|
||||
* Plugin Name: `{{PLUGIN_NAME}}`
|
||||
* Plugin URI: https://plugincompass.com/plugins/{{PLUGIN_SLUG}}
|
||||
* Update URI: false
|
||||
* Author: Plugin Compass
|
||||
* Author URI: https://plugincompass.com
|
||||
- Text Domain MUST match slug: `{{PLUGIN_SLUG}}`
|
||||
- CRITICAL: The plugin slug `{{PLUGIN_SLUG}}` is used by WordPress to identify the plugin across updates. NEVER change or regenerate it, even if the plugin name or description changes. Always use the exact slug provided in the placeholders.
|
||||
- Add update check filter in main file to prevent WordPress.org conflicts:
|
||||
```php
|
||||
// Prevent WordPress.org update checks
|
||||
add_filter('site_transient_update_plugins', function($value) {
|
||||
$plugin_file = plugin_basename(__FILE__);
|
||||
if (isset($value->response[$plugin_file])) {
|
||||
unset($value->response[$plugin_file]);
|
||||
}
|
||||
return $value;
|
||||
});
|
||||
```
|
||||
- Prefix all PHP functions, classes and CSS classes with the slug to avoid conflicts.
|
||||
|
||||
STYLING BEST PRACTICES:
|
||||
- Use wp_enqueue_style() and wp_enqueue_script() properly with dependencies
|
||||
- Prefix all CSS classes with plugin slug (use `{{PLUGIN_SLUG}}`) to avoid conflicts
|
||||
- Minify CSS/JS for production (provide both source and minified versions)
|
||||
- Use CSS custom properties for easy theme customization
|
||||
- Include RTL stylesheet support (style-rtl.css)
|
||||
- Add print styles where appropriate
|
||||
- Ensure high contrast ratios for text readability
|
||||
|
||||
DESIGN PATTERNS TO FOLLOW:
|
||||
- Admin: Use WordPress's .widefat tables, .button classes, .postbox containers
|
||||
- Public: Modern card layouts, clean typography, appropriate whitespace
|
||||
- Both: Clear visual hierarchy, consistent spacing, intuitive navigation
|
||||
|
||||
When building, create COMPLETE and WELL-STYLED CSS files for both admin and public interfaces. Don't just create placeholder styles—provide production-ready, attractive designs that follow modern web design principles. You must ensure that all colours chosen stand out enough
|
||||
47
chat/public/shopify-builder-subsequent-prompt.txt
Normal file
@@ -0,0 +1,47 @@
|
||||
You are an expert wordpress plugin developer. You are continuing to help build a WordPress plugin. . When asked to continue a WordPress plugin, you must edit the plugin to provide a complete fully working implementation of the users request. You must ensure that the plugin is completely valid and will function exactly as asked and will not cause any critical errors.
|
||||
|
||||
|
||||
REMEMBER THESE CRITICAL REQUIREMENTS:
|
||||
CRITICAL PHP SYNTAX CHECKING REQUIREMENTS:
|
||||
- You MUST always use PHP syntax checking commands after creating or modifying any PHP files to ensure that there are no errors with the syntax to ensure that the plugin will not cause a critical errors.
|
||||
- Use `php -l filename.php` to check syntax for each PHP file
|
||||
- Use `php -d display_errors=1 -l filename.php` for detailed syntax error reporting
|
||||
- Check for missing function definitions, undefined variables, and syntax errors
|
||||
- Fix all syntax errors and warnings before considering the code complete
|
||||
- Always verify the syntax of the main plugin file and all included PHP files
|
||||
- When using shortcodes, you must always ensure that they can be loaded properly
|
||||
- You must ensure that there are no missing methods or missing registrations
|
||||
- After generating the plugin, run `./scripts/validate-wordpress-plugin.sh <plugin-root-directory>` to lint every PHP file and verify the plugin header. Do NOT mark the work complete until this script exits successfully; if it reports errors, fix them and re-run the script before finishing.
|
||||
|
||||
CRITICAL RUNTIME ERROR DETECTION REQUIREMENTS:
|
||||
- Duplicate class/interface/trait/function/const declarations cause "Cannot redeclare" fatal errors at runtime
|
||||
- Missing include/require files cause "Failed opening required" fatal errors
|
||||
- Using undefined classes (new, instanceof, extends, implements) cause "Class not found" errors
|
||||
- After generating or modifying any PHP files, ALWAYS run `./scripts/check-duplicate-classes.php <plugin-root-directory>` to detect these issues
|
||||
- If duplicates are found, fix them by either:
|
||||
- Using `class_exists()` guards around class definitions
|
||||
- Removing duplicate declarations and consolidating into one file
|
||||
- Namespacing conflicting classes differently
|
||||
- If missing includes are found, fix the file paths
|
||||
- If missing classes are found, either declare them or remove the usage
|
||||
- Re-run the duplicate checker until no issues are reported
|
||||
- This is especially important when including files or using require/include statements
|
||||
|
||||
2. After generating code, run `./scripts/validate-wordpress-plugin.sh <plugin-root-directory>` to verify all PHP files
|
||||
3. If the plugin includes WooCommerce functionality (products, orders, cart, checkout, payments, etc.), you MUST also run `./scripts/validate-woocommerce.sh <plugin-root-directory>` to verify WooCommerce compatibility. Do NOT mark work complete until both validation scripts pass.
|
||||
4. Use {{PLUGIN_SLUG}} prefix for all functions, classes, and CSS classes (NEVER change this slug - it must remain constant across all updates for WordPress to recognize this as the same plugin)
|
||||
5. Main plugin file must be named {{PLUGIN_SLUG}}.php and include WordPress.org update prevention filter
|
||||
6. Plugin Name MUST be unique and include the identifier (use placeholder `{{PLUGIN_NAME}}`)
|
||||
7. Plugin header: Plugin Name: `{{PLUGIN_NAME}}`, Plugin URI and Update URI as specified, Author: Plugin Compass
|
||||
7. Complete admin styling with WordPress admin classes (.wrap, .card, .notice, .button, .widefat)
|
||||
8. Complete public styling with responsive design, mobile-first, WCAG 2.1 AA compliance
|
||||
9. Enqueue styles/scripts properly with dependencies
|
||||
10. Security: capability checks, nonce protection, sanitization, escaping
|
||||
11. Compatibility with latest WordPress and WooCommerce
|
||||
|
||||
STYLING REQUIREMENTS:
|
||||
- Admin: WordPress color schemes, proper icons (Dashicons/SVG), responsive design
|
||||
- Public: Modern responsive design, hover states, transitions, high contrast
|
||||
- Both: Clear hierarchy, consistent spacing, accessible
|
||||
|
||||
Never edit files outside the workspace. Be concise and provide complete, production-ready code with full CSS.
|
||||
237
chat/public/signup-success.html
Normal file
@@ -0,0 +1,237 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Registration Successful | Plugin Compass</title>
|
||||
<link rel="icon" type="image/png" href="/assets/Plugin.png">
|
||||
<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">
|
||||
<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=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
|
||||
|
||||
<script>
|
||||
tailwind.config = {
|
||||
theme: {
|
||||
extend: {
|
||||
fontFamily: {
|
||||
sans: ['Inter', 'sans-serif'],
|
||||
},
|
||||
colors: {
|
||||
brand: {
|
||||
50: '#eef2ff',
|
||||
100: '#e0e7ff',
|
||||
200: '#c7d2fe',
|
||||
300: '#a5b4fc',
|
||||
400: '#818cf8',
|
||||
500: '#6366f1',
|
||||
600: '#4f46e5',
|
||||
700: '#4338ca',
|
||||
800: '#3730a3',
|
||||
900: '#312e81',
|
||||
950: '#1e1b4b',
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style>
|
||||
::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
}
|
||||
::-webkit-scrollbar-track {
|
||||
background: #fdf6ed;
|
||||
}
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: #d1d5db;
|
||||
border-radius: 4px;
|
||||
}
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background: #9ca3af;
|
||||
}
|
||||
|
||||
.glass-nav {
|
||||
background: rgba(251, 246, 239, 0.9);
|
||||
backdrop-filter: blur(12px);
|
||||
-webkit-backdrop-filter: blur(12px);
|
||||
border-bottom: 1px solid rgba(0, 66, 37, 0.1);
|
||||
}
|
||||
|
||||
.glass-panel {
|
||||
background: rgba(255, 255, 255, 0.8);
|
||||
backdrop-filter: blur(12px);
|
||||
-webkit-backdrop-filter: blur(12px);
|
||||
border: 1px solid rgba(0, 66, 37, 0.1);
|
||||
}
|
||||
</style>
|
||||
|
||||
<!-- PostHog Analytics -->
|
||||
<script src="/posthog.js"></script>
|
||||
</head>
|
||||
<body class="bg-amber-50 text-gray-900 font-sans antialiased min-h-screen flex flex-col">
|
||||
|
||||
<!-- Navigation -->
|
||||
<nav class="fixed w-full z-50 glass-nav transition-all duration-300" id="navbar">
|
||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div class="flex justify-between items-center h-20">
|
||||
<!-- Logo -->
|
||||
<a href="/" class="flex-shrink-0 flex items-center gap-2 cursor-pointer">
|
||||
<img src="/assets/Plugin.png" alt="Plugin Compass" class="w-8 h-8">
|
||||
<span class="font-bold text-xl tracking-tight text-gray-800">Plugin<span class="text-green-700">Compass</span></span>
|
||||
</a>
|
||||
|
||||
<!-- Desktop Menu -->
|
||||
<div class="hidden md:flex items-center space-x-8">
|
||||
<a href="/features"
|
||||
class="text-gray-700 hover:text-gray-900 transition-colors text-sm font-medium">Features</a>
|
||||
<a href="/pricing"
|
||||
class="text-gray-700 hover:text-gray-900 transition-colors text-sm font-medium">Pricing</a>
|
||||
<a href="/docs"
|
||||
class="text-gray-700 hover:text-gray-900 transition-colors text-sm font-medium">Docs</a>
|
||||
</div>
|
||||
|
||||
<!-- CTA Buttons -->
|
||||
<div class="hidden md:flex items-center gap-4">
|
||||
<a href="/login" class="text-gray-700 hover:text-gray-900 font-medium text-sm transition-colors">Sign In</a>
|
||||
<a href="/signup" class="bg-green-700 hover:bg-green-600 text-white px-5 py-2.5 rounded-full font-medium text-sm transition-all shadow-lg shadow-green-700/20 hover:shadow-green-700/40 transform hover:-translate-y-0.5">
|
||||
Get Started
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<!-- Mobile Menu Button -->
|
||||
<div class="md:hidden flex items-center">
|
||||
<button id="mobile-menu-btn" class="text-gray-700 hover:text-gray-900 focus:outline-none">
|
||||
<i class="fa-solid fa-bars text-xl"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Mobile Menu Panel -->
|
||||
<div id="mobile-menu" class="hidden md:hidden bg-amber-50 border-b border-amber-200">
|
||||
<div class="px-4 pt-2 pb-6 space-y-1">
|
||||
<a href="/features"
|
||||
class="block px-3 py-3 text-base font-medium text-gray-700 hover:text-gray-900 hover:bg-amber-100 rounded-md">Features</a>
|
||||
<a href="/pricing"
|
||||
class="block px-3 py-3 text-base font-medium text-gray-700 hover:text-gray-900 hover:bg-amber-100 rounded-md">Pricing</a>
|
||||
<a href="/docs"
|
||||
class="block px-3 py-3 text-base font-medium text-gray-700 hover:text-gray-900 hover:bg-amber-100 rounded-md">Docs</a>
|
||||
<div class="pt-4 flex flex-col gap-3">
|
||||
<a href="/login" class="w-full text-center py-2 text-gray-700 font-medium">Sign In</a>
|
||||
<a href="/signup" class="w-full bg-green-700 text-white text-center py-3 rounded-lg font-medium">Get
|
||||
Started</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<main class="flex-grow flex items-center justify-center px-4 pt-32 pb-12">
|
||||
<div class="max-w-lg w-full">
|
||||
<div class="glass-panel p-10 rounded-3xl shadow-2xl shadow-green-900/10 text-center">
|
||||
<div class="w-20 h-20 bg-green-100 rounded-full flex items-center justify-center text-green-700 mx-auto mb-8">
|
||||
<i class="fa-solid fa-envelope-open-text text-3xl"></i>
|
||||
</div>
|
||||
|
||||
<h1 class="text-3xl font-bold text-gray-900 mb-4">Check your email</h1>
|
||||
<p class="text-lg text-gray-600 mb-8">
|
||||
We've sent a verification link to your email address. Please click the link to verify your account and start building.
|
||||
</p>
|
||||
|
||||
<div class="bg-amber-100/50 rounded-2xl p-6 mb-8 border border-green-700/10 text-sm text-gray-700 text-left">
|
||||
<h3 class="font-bold text-gray-900 mb-2 flex items-center gap-2">
|
||||
<i class="fa-solid fa-circle-info text-green-700"></i>
|
||||
Didn't receive the email?
|
||||
</h3>
|
||||
<ul class="list-disc pl-5 space-y-1">
|
||||
<li>Check your spam or junk folder</li>
|
||||
<li>Verify that your email address was entered correctly</li>
|
||||
<li>Wait a few minutes as delivery can sometimes be delayed</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-4">
|
||||
<a href="/login" class="w-full bg-green-700 hover:bg-green-600 text-white font-bold py-4 rounded-xl transition-all shadow-lg shadow-green-700/20 transform hover:-translate-y-0.5">
|
||||
Back to Login
|
||||
</a>
|
||||
<a href="/" class="text-green-700 hover:underline font-medium">
|
||||
Return to Homepage
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<!-- Footer -->
|
||||
<footer class="bg-white border-t border-green-200 pt-16 pb-8">
|
||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div class="grid grid-cols-2 md:grid-cols-4 gap-12 mb-16">
|
||||
<div class="col-span-2 md:col-span-1">
|
||||
<div class="flex items-center gap-2 mb-6">
|
||||
<img src="/assets/Plugin.png" alt="Plugin Compass" class="w-8 h-8">
|
||||
<span class="font-bold text-xl tracking-tight text-gray-800">Plugin<span
|
||||
class="text-green-700">Compass</span></span>
|
||||
</div>
|
||||
<p class="text-gray-600 text-sm leading-relaxed">
|
||||
The smart way for WordPress site owners to replace expensive plugin subscriptions with custom
|
||||
solutions. Save thousands monthly.
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<h4 class="font-bold text-gray-900 mb-6">Product</h4>
|
||||
<ul class="space-y-4 text-sm">
|
||||
<li><a href="/features" class="text-gray-600 hover:text-green-700">Features</a></li>
|
||||
<li><a href="/pricing" class="text-gray-600 hover:text-green-700">Pricing</a></li>
|
||||
<li><a href="#" class="text-gray-600 hover:text-green-700">Templates</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div>
|
||||
<h4 class="font-bold text-gray-900 mb-6">Resources</h4>
|
||||
<ul class="space-y-4 text-sm">
|
||||
<li><a href="/docs" class="text-gray-600 hover:text-green-700">Documentation</a></li>
|
||||
<li><a href="/faq" class="text-gray-600 hover:text-green-700">FAQ</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div>
|
||||
<h4 class="font-bold text-gray-900 mb-6">Legal</h4>
|
||||
<ul class="space-y-4 text-sm">
|
||||
<li><a href="/privacy"
|
||||
class="text-gray-600 hover:text-green-700">Privacy Policy</a></li>
|
||||
<li><a href="/terms"
|
||||
class="text-gray-600 hover:text-green-700">Terms of Service</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="border-t border-gray-100 pt-8 flex justify-center">
|
||||
<p class="text-gray-500 text-xs text-center">© 2026 Plugin Compass. All rights reserved.</p>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<script>
|
||||
// Navigation functionality
|
||||
const mobileMenuBtn = document.getElementById('mobile-menu-btn');
|
||||
const mobileMenu = document.getElementById('mobile-menu');
|
||||
|
||||
if (mobileMenuBtn && mobileMenu) {
|
||||
mobileMenuBtn.addEventListener('click', () => {
|
||||
mobileMenu.classList.toggle('hidden');
|
||||
});
|
||||
}
|
||||
|
||||
// Navbar scroll effect
|
||||
window.addEventListener('scroll', () => {
|
||||
const navbar = document.getElementById('navbar');
|
||||
if (!navbar) return;
|
||||
if (window.scrollY > 20) {
|
||||
navbar.classList.add('shadow-md', 'h-16');
|
||||
navbar.classList.remove('h-20');
|
||||
} else {
|
||||
navbar.classList.remove('shadow-md', 'h-16');
|
||||
navbar.classList.add('h-20');
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
502
chat/public/signup.html
Normal file
@@ -0,0 +1,502 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Sign Up | Plugin Compass</title>
|
||||
<link rel="icon" type="image/png" href="/assets/Plugin.png">
|
||||
<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">
|
||||
<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=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
|
||||
|
||||
<script>
|
||||
tailwind.config = {
|
||||
theme: {
|
||||
extend: {
|
||||
fontFamily: {
|
||||
sans: ['Inter', 'sans-serif'],
|
||||
},
|
||||
colors: {
|
||||
brand: {
|
||||
50: '#eef2ff',
|
||||
100: '#e0e7ff',
|
||||
200: '#c7d2fe',
|
||||
300: '#a5b4fc',
|
||||
400: '#818cf8',
|
||||
500: '#6366f1',
|
||||
600: '#4f46e5',
|
||||
700: '#4338ca',
|
||||
800: '#3730a3',
|
||||
900: '#312e81',
|
||||
950: '#1e1b4b',
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style>
|
||||
::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
background: #fdf6ed;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: #d1d5db;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background: #9ca3af;
|
||||
}
|
||||
|
||||
.glass-nav {
|
||||
background: rgba(251, 246, 239, 0.9);
|
||||
backdrop-filter: blur(12px);
|
||||
-webkit-backdrop-filter: blur(12px);
|
||||
border-bottom: 1px solid rgba(0, 66, 37, 0.1);
|
||||
}
|
||||
|
||||
.glass-panel {
|
||||
background: rgba(255, 255, 255, 0.8);
|
||||
backdrop-filter: blur(12px);
|
||||
-webkit-backdrop-filter: blur(12px);
|
||||
border: 1px solid rgba(0, 66, 37, 0.1);
|
||||
}
|
||||
</style>
|
||||
|
||||
<!-- PostHog Analytics -->
|
||||
<script src="/posthog.js"></script>
|
||||
</head>
|
||||
|
||||
<body class="bg-amber-50 text-gray-900 font-sans antialiased min-h-screen flex flex-col">
|
||||
|
||||
<!-- Navigation -->
|
||||
<nav class="fixed w-full z-50 glass-nav transition-all duration-300" id="navbar">
|
||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div class="flex justify-between items-center h-20">
|
||||
<!-- Logo -->
|
||||
<a href="/" class="flex-shrink-0 flex items-center gap-2 cursor-pointer">
|
||||
<img src="/assets/Plugin.png" alt="Plugin Compass" class="w-8 h-8">
|
||||
<span class="font-bold text-xl tracking-tight text-gray-800">Plugin<span
|
||||
class="text-green-700">Compass</span></span>
|
||||
</a>
|
||||
|
||||
<!-- Desktop Menu -->
|
||||
<div class="hidden md:flex items-center space-x-8">
|
||||
<a href="/features"
|
||||
class="text-gray-700 hover:text-gray-900 transition-colors text-sm font-medium">Features</a>
|
||||
<a href="/pricing"
|
||||
class="text-gray-700 hover:text-gray-900 transition-colors text-sm font-medium">Pricing</a>
|
||||
<a href="/docs"
|
||||
class="text-gray-700 hover:text-gray-900 transition-colors text-sm font-medium">Docs</a>
|
||||
</div>
|
||||
|
||||
<!-- CTA Buttons -->
|
||||
<div class="hidden md:flex items-center gap-4">
|
||||
<a href="/login"
|
||||
class="text-gray-700 hover:text-gray-900 font-medium text-sm transition-colors">Sign In</a>
|
||||
<a href="/signup"
|
||||
class="bg-green-700 hover:bg-green-600 text-white px-5 py-2.5 rounded-full font-medium text-sm transition-all shadow-lg shadow-green-700/20 hover:shadow-green-700/40 transform hover:-translate-y-0.5">
|
||||
Get Started
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<!-- Mobile Menu Button -->
|
||||
<div class="md:hidden flex items-center">
|
||||
<button id="mobile-menu-btn" class="text-gray-700 hover:text-gray-900 focus:outline-none">
|
||||
<i class="fa-solid fa-bars text-xl"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Mobile Menu Panel -->
|
||||
<div id="mobile-menu" class="hidden md:hidden bg-amber-50 border-b border-amber-200">
|
||||
<div class="px-4 pt-2 pb-6 space-y-1">
|
||||
<a href="/features"
|
||||
class="block px-3 py-3 text-base font-medium text-gray-700 hover:text-gray-900 hover:bg-amber-100 rounded-md">Features</a>
|
||||
<a href="/pricing"
|
||||
class="block px-3 py-3 text-base font-medium text-gray-700 hover:text-gray-900 hover:bg-amber-100 rounded-md">Pricing</a>
|
||||
<a href="/docs"
|
||||
class="block px-3 py-3 text-base font-medium text-gray-700 hover:text-gray-900 hover:bg-amber-100 rounded-md">Docs</a>
|
||||
<div class="pt-4 flex flex-col gap-3">
|
||||
<a href="/login" class="w-full text-center py-2 text-gray-700 font-medium">Sign In</a>
|
||||
<a href="/signup" class="w-full bg-green-700 text-white text-center py-3 rounded-lg font-medium">Get
|
||||
Started</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<main class="flex-grow flex items-center justify-center px-4 pt-32 pb-12">
|
||||
<div class="max-w-md w-full">
|
||||
<div class="glass-panel p-8 rounded-3xl shadow-2xl shadow-green-900/10">
|
||||
<div class="text-center mb-8">
|
||||
<h1 class="text-3xl font-bold text-gray-900 mb-2">Create an account</h1>
|
||||
<p class="text-gray-600">Start building your WordPress plugin today.</p>
|
||||
</div>
|
||||
|
||||
<form id="signup-form" class="space-y-6">
|
||||
<!-- Honeypot field - hidden from real users -->
|
||||
<input type="text" name="website" id="website" tabindex="-1" autocomplete="off"
|
||||
style="position: absolute; left: -9999px;">
|
||||
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label for="first-name" class="block text-sm font-medium text-gray-700 mb-1">First
|
||||
Name</label>
|
||||
<input type="text" id="first-name"
|
||||
class="w-full px-4 py-3 rounded-xl border border-gray-200 focus:ring-2 focus:ring-green-700/20 focus:border-green-700 outline-none transition-all bg-white/50"
|
||||
placeholder="John">
|
||||
</div>
|
||||
<div>
|
||||
<label for="last-name" class="block text-sm font-medium text-gray-700 mb-1">Last
|
||||
Name</label>
|
||||
<input type="text" id="last-name"
|
||||
class="w-full px-4 py-3 rounded-xl border border-gray-200 focus:ring-2 focus:ring-green-700/20 focus:border-green-700 outline-none transition-all bg-white/50"
|
||||
placeholder="Doe">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="email" class="block text-sm font-medium text-gray-700 mb-1">Email Address</label>
|
||||
<input type="email" id="email"
|
||||
class="w-full px-4 py-3 rounded-xl border border-gray-200 focus:ring-2 focus:ring-green-700/20 focus:border-green-700 outline-none transition-all bg-white/50"
|
||||
placeholder="john@example.com">
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="password" class="block text-sm font-medium text-gray-700 mb-1">Password</label>
|
||||
<input type="password" id="password"
|
||||
class="w-full px-4 py-3 rounded-xl border border-gray-200 focus:ring-2 focus:ring-green-700/20 focus:border-green-700 outline-none transition-all bg-white/50"
|
||||
placeholder="••••••••">
|
||||
</div>
|
||||
|
||||
<div class="flex items-center">
|
||||
<input id="terms" name="terms" type="checkbox"
|
||||
class="h-4 w-4 text-green-700 focus:ring-green-600 border-gray-300 rounded">
|
||||
<label for="terms" class="ml-2 block text-sm text-gray-600">
|
||||
I agree to the <a href="/terms" class="text-green-700 hover:underline">Terms of Service</a>
|
||||
and <a href="/privacy" class="text-green-700 hover:underline">Privacy Policy</a>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<button type="submit"
|
||||
class="w-full bg-green-700 hover:bg-green-600 text-white font-bold py-4 rounded-xl transition-all shadow-lg shadow-green-700/20 transform hover:-translate-y-0.5">
|
||||
Create Account
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<div class="mt-8">
|
||||
<div class="relative">
|
||||
<div class="absolute inset-0 flex items-center">
|
||||
<div class="w-full border-t border-gray-200"></div>
|
||||
</div>
|
||||
<div class="relative flex justify-center text-sm">
|
||||
<span class="px-2 bg-amber-50 text-gray-500">Or sign up with</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-6 grid grid-cols-2 gap-4">
|
||||
<button type="button" data-oauth-provider="google"
|
||||
class="flex items-center justify-center gap-2 px-4 py-3 border border-gray-200 rounded-xl hover:bg-gray-50 transition-all bg-white/50">
|
||||
<i class="fa-brands fa-google text-red-500"></i>
|
||||
<span class="text-sm font-medium text-gray-700">Google</span>
|
||||
</button>
|
||||
<button type="button" data-oauth-provider="github"
|
||||
class="flex items-center justify-center gap-2 px-4 py-3 border border-gray-200 rounded-xl hover:bg-gray-50 transition-all bg-white/50">
|
||||
<i class="fa-brands fa-github text-gray-900"></i>
|
||||
<span class="text-sm font-medium text-gray-700">GitHub</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<!-- Footer -->
|
||||
<footer class="bg-white border-t border-green-200 pt-16 pb-8">
|
||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div class="grid grid-cols-2 md:grid-cols-4 gap-12 mb-16">
|
||||
<div class="col-span-2 md:col-span-1">
|
||||
<div class="flex items-center gap-2 mb-6">
|
||||
<img src="/assets/Plugin.png" alt="Plugin Compass" class="w-8 h-8">
|
||||
<span class="font-bold text-xl tracking-tight text-gray-800">Plugin<span
|
||||
class="text-green-700">Compass</span></span>
|
||||
</div>
|
||||
<p class="text-gray-600 text-sm leading-relaxed">
|
||||
The smart way for WordPress site owners to replace expensive plugin subscriptions with custom
|
||||
solutions. Save thousands monthly.
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<h4 class="font-bold text-gray-900 mb-6">Product</h4>
|
||||
<ul class="space-y-4 text-sm">
|
||||
<li><a href="/features" class="text-gray-600 hover:text-green-700">Features</a></li>
|
||||
<li><a href="/pricing" class="text-gray-600 hover:text-green-700">Pricing</a></li>
|
||||
<li><a href="#" class="text-gray-600 hover:text-green-700">Templates</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div>
|
||||
<h4 class="font-bold text-gray-900 mb-6">Resources</h4>
|
||||
<ul class="space-y-4 text-sm">
|
||||
<li><a href="/docs" class="text-gray-600 hover:text-green-700">Documentation</a></li>
|
||||
<li><a href="/faq" class="text-gray-600 hover:text-green-700">FAQ</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div>
|
||||
<h4 class="font-bold text-gray-900 mb-6">Legal</h4>
|
||||
<ul class="space-y-4 text-sm">
|
||||
<li><a href="/privacy" class="text-gray-600 hover:text-green-700">Privacy Policy</a></li>
|
||||
<li><a href="/terms" class="text-gray-600 hover:text-green-700">Terms of Service</a></li>
|
||||
<li><a href="/contact" class="text-gray-600 hover:text-green-700">Contact Us</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="border-t border-gray-100 pt-8 flex justify-center">
|
||||
<p class="text-gray-500 text-xs text-center">© 2026 Plugin Compass. All rights reserved.</p>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
|
||||
<script>
|
||||
// Navigation functionality
|
||||
const mobileMenuBtn = document.getElementById('mobile-menu-btn');
|
||||
const mobileMenu = document.getElementById('mobile-menu');
|
||||
|
||||
if (mobileMenuBtn && mobileMenu) {
|
||||
mobileMenuBtn.addEventListener('click', () => {
|
||||
mobileMenu.classList.toggle('hidden');
|
||||
});
|
||||
}
|
||||
|
||||
// Navbar scroll effect
|
||||
window.addEventListener('scroll', () => {
|
||||
const navbar = document.getElementById('navbar');
|
||||
if (!navbar) return;
|
||||
if (window.scrollY > 20) {
|
||||
navbar.classList.add('shadow-md', 'h-16');
|
||||
navbar.classList.remove('h-20');
|
||||
} else {
|
||||
navbar.classList.remove('shadow-md', 'h-16');
|
||||
navbar.classList.add('h-20');
|
||||
}
|
||||
});
|
||||
function cyrb53(str, seed = 0) {
|
||||
let h1 = 0xdeadbeef ^ seed;
|
||||
let h2 = 0x41c6ce57 ^ seed;
|
||||
for (let i = 0, ch; i < str.length; i++) {
|
||||
ch = str.charCodeAt(i);
|
||||
h1 = Math.imul(h1 ^ ch, 2654435761);
|
||||
h2 = Math.imul(h2 ^ ch, 1597334677);
|
||||
}
|
||||
h1 = Math.imul(h1 ^ (h1 >>> 16), 2246822507) ^ Math.imul(h2 ^ (h2 >>> 13), 3266489909);
|
||||
h2 = Math.imul(h2 ^ (h2 >>> 16), 2246822507) ^ Math.imul(h1 ^ (h1 >>> 13), 3266489909);
|
||||
return 4294967296 * (2097151 & h2) + (h1 >>> 0);
|
||||
}
|
||||
|
||||
function computeAccountId(email) {
|
||||
const normalized = (email || '').trim().toLowerCase();
|
||||
if (!normalized) return '';
|
||||
const hash = cyrb53(normalized);
|
||||
return `acct-${hash.toString(16)}`;
|
||||
}
|
||||
|
||||
function getDeviceUserId() {
|
||||
try {
|
||||
const existing = localStorage.getItem('wordpress_plugin_ai_user_id');
|
||||
if (existing) return existing;
|
||||
const uuidPart = crypto.randomUUID ? crypto.randomUUID() : Math.random().toString(36).slice(2, 12);
|
||||
const generated = `user-${uuidPart}`;
|
||||
localStorage.setItem('wordpress_plugin_ai_user_id', generated);
|
||||
return generated;
|
||||
} catch (_) {
|
||||
const fallbackPart = (typeof crypto !== 'undefined' && crypto.randomUUID)
|
||||
? crypto.randomUUID()
|
||||
: `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 12)}`;
|
||||
return `user-${fallbackPart}`;
|
||||
}
|
||||
}
|
||||
|
||||
function getNextPath() {
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
const next = params.get('next');
|
||||
if (next && next.startsWith('/') && !next.startsWith('//')) return next;
|
||||
return '/apps';
|
||||
}
|
||||
|
||||
function startOAuth(provider) {
|
||||
if (!provider) return;
|
||||
const next = encodeURIComponent(getNextPath());
|
||||
window.location.href = `/auth/${provider}?next=${next}`;
|
||||
}
|
||||
|
||||
document.querySelectorAll('[data-oauth-provider]').forEach((btn) => {
|
||||
btn.addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
const provider = btn.getAttribute('data-oauth-provider');
|
||||
startOAuth(provider);
|
||||
});
|
||||
});
|
||||
|
||||
async function claimDeviceApps(accountId, previousUserId) {
|
||||
try {
|
||||
await fetch('/api/account/claim', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-User-Id': accountId,
|
||||
},
|
||||
body: JSON.stringify({ previousUserId })
|
||||
});
|
||||
} catch (_) {
|
||||
// Best-effort; app data is not deleted.
|
||||
}
|
||||
}
|
||||
|
||||
document.getElementById('signup-form').addEventListener('submit', async (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
const statusEl = document.createElement('div');
|
||||
statusEl.id = 'signup-status';
|
||||
statusEl.className = 'text-sm text-red-600 mt-2 min-h-[1.25rem]';
|
||||
|
||||
const form = document.getElementById('signup-form');
|
||||
const existingStatus = document.getElementById('signup-status');
|
||||
if (existingStatus) {
|
||||
existingStatus.remove();
|
||||
}
|
||||
form.appendChild(statusEl);
|
||||
|
||||
function setStatus(msg, isError = true) {
|
||||
statusEl.textContent = msg || '';
|
||||
statusEl.style.color = isError ? 'rgb(185 28 28)' : 'rgb(34 197 94)';
|
||||
}
|
||||
|
||||
const emailEl = document.getElementById('email');
|
||||
const email = (emailEl && emailEl.value || '').trim().toLowerCase();
|
||||
const passwordEl = document.getElementById('password');
|
||||
const password = (passwordEl && passwordEl.value) || '';
|
||||
const termsEl = document.getElementById('terms');
|
||||
|
||||
if (!email) {
|
||||
if (emailEl) emailEl.focus();
|
||||
setStatus('Email address is required', true);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!password) {
|
||||
if (passwordEl) passwordEl.focus();
|
||||
setStatus('Password is required', true);
|
||||
return;
|
||||
}
|
||||
|
||||
const passwordRequirements = {
|
||||
hasMinLength: password.length >= 8,
|
||||
hasUppercase: /[A-Z]/.test(password),
|
||||
hasLowercase: /[a-z]/.test(password),
|
||||
hasNumber: /[0-9]/.test(password),
|
||||
hasSpecial: /[!@#$%^&*()_+\-=\[\]{};':"\\|,.<>\/?]/.test(password)
|
||||
};
|
||||
|
||||
const allRequirementsMet = Object.values(passwordRequirements).every(req => req);
|
||||
|
||||
if (!allRequirementsMet) {
|
||||
const missingRequirements = [];
|
||||
if (!passwordRequirements.hasMinLength) missingRequirements.push('at least 8 characters');
|
||||
if (!passwordRequirements.hasUppercase) missingRequirements.push('an uppercase letter');
|
||||
if (!passwordRequirements.hasLowercase) missingRequirements.push('a lowercase letter');
|
||||
if (!passwordRequirements.hasNumber) missingRequirements.push('a number');
|
||||
if (!passwordRequirements.hasSpecial) missingRequirements.push('a special character (!@#$%^&*...)');
|
||||
|
||||
const requirementText = missingRequirements.join(', ');
|
||||
if (passwordEl) passwordEl.focus();
|
||||
setStatus(`Password must include: ${requirementText}`, true);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!termsEl || !termsEl.checked) {
|
||||
setStatus('You must agree to the Terms of Service and Privacy Policy', true);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// Try server-side registration first
|
||||
const resp = await fetch('/api/register', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ email, password }),
|
||||
});
|
||||
const data = await resp.json().catch(() => ({}));
|
||||
|
||||
if (!resp.ok) {
|
||||
setStatus(data.error || 'Registration failed', true);
|
||||
return;
|
||||
}
|
||||
|
||||
if (data.ok && data.user) {
|
||||
if (data.verificationRequired) {
|
||||
window.location.href = '/signup-success';
|
||||
return;
|
||||
}
|
||||
|
||||
if (data.token) {
|
||||
try {
|
||||
localStorage.setItem('wordpress_plugin_ai_user', JSON.stringify({
|
||||
email: data.user.email,
|
||||
accountId: data.user.id,
|
||||
sessionToken: data.token
|
||||
}));
|
||||
} catch (_) { }
|
||||
|
||||
const deviceUserId = getDeviceUserId();
|
||||
await claimDeviceApps(data.user.id, deviceUserId);
|
||||
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
let next = params.get('next') || '/apps';
|
||||
if (!next || !next.startsWith('/') || next.startsWith('//')) next = '/apps';
|
||||
|
||||
// Check if user has selected a plan
|
||||
const hasPlan = data.user.plan && ['hobby', 'starter', 'business', 'enterprise'].includes(data.user.plan.toLowerCase());
|
||||
if (!hasPlan && next === '/apps') {
|
||||
next = '/select-plan';
|
||||
}
|
||||
|
||||
window.location.href = next;
|
||||
return;
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
setStatus('Registration request failed, please try again', true);
|
||||
console.warn('Registration request failed:', String(err));
|
||||
return;
|
||||
}
|
||||
|
||||
// Fallback to old behavior if server registration not available
|
||||
const deviceUserId = getDeviceUserId();
|
||||
const accountId = computeAccountId(email);
|
||||
try {
|
||||
localStorage.setItem('wordpress_plugin_ai_user', JSON.stringify({ email, accountId }));
|
||||
} catch (_) { }
|
||||
try {
|
||||
document.cookie = `chat_user=${encodeURIComponent(accountId)}; path=/; SameSite=Lax`;
|
||||
} catch (_) { }
|
||||
|
||||
await claimDeviceApps(accountId, deviceUserId);
|
||||
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
let next = params.get('next') || '/apps';
|
||||
if (!next || !next.startsWith('/') || next.startsWith('//')) next = '/apps';
|
||||
window.location.href = next;
|
||||
});
|
||||
</script>
|
||||
69
chat/public/sitemap.xml
Normal file
@@ -0,0 +1,69 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
||||
<url>
|
||||
<loc>https://plugincompass.com/</loc>
|
||||
<lastmod>2025-01-08</lastmod>
|
||||
<changefreq>daily</changefreq>
|
||||
<priority>1.0</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://plugincompass.com/features</loc>
|
||||
<lastmod>2025-01-08</lastmod>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.9</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://plugincompass.com/pricing</loc>
|
||||
<lastmod>2025-01-08</lastmod>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.9</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://plugincompass.com/apps</loc>
|
||||
<lastmod>2025-01-08</lastmod>
|
||||
<changefreq>daily</changefreq>
|
||||
<priority>0.9</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://plugincompass.com/builder</loc>
|
||||
<lastmod>2025-01-08</lastmod>
|
||||
<changefreq>daily</changefreq>
|
||||
<priority>0.9</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://plugincompass.com/affiliate</loc>
|
||||
<lastmod>2025-01-08</lastmod>
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>0.8</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://plugincompass.com/affiliate-signup</loc>
|
||||
<lastmod>2025-01-08</lastmod>
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>0.7</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://plugincompass.com/docs</loc>
|
||||
<lastmod>2025-01-08</lastmod>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.8</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://plugincompass.com/faq</loc>
|
||||
<lastmod>2025-01-08</lastmod>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.8</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://plugincompass.com/terms</loc>
|
||||
<lastmod>2025-01-08</lastmod>
|
||||
<changefreq>yearly</changefreq>
|
||||
<priority>0.3</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://plugincompass.com/privacy</loc>
|
||||
<lastmod>2025-01-08</lastmod>
|
||||
<changefreq>yearly</changefreq>
|
||||
<priority>0.3</priority>
|
||||
</url>
|
||||
</urlset>
|
||||
1164
chat/public/styles.css
Normal file
375
chat/public/subscription-success.html
Normal file
@@ -0,0 +1,375 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Subscription Activated | Plugin Compass</title>
|
||||
<link rel="icon" type="image/png" href="/assets/Plugin.png">
|
||||
<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">
|
||||
<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=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
|
||||
|
||||
<script>
|
||||
tailwind.config = {
|
||||
theme: {
|
||||
extend: {
|
||||
fontFamily: {
|
||||
sans: ['Inter', 'sans-serif'],
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style>
|
||||
.glass-panel {
|
||||
background: rgba(255, 255, 255, 0.9);
|
||||
backdrop-filter: blur(12px);
|
||||
-webkit-backdrop-filter: blur(12px);
|
||||
border: 1px solid rgba(0, 66, 37, 0.1);
|
||||
}
|
||||
</style>
|
||||
|
||||
<!-- PostHog Analytics -->
|
||||
<script src="/posthog.js"></script>
|
||||
</head>
|
||||
|
||||
<body class="bg-amber-50 text-gray-900 font-sans antialiased min-h-screen flex flex-col">
|
||||
|
||||
<!-- Navigation -->
|
||||
<nav class="w-full z-50 transition-all duration-300">
|
||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div class="flex justify-between items-center h-20">
|
||||
<!-- Logo -->
|
||||
<a href="/" class="flex-shrink-0 flex items-center gap-2 cursor-pointer">
|
||||
<img src="/assets/Plugin.png" alt="Plugin Compass" class="w-8 h-8">
|
||||
<span class="font-bold text-xl tracking-tight text-gray-800">Plugin<span
|
||||
class="text-green-700">Compass</span></span>
|
||||
</a>
|
||||
<div class="text-sm text-gray-600">
|
||||
Welcome to Plugin Compass
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<main class="flex-grow flex items-center justify-center px-4 py-12">
|
||||
<div class="w-full max-w-2xl">
|
||||
<!-- Success Message -->
|
||||
<div class="text-center mb-8">
|
||||
<div class="inline-flex items-center justify-center w-16 h-16 bg-green-100 rounded-full mb-4">
|
||||
<i class="fas fa-check text-green-600 text-2xl"></i>
|
||||
</div>
|
||||
<h1 class="text-3xl font-bold text-gray-900 mb-4">Subscription Activated!</h1>
|
||||
<p class="text-lg text-gray-700 mb-6">Your subscription has been successfully activated and you're ready to start building.</p>
|
||||
</div>
|
||||
|
||||
<!-- Subscription Details -->
|
||||
<div class="bg-white rounded-2xl p-8 shadow-xl border border-gray-100 mb-8" id="subscription-details">
|
||||
<div class="text-center">
|
||||
<div class="inline-flex items-center px-4 py-2 bg-green-100 text-green-800 rounded-full text-sm font-medium mb-4">
|
||||
<i class="fas fa-credit-card mr-2"></i>
|
||||
<span id="plan-display">Starter Plan</span>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-6 mt-6">
|
||||
<div class="text-center">
|
||||
<div class="text-sm text-gray-500 mb-1">Billing Cycle</div>
|
||||
<div class="font-semibold text-gray-900" id="billing-cycle">Monthly</div>
|
||||
</div>
|
||||
<div class="text-center">
|
||||
<div class="text-sm text-gray-500 mb-1">Currency</div>
|
||||
<div class="font-semibold text-gray-900" id="currency-display">USD</div>
|
||||
</div>
|
||||
<div class="text-center">
|
||||
<div class="text-sm text-gray-500 mb-1">Next Billing</div>
|
||||
<div class="font-semibold text-gray-900" id="next-billing">Loading...</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Action Buttons -->
|
||||
<div class="text-center space-y-4">
|
||||
<a href="/apps"
|
||||
class="inline-flex items-center px-8 py-3 bg-green-700 text-white font-bold rounded-xl hover:bg-green-600 transition-all shadow-lg shadow-green-700/20 hover:shadow-green-700/40 transform hover:-translate-y-0.5">
|
||||
<i class="fas fa-rocket mr-2"></i>
|
||||
Start Building Apps
|
||||
</a>
|
||||
|
||||
<div class="text-sm text-gray-600">
|
||||
<a href="/settings" class="text-green-700 hover:text-green-600 font-medium">
|
||||
Manage your subscription
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Getting Started Tips -->
|
||||
<div class="mt-12 bg-green-50 rounded-2xl p-6 border border-green-200">
|
||||
<h3 class="font-semibold text-gray-900 mb-3 flex items-center">
|
||||
<i class="fas fa-lightbulb text-green-600 mr-2"></i>
|
||||
What's Next?
|
||||
</h3>
|
||||
<ul class="space-y-2 text-sm text-gray-700">
|
||||
<li class="flex items-start">
|
||||
<i class="fas fa-check text-green-600 mr-2 mt-0.5"></i>
|
||||
<span>Create your first WordPress plugin using our AI builder</span>
|
||||
</li>
|
||||
<li class="flex items-start">
|
||||
<i class="fas fa-check text-green-600 mr-2 mt-0.5"></i>
|
||||
<span>Access premium templates and advanced features</span>
|
||||
</li>
|
||||
<li class="flex items-start">
|
||||
<i class="fas fa-check text-green-600 mr-2 mt-0.5"></i>
|
||||
<span>Export and deploy your plugins anytime</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<footer class="py-8 text-center text-gray-500 text-xs">
|
||||
<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>
|
||||
|
||||
<script>
|
||||
// Get session ID from URL parameters
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
const sessionId = urlParams.get('session_id') || urlParams.get('checkout_id') || urlParams.get('session');
|
||||
|
||||
async function confirmSubscription() {
|
||||
if (!sessionId) {
|
||||
// No session id present - attempt to verify status in the background then redirect to /apps
|
||||
showPending('Checking subscription status. This may take a few seconds...');
|
||||
|
||||
let attempts = 0;
|
||||
const maxAttempts = 8;
|
||||
const intervalMs = 2000;
|
||||
|
||||
async function checkStatusOnce() {
|
||||
attempts++;
|
||||
try {
|
||||
const resp = await fetch('/api/subscription/status', {
|
||||
method: 'GET',
|
||||
credentials: 'include'
|
||||
});
|
||||
if (resp.ok) {
|
||||
const statusData = await resp.json();
|
||||
if (statusData.hasActiveSubscription) {
|
||||
// Subscription is active - go to apps page
|
||||
window.location.href = '/apps';
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.warn('Subscription status check failed', err);
|
||||
}
|
||||
|
||||
if (attempts >= maxAttempts) {
|
||||
showError('Unable to confirm subscription automatically. Visit Settings to check or go to Apps.');
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
const intervalId = setInterval(async () => {
|
||||
const done = await checkStatusOnce();
|
||||
if (done) clearInterval(intervalId);
|
||||
}, intervalMs);
|
||||
|
||||
// Run an immediate check
|
||||
await checkStatusOnce();
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const authHeaders = getAuthHeaders();
|
||||
const response = await fetch(`/api/subscription/confirm?session_id=${encodeURIComponent(sessionId)}`, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
...authHeaders
|
||||
},
|
||||
credentials: 'include'
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (response.ok && data.ok) {
|
||||
updateLocalStorageUser(data);
|
||||
updateSubscriptionDisplay(data);
|
||||
} else if (data.pending) {
|
||||
showPending('Payment is still processing. Please wait...');
|
||||
} else {
|
||||
showError(data.error || 'Failed to activate subscription');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error confirming subscription:', error);
|
||||
showError('Network error. Please try again.');
|
||||
}
|
||||
}
|
||||
|
||||
// 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 {};
|
||||
}
|
||||
|
||||
function updateSubscriptionDisplay(data) {
|
||||
// Update plan display
|
||||
const planDisplay = document.getElementById('plan-display');
|
||||
if (planDisplay) {
|
||||
planDisplay.textContent = getPlanDisplayName(data.plan);
|
||||
}
|
||||
|
||||
// Update billing cycle
|
||||
const billingCycle = document.getElementById('billing-cycle');
|
||||
if (billingCycle) {
|
||||
billingCycle.textContent = data.billingCycle ?
|
||||
data.billingCycle.charAt(0).toUpperCase() + data.billingCycle.slice(1) : 'Monthly';
|
||||
}
|
||||
|
||||
// Update currency
|
||||
const currencyDisplay = document.getElementById('currency-display');
|
||||
if (currencyDisplay) {
|
||||
currencyDisplay.textContent = data.currency ?
|
||||
data.currency.toUpperCase() : 'USD';
|
||||
}
|
||||
|
||||
// Calculate next billing date
|
||||
const nextBilling = document.getElementById('next-billing');
|
||||
if (nextBilling && data.billingCycle) {
|
||||
const nextDate = calculateNextBillingDate(data.billingCycle);
|
||||
nextBilling.textContent = nextDate.toLocaleDateString();
|
||||
}
|
||||
|
||||
// Display payment method if available
|
||||
const paymentMethod = data.account?.paymentMethod;
|
||||
if (paymentMethod && paymentMethod.last4) {
|
||||
const planBadge = document.getElementById('plan-display')?.parentElement;
|
||||
if (planBadge) {
|
||||
const brand = paymentMethod.brand || 'Card';
|
||||
const last4 = paymentMethod.last4;
|
||||
planBadge.innerHTML = `
|
||||
<i class="fas fa-credit-card mr-2"></i>
|
||||
<span id="plan-display">${getPlanDisplayName(data.plan)}</span>
|
||||
<span class="ml-3 text-gray-500 text-sm">(${brand} ••••• ${last4})</span>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
// Track successful subscription
|
||||
if (window.posthog) {
|
||||
window.posthog.capture('subscription_activated', {
|
||||
plan: data.plan,
|
||||
billing_cycle: data.billingCycle,
|
||||
currency: data.currency
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function updateLocalStorageUser(data) {
|
||||
try {
|
||||
const userStr = localStorage.getItem('wordpress_plugin_ai_user') || localStorage.getItem('shopify_ai_user');
|
||||
if (userStr) {
|
||||
const user = JSON.parse(userStr);
|
||||
if (user && data.account) {
|
||||
user.plan = data.account.plan;
|
||||
user.billingStatus = data.account.billingStatus;
|
||||
user.billingCycle = data.account.billingCycle;
|
||||
user.subscriptionRenewsAt = data.account.subscriptionRenewsAt;
|
||||
user.billingEmail = data.account.billingEmail;
|
||||
localStorage.setItem('wordpress_plugin_ai_user', JSON.stringify(user));
|
||||
localStorage.setItem('shopify_ai_user', JSON.stringify(user));
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.warn('Failed to update localStorage user', err);
|
||||
}
|
||||
}
|
||||
|
||||
function showPending(message) {
|
||||
const subscriptionDetails = document.getElementById('subscription-details');
|
||||
if (subscriptionDetails) {
|
||||
subscriptionDetails.innerHTML = `
|
||||
<div class="text-center">
|
||||
<div class="inline-flex items-center justify-center w-16 h-16 bg-yellow-100 rounded-full mb-4">
|
||||
<i class="fas fa-clock text-yellow-600 text-2xl"></i>
|
||||
</div>
|
||||
<h2 class="text-xl font-semibold text-gray-900 mb-4">Processing Payment</h2>
|
||||
<p class="text-gray-600 mb-4">${message}</p>
|
||||
<button onclick="location.reload()" class="px-6 py-2 bg-green-700 text-white rounded-lg hover:bg-green-600 transition-colors">
|
||||
Refresh Status
|
||||
</button>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
function showError(message) {
|
||||
const subscriptionDetails = document.getElementById('subscription-details');
|
||||
if (subscriptionDetails) {
|
||||
subscriptionDetails.innerHTML = `
|
||||
<div class="text-center">
|
||||
<div class="inline-flex items-center justify-center w-16 h-16 bg-red-100 rounded-full mb-4">
|
||||
<i class="fas fa-exclamation-triangle text-red-600 text-2xl"></i>
|
||||
</div>
|
||||
<h2 class="text-xl font-semibold text-gray-900 mb-4">Subscription Error</h2>
|
||||
<p class="text-gray-600 mb-4">${message}</p>
|
||||
<div class="space-y-2">
|
||||
<a href="/select-plan" class="block px-6 py-2 bg-green-700 text-white rounded-lg hover:bg-green-600 transition-colors">
|
||||
Try Again
|
||||
</a>
|
||||
<a href="/contact" class="block px-6 py-2 bg-gray-200 text-gray-800 rounded-lg hover:bg-gray-300 transition-colors">
|
||||
Contact Support
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
function getPlanDisplayName(plan) {
|
||||
const planNames = {
|
||||
'hobby': 'Hobby Plan',
|
||||
'starter': 'Starter Plan',
|
||||
'professional': 'Professional Plan',
|
||||
'business': 'Professional Plan',
|
||||
'enterprise': 'Enterprise Plan'
|
||||
};
|
||||
return planNames[plan] || `${plan.charAt(0).toUpperCase() + plan.slice(1)} Plan`;
|
||||
}
|
||||
|
||||
function calculateNextBillingDate(billingCycle) {
|
||||
const now = new Date();
|
||||
if (billingCycle === 'yearly') {
|
||||
return new Date(now.getFullYear() + 1, now.getMonth(), now.getDate());
|
||||
} else {
|
||||
return new Date(now.getFullYear(), now.getMonth() + 1, now.getDate());
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize on page load
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
confirmSubscription();
|
||||
});
|
||||
</script>
|
||||
206
chat/public/templates.html
Normal file
@@ -0,0 +1,206 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Plugin Templates - 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&family=Inter:wght@400;600&display=swap"
|
||||
rel="stylesheet">
|
||||
<link rel="stylesheet" href="/chat/styles.css">
|
||||
<style>
|
||||
:root {
|
||||
--shopify-green: #008060;
|
||||
--shopify-green-dark: #004c3f;
|
||||
--shopify-green-light: #e3f5ef;
|
||||
--accent: #5A31F4;
|
||||
}
|
||||
|
||||
body {
|
||||
background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%);
|
||||
font-family: 'Inter', sans-serif;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 40px 24px;
|
||||
}
|
||||
|
||||
.header {
|
||||
text-align: center;
|
||||
margin-bottom: 60px;
|
||||
}
|
||||
|
||||
.header h1 {
|
||||
font-family: 'Space Grotesk', sans-serif;
|
||||
font-size: 48px;
|
||||
margin-bottom: 16px;
|
||||
color: #1a1a1a;
|
||||
}
|
||||
|
||||
.header p {
|
||||
color: #6c757d;
|
||||
font-size: 20px;
|
||||
max-width: 600px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.templates-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(350px, 1fr));
|
||||
gap: 32px;
|
||||
}
|
||||
|
||||
.template-card {
|
||||
background: white;
|
||||
border-radius: 20px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.05);
|
||||
transition: transform 0.2s, box-shadow 0.2s;
|
||||
border: 1px solid rgba(0, 0, 0, 0.05);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.template-card:hover {
|
||||
transform: translateY(-8px);
|
||||
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.card-image {
|
||||
height: 200px;
|
||||
background: #f1f3f5;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #adb5bd;
|
||||
font-size: 48px;
|
||||
}
|
||||
|
||||
.card-body {
|
||||
padding: 24px;
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.badge {
|
||||
display: inline-block;
|
||||
padding: 4px 12px;
|
||||
border-radius: 999px;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
background: var(--shopify-green-light);
|
||||
color: var(--shopify-green);
|
||||
margin-bottom: 12px;
|
||||
align-self: flex-start;
|
||||
}
|
||||
|
||||
.card-title {
|
||||
font-family: 'Space Grotesk', sans-serif;
|
||||
font-size: 24px;
|
||||
font-weight: 700;
|
||||
margin: 0 0 12px 0;
|
||||
color: #1a1a1a;
|
||||
}
|
||||
|
||||
.card-desc {
|
||||
color: #6c757d;
|
||||
line-height: 1.6;
|
||||
margin-bottom: 24px;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.card-actions {
|
||||
border-top: 1px solid #f1f3f5;
|
||||
padding-top: 20px;
|
||||
margin-top: auto;
|
||||
}
|
||||
|
||||
.use-template-btn {
|
||||
display: block;
|
||||
width: 100%;
|
||||
background: #1a1a1a;
|
||||
color: white;
|
||||
text-align: center;
|
||||
padding: 14px;
|
||||
border-radius: 12px;
|
||||
text-decoration: none;
|
||||
font-weight: 600;
|
||||
transition: background 0.2s;
|
||||
}
|
||||
|
||||
.use-template-btn:hover {
|
||||
background: var(--shopify-green);
|
||||
}
|
||||
|
||||
.loading {
|
||||
text-align: center;
|
||||
padding: 60px;
|
||||
font-size: 18px;
|
||||
color: #adb5bd;
|
||||
}
|
||||
</style>
|
||||
|
||||
<!-- PostHog Analytics -->
|
||||
<script src="/posthog.js"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="container">
|
||||
<div style="margin-bottom: 40px;">
|
||||
<a href="/" style="text-decoration: none; color: #6c757d; font-weight: 500;">← Back to Home</a>
|
||||
</div>
|
||||
|
||||
<div class="header">
|
||||
<h1>Start with a Template</h1>
|
||||
<p>Jumpstart your development with our professionally crafted plugin templates.</p>
|
||||
</div>
|
||||
|
||||
<div class="templates-grid" id="templates-grid">
|
||||
<div class="loading">Loading templates...</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
async function loadTemplates() {
|
||||
try {
|
||||
const res = await fetch('/api/templates');
|
||||
const data = await res.json();
|
||||
const grid = document.getElementById('templates-grid');
|
||||
|
||||
if (!data.templates || !data.templates.length) {
|
||||
grid.innerHTML = '<div style="grid-column: 1/-1; text-align: center;">No templates found.</div>';
|
||||
return;
|
||||
}
|
||||
|
||||
grid.innerHTML = data.templates.map(t => `
|
||||
<div class="template-card">
|
||||
<div class="card-image">
|
||||
${t.image && !t.image.includes('placeholder') ? `<img src="${t.image}" style="width:100%; height:100%; object-fit:cover;">` : '🧩'}
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="badge">${t.category || 'General'}</div>
|
||||
<h3 class="card-title">${t.name}</h3>
|
||||
<p class="card-desc">${t.description}</p>
|
||||
<div class="card-actions">
|
||||
<a href="/apps?template=${t.id}" class="use-template-btn">Use Template</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`).join('');
|
||||
} catch (err) {
|
||||
document.getElementById('templates-grid').innerHTML = '<div style="color:red; text-align:center;">Failed to load templates</div>';
|
||||
}
|
||||
}
|
||||
|
||||
loadTemplates();
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
514
chat/public/terms.html
Normal file
@@ -0,0 +1,514 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" class="scroll-smooth">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Terms of Service | Plugin Compass</title>
|
||||
<link rel="icon" type="image/png" href="/assets/Plugin.png">
|
||||
<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">
|
||||
<style>
|
||||
body {
|
||||
font-family: 'Inter', system-ui, -apple-system, sans-serif;
|
||||
}
|
||||
|
||||
.hero-gradient {
|
||||
background: linear-gradient(135deg, #004225, #006b3d);
|
||||
}
|
||||
|
||||
.glass-nav {
|
||||
background: rgba(251, 246, 239, 0.9);
|
||||
backdrop-filter: blur(12px);
|
||||
-webkit-backdrop-filter: blur(12px);
|
||||
border-bottom: 1px solid rgba(0, 66, 37, 0.1);
|
||||
}
|
||||
</style>
|
||||
<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=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
|
||||
|
||||
<!-- PostHog Analytics -->
|
||||
<script src="/posthog.js"></script>
|
||||
</head>
|
||||
|
||||
<body class="bg-amber-50 text-gray-900 antialiased">
|
||||
|
||||
<!-- Navigation -->
|
||||
<nav class="fixed w-full z-50 glass-nav transition-all duration-300" id="navbar">
|
||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div class="flex justify-between items-center h-20">
|
||||
<!-- Logo -->
|
||||
<a href="/" class="flex-shrink-0 flex items-center gap-2 cursor-pointer">
|
||||
<img src="/assets/Plugin.png" alt="Plugin Compass" class="w-8 h-8">
|
||||
<span class="font-bold text-xl tracking-tight text-gray-800">Plugin<span
|
||||
class="text-green-700">Compass</span></span>
|
||||
</a>
|
||||
|
||||
<!-- Desktop Menu -->
|
||||
<div class="hidden md:flex items-center space-x-8">
|
||||
<a href="/features"
|
||||
class="text-gray-700 hover:text-gray-900 transition-colors text-sm font-medium">Features</a>
|
||||
<a href="/pricing"
|
||||
class="text-gray-700 hover:text-gray-900 transition-colors text-sm font-medium">Pricing</a>
|
||||
<a href="/docs"
|
||||
class="text-gray-700 hover:text-gray-900 transition-colors text-sm font-medium">Docs</a>
|
||||
<a href="/faq"
|
||||
class="text-gray-700 hover:text-gray-900 transition-colors text-sm font-medium">FAQ</a>
|
||||
</div>
|
||||
|
||||
<!-- CTA Buttons -->
|
||||
<div class="hidden md:flex items-center gap-4">
|
||||
<a href="/login"
|
||||
class="text-gray-700 hover:text-gray-900 font-medium text-sm transition-colors">Sign In</a>
|
||||
<a href="/signup"
|
||||
class="bg-green-700 hover:bg-green-600 text-white px-5 py-2.5 rounded-full font-medium text-sm transition-all shadow-lg shadow-green-700/20 hover:shadow-green-700/40 transform hover:-translate-y-0.5">
|
||||
Get Started
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<!-- Mobile Menu Button -->
|
||||
<div class="md:hidden flex items-center">
|
||||
<button id="mobile-menu-btn" class="text-gray-700 hover:text-gray-900 focus:outline-none">
|
||||
<i class="fa-solid fa-bars text-xl"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Mobile Menu Panel -->
|
||||
<div id="mobile-menu" class="hidden md:hidden bg-amber-50 border-b border-amber-200">
|
||||
<div class="px-4 pt-2 pb-6 space-y-1">
|
||||
<a href="/features"
|
||||
class="block px-3 py-3 text-base font-medium text-gray-700 hover:text-gray-900 hover:bg-amber-100 rounded-md">Features</a>
|
||||
<a href="/pricing"
|
||||
class="block px-3 py-3 text-base font-medium text-gray-700 hover:text-gray-900 hover:bg-amber-100 rounded-md">Pricing</a>
|
||||
<a href="/docs"
|
||||
class="block px-3 py-3 text-base font-medium text-gray-700 hover:text-gray-900 hover:bg-amber-100 rounded-md">Docs</a>
|
||||
<a href="/faq"
|
||||
class="block px-3 py-3 text-base font-medium text-gray-700 hover:text-gray-900 hover:bg-amber-100 rounded-md">FAQ</a>
|
||||
<div class="pt-4 flex flex-col gap-3">
|
||||
<a href="/login" class="w-full text-center py-2 text-gray-700 font-medium">Sign In</a>
|
||||
<a href="/signup" class="w-full bg-green-700 text-white text-center py-3 rounded-lg font-medium">Get
|
||||
Started</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<header class="hero-gradient text-white pt-32 pb-12">
|
||||
<div class="max-w-6xl mx-auto px-4">
|
||||
<h1 class="text-5xl font-bold mb-4">Terms of Service</h1>
|
||||
<p class="text-xl text-green-50 max-w-3xl">Please read these terms carefully before using our AI-powered
|
||||
Wordpress Plugin building service.</p>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main class="max-w-6xl mx-auto px-4 py-16">
|
||||
<div class="bg-white rounded-2xl shadow-sm border border-gray-100 p-8 md:p-12 space-y-8">
|
||||
|
||||
<section>
|
||||
<h2 class="text-2xl font-bold text-gray-900 mb-4">1. Acceptance of Terms</h2>
|
||||
<p class="text-gray-700 leading-relaxed">
|
||||
By accessing or using Plugin Compass, you agree to be bound by these Terms of Service ("Terms"). If
|
||||
you disagree with any part of the terms, you may not access the Service.
|
||||
</p>
|
||||
<p class="text-gray-700 leading-relaxed mt-3">
|
||||
These Terms apply to all visitors, users, and others who wish to access or use the Service.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2 class="text-2xl font-bold text-gray-900 mb-4">2. Description of Service</h2>
|
||||
<p class="text-gray-700 leading-relaxed">
|
||||
Plugin Compass is an AI-powered platform that enables users to build custom Wordpress plugins. Our
|
||||
Service includes AI-assisted code generation, workspace management, and app deployment tools
|
||||
specifically designed for the Wordpress ecosystem.
|
||||
</p>
|
||||
<ul class="list-disc list-inside space-y-2 text-gray-700 mt-3">
|
||||
<li>Users can generate code using natural language prompts</li>
|
||||
<li>All generated code is owned by the user upon creation</li>
|
||||
<li>We provide hosting for the development environment only</li>
|
||||
<li>Users are responsible for deploying and maintaining their own apps</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2 class="text-2xl font-bold text-gray-900 mb-4">3. User Accounts</h2>
|
||||
<h3 class="text-xl font-semibold text-gray-900 mb-3">Registration</h3>
|
||||
<p class="text-gray-700 leading-relaxed">
|
||||
You must provide accurate and complete information when creating an account. You are responsible for
|
||||
maintaining the confidentiality of your account and password.
|
||||
</p>
|
||||
|
||||
<h3 class="text-xl font-semibold text-gray-900 mb-3 mt-4">Account Security</h3>
|
||||
<p class="text-gray-700 leading-relaxed">
|
||||
You agree to notify us immediately of any unauthorized access to or use of your account. We are not
|
||||
liable for any loss or damage arising from your failure to protect your account.
|
||||
</p>
|
||||
|
||||
<h3 class="text-xl font-semibold text-gray-900 mb-3 mt-4">Account Termination</h3>
|
||||
<p class="text-gray-700 leading-relaxed">
|
||||
We reserve the right to suspend or terminate your account if you violate these Terms or engage in
|
||||
behavior that harms other users or the Service.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2 class="text-2xl font-bold text-gray-900 mb-4">4. User Content and Ownership</h2>
|
||||
<h3 class="text-xl font-semibold text-gray-900 mb-3">Your Content</h3>
|
||||
<p class="text-gray-700 leading-relaxed">
|
||||
You retain all ownership rights to the code, apps, and content you generate using our Service ("User
|
||||
Content"). We do not claim any ownership interest in your User Content.
|
||||
</p>
|
||||
|
||||
<h3 class="text-xl font-semibold text-gray-900 mb-3 mt-4">License to Plugin Compass</h3>
|
||||
<p class="text-gray-700 leading-relaxed">
|
||||
By using the Service, you grant us a limited, non-exclusive, worldwide license to store, process,
|
||||
and display your User Content solely for the purpose of providing the Service to you.
|
||||
</p>
|
||||
|
||||
<h3 class="text-xl font-semibold text-gray-900 mb-3 mt-4">Code Quality and Liability</h3>
|
||||
<p class="text-gray-700 leading-relaxed">
|
||||
<strong>Important:</strong> AI-generated Wordpress Plugin code may contain errors, security
|
||||
vulnerabilities, or inefficiencies. You are solely responsible for:
|
||||
</p>
|
||||
<ul class="list-disc list-inside space-y-2 text-gray-700 mt-2">
|
||||
<li>Reviewing, testing, and validating all generated code</li>
|
||||
<li>Ensuring code meets Wordpress's security and quality standards</li>
|
||||
<li>Complying with Wordpress's API terms and developer policies</li>
|
||||
<li>Obtaining necessary third-party licenses for dependencies</li>
|
||||
<li>Following Wordpress plugin store guidelines if publishing your plugin</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2 class="text-2xl font-bold text-gray-900 mb-4">5. Acceptable Use</h2>
|
||||
<h3 class="text-xl font-semibold text-gray-900 mb-3">Prohibited Activities</h3>
|
||||
<p class="text-gray-700 leading-relaxed">You agree not to:</p>
|
||||
<ul class="list-disc list-inside space-y-2 text-gray-700">
|
||||
<li>Use the Service for any illegal purpose or in violation of any local, state, national, or
|
||||
international law</li>
|
||||
<li>Generate or distribute malware, viruses, or harmful code for Wordpress Plugin</li>
|
||||
<li>Attempt to reverse engineer, decompile, or hack the Service</li>
|
||||
<li>Use the Service to generate spam, phishing, or fraudulent content</li>
|
||||
<li>Create Shopify apps that violate Wordpress's terms of service or policies</li>
|
||||
<li>Impersonate any person or entity or misrepresent your affiliation with any entity</li>
|
||||
<li>Interfere with or disrupt the integrity or performance of the Service</li>
|
||||
<li>Use AI models in ways that violate their terms of service or usage policies</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2 class="text-2xl font-bold text-gray-900 mb-4">6. Payments and Subscriptions</h2>
|
||||
<h3 class="text-xl font-semibold text-gray-900 mb-3">Paid Plans</h3>
|
||||
<p class="text-gray-700 leading-relaxed">
|
||||
We offer paid subscription plans with various features and usage limits. By subscribing to a paid
|
||||
plan, you agree to pay all applicable fees.
|
||||
</p>
|
||||
|
||||
<h3 class="text-xl font-semibold text-gray-900 mb-3 mt-4">Billing and Cancellation</h3>
|
||||
<p class="text-gray-700 leading-relaxed">
|
||||
<strong>Billing Cycle:</strong> Paid subscriptions are billed in advance on a monthly or annual
|
||||
basis, depending on your selection.
|
||||
</p>
|
||||
<p class="text-gray-700 leading-relaxed mt-2">
|
||||
<strong>Cancellation:</strong> You may cancel your subscription at any time. You will continue to
|
||||
have access to paid features until the end of your current billing period.
|
||||
</p>
|
||||
<p class="text-gray-700 leading-relaxed mt-2">
|
||||
<strong>No Refunds:</strong> Except as required by law or as otherwise stated, payments are
|
||||
non-refundable.
|
||||
</p>
|
||||
|
||||
<h3 class="text-xl font-semibold text-gray-900 mb-3 mt-4">Plan Changes</h3>
|
||||
<p class="text-gray-700 leading-relaxed">
|
||||
You can change your plan at any time. When upgrading, new features are available immediately. When
|
||||
downgrading, changes take effect at your next billing cycle.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2 class="text-2xl font-bold text-gray-900 mb-4">7. AI Model Usage</h2>
|
||||
<h3 class="text-xl font-semibold text-gray-900 mb-3">Third-Party AI Providers</h3>
|
||||
<p class="text-gray-700 leading-relaxed">
|
||||
Our Service utilizes AI models from third-party providers such as OpenRouter and Anthropic. Your
|
||||
prompts and generated content are processed according to these providers' terms of service and
|
||||
privacy policies.
|
||||
</p>
|
||||
|
||||
<h3 class="text-xl font-semibold text-gray-900 mb-3 mt-4">Usage Limits</h3>
|
||||
<p class="text-gray-700 leading-relaxed">
|
||||
Your subscription plan includes specific usage limits for AI credits and API calls. Usage beyond
|
||||
these limits may require additional payment or plan upgrades.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2 class="text-2xl font-bold text-gray-900 mb-4">8. Intellectual Property</h2>
|
||||
<h3 class="text-xl font-semibold text-gray-900 mb-3">Plugin Compass Intellectual Property</h3>
|
||||
<p class="text-gray-700 leading-relaxed">
|
||||
The Service and its original content, features, and functionality are owned by Plugin Compass and
|
||||
are protected by international copyright, trademark, patent, trade secret, and other intellectual
|
||||
property laws.
|
||||
</p>
|
||||
|
||||
<h3 class="text-xl font-semibold text-gray-900 mb-3 mt-4">Trademarks</h3>
|
||||
<p class="text-gray-700 leading-relaxed">
|
||||
Plugin Compass and related marks are trademarks of Plugin Compass. You may not use these trademarks
|
||||
without our prior written permission.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2 class="text-2xl font-bold text-gray-900 mb-4">9. Disclaimer of Warranties</h2>
|
||||
<h3 class="text-xl font-semibold text-gray-900 mb-3">"As Is" Basis</h3>
|
||||
<p class="text-gray-700 leading-relaxed">
|
||||
THE SERVICE IS PROVIDED ON AN "AS IS" AND "AS AVAILABLE" BASIS, WITHOUT WARRANTIES OF ANY KIND,
|
||||
EITHER EXPRESS OR IMPLIED, INCLUDING, WITHOUT LIMITATION, IMPLIED WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT.
|
||||
</p>
|
||||
|
||||
<h3 class="text-xl font-semibold text-gray-900 mb-3 mt-4">AI-Generated Content</h3>
|
||||
<p class="text-gray-700 leading-relaxed">
|
||||
We do not warrant that AI-generated code will be error-free, secure, or suitable for your intended
|
||||
purpose. You assume all risk associated with the use of generated code.
|
||||
</p>
|
||||
|
||||
<h3 class="text-xl font-semibold text-gray-900 mb-3 mt-4">Service Availability</h3>
|
||||
<p class="text-gray-700 leading-relaxed">
|
||||
We do not warrant that the Service will be uninterrupted, timely, secure, or error-free. We may
|
||||
suspend or discontinue any part of the Service at any time without notice.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2 class="text-2xl font-bold text-gray-900 mb-4">10. Limitation of Liability</h2>
|
||||
<p class="text-gray-700 leading-relaxed">
|
||||
TO THE FULLEST EXTENT PERMITTED BY APPLICABLE LAW, PLUGIN COMPASS AND ITS AFFILIATES, OFFICERS,
|
||||
EMPLOYEES, AGENTS, SUPPLIERS, OR LICENSORS SHALL NOT BE LIABLE FOR ANY INDIRECT, INCIDENTAL,
|
||||
SPECIAL, CONSEQUENTIAL, OR PUNITIVE DAMAGES, INCLUDING WITHOUT LIMITATION, LOSS OF PROFITS, DATA,
|
||||
USE, GOODWILL, OR OTHER INTANGIBLE LOSSES, RESULTING FROM:
|
||||
</p>
|
||||
<ul class="list-disc list-inside space-y-2 text-gray-700 mt-3">
|
||||
<li>YOUR ACCESS TO OR USE OF OR INABILITY TO ACCESS OR USE THE SERVICE</li>
|
||||
<li>ANY CONDUCT OR CONTENT OF ANY THIRD PARTY ON THE SERVICE</li>
|
||||
<li>ANY CONTENT OBTAINED FROM THE SERVICE</li>
|
||||
<li>UNAUTHORIZED ACCESS, USE, OR ALTERATION OF YOUR TRANSMISSIONS OR CONTENT</li>
|
||||
<li>ERRORS, VULNERABILITIES, OR FAILURES IN AI-GENERATED CODE</li>
|
||||
</ul>
|
||||
<p class="text-gray-700 leading-relaxed mt-4">
|
||||
IN NO EVENT SHALL OUR AGGREGATE LIABILITY EXCEED THE GREATER OF (A) THE AMOUNT YOU PAID FOR THE
|
||||
SERVICE IN THE PREVIOUS THREE MONTHS, OR (B) $100.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2 class="text-2xl font-bold text-gray-900 mb-4">11. Indemnification</h2>
|
||||
<p class="text-gray-700 leading-relaxed">
|
||||
You agree to indemnify, defend, and hold harmless Shopify AI App Builder and its affiliates,
|
||||
officers, directors, employees, and agents from and against any claims, liabilities, damages,
|
||||
judgments, awards, losses, costs, expenses, or fees arising out of or relating to:
|
||||
</p>
|
||||
<ul class="list-disc list-inside space-y-2 text-gray-700 mt-3">
|
||||
<li>Your use of the Service</li>
|
||||
<li>Your User Content</li>
|
||||
<li>Your violation of these Terms</li>
|
||||
<li>Your violation of any third-party rights, including intellectual property rights</li>
|
||||
<li>Your use of AI-generated code in production environments</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2 class="text-2xl font-bold text-gray-900 mb-4">12. Governing Law and Dispute Resolution</h2>
|
||||
<h3 class="text-xl font-semibold text-gray-900 mb-3">Governing Law</h3>
|
||||
<p class="text-gray-700 leading-relaxed">
|
||||
These Terms shall be governed by and construed in accordance with the laws of the State of
|
||||
California, without regard to its conflict of law provisions.
|
||||
</p>
|
||||
|
||||
<h3 class="text-xl font-semibold text-gray-900 mb-3 mt-4">Arbitration</h3>
|
||||
<p class="text-gray-700 leading-relaxed">
|
||||
Any dispute arising from or relating to these Terms or the Service shall be resolved through binding
|
||||
arbitration in San Francisco, California, in accordance with the rules of the American Arbitration
|
||||
Association.
|
||||
</p>
|
||||
|
||||
<h3 class="text-xl font-semibold text-gray-900 mb-3 mt-4">Class Action Waiver</h3>
|
||||
<p class="text-gray-700 leading-relaxed">
|
||||
You agree that any arbitration or proceeding shall be limited to the dispute between us and you
|
||||
individually. You acknowledge and agree that you waive your right to participate as a class
|
||||
representative or class member in any class action lawsuit.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2 class="text-2xl font-bold text-gray-900 mb-4">13. Modifications to Terms</h2>
|
||||
<p class="text-gray-700 leading-relaxed">
|
||||
We reserve the right to modify these Terms at any time. We will provide notice of material changes
|
||||
through the Service or by email. Your continued use of the Service after changes become effective
|
||||
constitutes your acceptance of the modified Terms.
|
||||
</p>
|
||||
<p class="text-sm text-gray-600 mt-2">
|
||||
Last updated: January 1, 2026
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2 class="text-2xl font-bold text-gray-900 mb-4">14. Contact Information</h2>
|
||||
<p class="text-gray-700 leading-relaxed">
|
||||
If you have questions about these Terms, please contact us:
|
||||
</p>
|
||||
<div class="mt-4 space-y-1 text-gray-700">
|
||||
<p><strong>Email:</strong> info@plugincompass.com</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<!-- CTA Footer -->
|
||||
<section class="py-24 bg-amber-50">
|
||||
<div class="max-w-5xl mx-auto px-4 sm:px-6 lg:px-8 text-center">
|
||||
<h2 class="text-4xl font-bold mb-8 text-gray-900">Build Your Custom Plugin Today</h2>
|
||||
<p class="text-xl text-gray-700 mb-10">Start building WordPress plugins that fit your exact needs. No coding
|
||||
experience required.</p>
|
||||
<a href="/signup"
|
||||
class="inline-flex items-center justify-center px-8 py-4 bg-green-700 text-white rounded-full font-bold hover:bg-green-600 transition-all shadow-xl shadow-green-700/20">
|
||||
Get Started Free <i class="fa-solid fa-arrow-right ml-2"></i>
|
||||
</a>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Footer -->
|
||||
<footer class="bg-white border-t border-green-200 pt-16 pb-8">
|
||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div class="grid grid-cols-2 lg:grid-cols-5 gap-12 mb-16">
|
||||
<div class="col-span-2 lg:col-span-1">
|
||||
<div class="flex items-center gap-2 mb-6">
|
||||
<img src="/assets/Plugin.png" alt="Plugin Compass" class="w-8 h-8">
|
||||
<span class="font-bold text-xl tracking-tight text-gray-800">Plugin<span
|
||||
class="text-green-700">Compass</span></span>
|
||||
</div>
|
||||
<p class="text-gray-600 text-sm leading-relaxed">
|
||||
The smart way for WordPress site owners to replace expensive plugin subscriptions with custom
|
||||
solutions. Save thousands monthly.
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<h4 class="font-bold text-gray-900 mb-6">Product</h4>
|
||||
<ul class="space-y-4 text-sm">
|
||||
<li><a href="/features" class="text-gray-600 hover:text-green-700">Features</a></li>
|
||||
<li><a href="/pricing" class="text-gray-600 hover:text-green-700">Pricing</a></li>
|
||||
<li><a href="/templates.html" class="text-gray-600 hover:text-green-700">Templates</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div>
|
||||
<h4 class="font-bold text-gray-900 mb-6">Resources</h4>
|
||||
<ul class="space-y-4 text-sm">
|
||||
<li><a href="/docs" class="text-gray-600 hover:text-green-700">Documentation</a></li>
|
||||
<li><a href="/faq" class="text-gray-600 hover:text-green-700">FAQ</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div>
|
||||
<h4 class="font-bold text-gray-900 mb-6">Legal</h4>
|
||||
<ul class="space-y-4 text-sm">
|
||||
<li><a href="/privacy.html" class="text-gray-600 hover:text-green-700">Privacy Policy</a></li>
|
||||
<li><a href="/terms" class="text-gray-600 hover:text-green-700">Terms of Service</a></li>
|
||||
<li><a href="/contact" class="text-gray-600 hover:text-green-700">Contact Us</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="col-span-2 lg:col-span-1">
|
||||
<h4 class="font-bold text-gray-900 mb-6">Stay Updated</h4>
|
||||
<p class="text-gray-600 text-sm mb-4">Get the latest updates and WordPress tips.</p>
|
||||
<form id="footer-signup-form" class="flex flex-col gap-2">
|
||||
<input type="email" name="email" placeholder="Your email" required
|
||||
class="px-4 py-2 border border-green-200 rounded-lg focus:outline-none focus:ring-2 focus:ring-green-700/20 text-sm">
|
||||
<button type="submit"
|
||||
class="bg-green-700 hover:bg-green-600 text-white px-4 py-2 rounded-lg font-medium text-sm transition-colors shadow-lg shadow-green-700/10">
|
||||
Subscribe
|
||||
</button>
|
||||
</form>
|
||||
<div id="signup-message" class="mt-2 text-xs hidden"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="border-t border-gray-100 pt-8 flex justify-center">
|
||||
<p class="text-gray-500 text-xs text-center">© 2026 Plugin Compass. All rights reserved.</p>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<script>
|
||||
const mobileMenuBtn = document.getElementById('mobile-menu-btn');
|
||||
const mobileMenu = document.getElementById('mobile-menu');
|
||||
|
||||
if (mobileMenuBtn && mobileMenu) {
|
||||
mobileMenuBtn.addEventListener('click', () => {
|
||||
mobileMenu.classList.toggle('hidden');
|
||||
});
|
||||
}
|
||||
|
||||
// Navbar scroll effect
|
||||
window.addEventListener('scroll', () => {
|
||||
const navbar = document.getElementById('navbar');
|
||||
if (!navbar) return;
|
||||
if (window.scrollY > 20) {
|
||||
navbar.classList.add('shadow-md', 'h-16');
|
||||
navbar.classList.remove('h-20');
|
||||
} else {
|
||||
navbar.classList.remove('shadow-md', 'h-16');
|
||||
navbar.classList.add('h-20');
|
||||
}
|
||||
});
|
||||
|
||||
// Email Signup Form Handler
|
||||
const signupForm = document.getElementById('footer-signup-form');
|
||||
const signupMessage = document.getElementById('signup-message');
|
||||
|
||||
if (signupForm) {
|
||||
signupForm.addEventListener('submit', async (e) => {
|
||||
e.preventDefault();
|
||||
const email = signupForm.querySelector('input[name="email"]').value;
|
||||
const button = signupForm.querySelector('button');
|
||||
|
||||
button.disabled = true;
|
||||
button.textContent = 'Subscribing...';
|
||||
|
||||
try {
|
||||
const response = await fetch('https://emailmarketing.modelrailway3d.co.uk/api/webhooks/incoming/wh_0Z49zi_DGj4-lKJMOPO8-g', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
email: email,
|
||||
source: 'plugin_compass_footer',
|
||||
timestamp: new Date().toISOString()
|
||||
})
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
signupMessage.textContent = 'Successfully subscribed!';
|
||||
signupMessage.className = 'mt-2 text-xs text-green-600';
|
||||
signupForm.reset();
|
||||
} else {
|
||||
throw new Error('Failed to subscribe');
|
||||
}
|
||||
} catch (error) {
|
||||
signupMessage.textContent = 'Failed to subscribe. Please try again.';
|
||||
signupMessage.className = 'mt-2 text-xs text-red-600';
|
||||
} finally {
|
||||
signupMessage.classList.remove('hidden');
|
||||
button.disabled = false;
|
||||
button.textContent = 'Subscribe';
|
||||
|
||||
setTimeout(() => {
|
||||
signupMessage.classList.add('hidden');
|
||||
}, 5000);
|
||||
}
|
||||
});
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
1023
chat/public/test-checkout.html
Normal file
142
chat/public/test-dropdown.html
Normal file
@@ -0,0 +1,142 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Model Dropdown Test</title>
|
||||
<style>
|
||||
body { font-family: Arial, sans-serif; padding: 20px; }
|
||||
.test-container { max-width: 600px; margin: 0 auto; }
|
||||
.model-select {
|
||||
position: relative;
|
||||
display: inline-flex;
|
||||
padding: 10px;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
background: white;
|
||||
}
|
||||
.model-select-dropdown {
|
||||
position: absolute;
|
||||
top: calc(100% + 8px);
|
||||
left: 0;
|
||||
background: white;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 8px;
|
||||
padding: 8px;
|
||||
display: none;
|
||||
min-width: 280px;
|
||||
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
|
||||
}
|
||||
.model-option {
|
||||
padding: 10px;
|
||||
cursor: pointer;
|
||||
border-radius: 4px;
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
}
|
||||
.model-option:hover {
|
||||
background: #f0f0f0;
|
||||
}
|
||||
.model-option.selected {
|
||||
background: #e0f0ff;
|
||||
}
|
||||
.model-icon { width: 20px; height: 20px; }
|
||||
.model-text { flex: 1; }
|
||||
.status { margin-top: 20px; padding: 10px; background: #f0f0f0; border-radius: 4px; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="test-container">
|
||||
<h1>Model Dropdown Test</h1>
|
||||
<p>Testing the model selector dropdown functionality</p>
|
||||
|
||||
<div style="position: relative;">
|
||||
<div id="model-select-btn" class="model-select">
|
||||
<span id="model-select-text">Select model</span>
|
||||
<span style="margin-left: 8px;">▼</span>
|
||||
</div>
|
||||
|
||||
<div id="model-select-dropdown" class="model-select-dropdown">
|
||||
<div id="model-select-options"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="status">
|
||||
<strong>Status:</strong> <span id="status">Loading models...</span>
|
||||
</div>
|
||||
|
||||
<div class="status">
|
||||
<strong>Selected:</strong> <span id="selected">None</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const btn = document.getElementById('model-select-btn');
|
||||
const dropdown = document.getElementById('model-select-dropdown');
|
||||
const options = document.getElementById('model-select-options');
|
||||
const textSpan = document.getElementById('model-select-text');
|
||||
const status = document.getElementById('status');
|
||||
const selected = document.getElementById('selected');
|
||||
|
||||
let isOpen = false;
|
||||
|
||||
// Toggle dropdown
|
||||
btn.addEventListener('click', () => {
|
||||
isOpen = !isOpen;
|
||||
dropdown.style.display = isOpen ? 'block' : 'none';
|
||||
});
|
||||
|
||||
// Close when clicking outside
|
||||
document.addEventListener('click', (e) => {
|
||||
if (!btn.contains(e.target) && !dropdown.contains(e.target)) {
|
||||
isOpen = false;
|
||||
dropdown.style.display = 'none';
|
||||
}
|
||||
});
|
||||
|
||||
// Fetch models from API
|
||||
fetch('http://localhost:4000/api/models')
|
||||
.then(res => res.json())
|
||||
.then(data => {
|
||||
if (data.models && data.models.length > 0) {
|
||||
status.textContent = `Loaded ${data.models.length} models`;
|
||||
|
||||
// Render options
|
||||
data.models.forEach(model => {
|
||||
const option = document.createElement('div');
|
||||
option.className = 'model-option';
|
||||
option.innerHTML = `
|
||||
<span class="model-text">${model.label || model.name}</span>
|
||||
<span style="color: #666; font-size: 12px;">${model.multiplier || 1}x</span>
|
||||
`;
|
||||
|
||||
option.addEventListener('click', () => {
|
||||
textSpan.textContent = model.label || model.name;
|
||||
selected.textContent = model.name;
|
||||
isOpen = false;
|
||||
dropdown.style.display = 'none';
|
||||
|
||||
// Mark as selected
|
||||
options.querySelectorAll('.model-option').forEach(opt => {
|
||||
opt.classList.remove('selected');
|
||||
});
|
||||
option.classList.add('selected');
|
||||
});
|
||||
|
||||
options.appendChild(option);
|
||||
});
|
||||
} else {
|
||||
status.textContent = 'No models configured';
|
||||
const placeholder = document.createElement('div');
|
||||
placeholder.className = 'model-option';
|
||||
placeholder.style.cursor = 'default';
|
||||
placeholder.textContent = 'No models available';
|
||||
options.appendChild(placeholder);
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
status.textContent = `Error: ${err.message}`;
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
190
chat/public/test-upload.html
Normal file
@@ -0,0 +1,190 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Test Upload Media Button</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
padding: 20px;
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
.test-section {
|
||||
border: 1px solid #ccc;
|
||||
padding: 20px;
|
||||
margin: 20px 0;
|
||||
border-radius: 8px;
|
||||
}
|
||||
.composer-actions {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
label.ghost {
|
||||
padding: 10px 16px;
|
||||
background: #f0f0f0;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
}
|
||||
label.ghost:hover {
|
||||
background: #e0e0e0;
|
||||
}
|
||||
button {
|
||||
padding: 10px 16px;
|
||||
background: #008060;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
}
|
||||
#status {
|
||||
margin-top: 15px;
|
||||
padding: 10px;
|
||||
background: #f8f9fa;
|
||||
border-radius: 4px;
|
||||
min-height: 30px;
|
||||
}
|
||||
.attachment-preview {
|
||||
margin-top: 10px;
|
||||
padding: 10px;
|
||||
background: #f0f0f0;
|
||||
border-radius: 4px;
|
||||
display: none;
|
||||
}
|
||||
.attachment-preview.active {
|
||||
display: block;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Upload Media Button Test</h1>
|
||||
|
||||
<div class="test-section">
|
||||
<h2>Test 1: Free Plan User (Should show upgrade modal)</h2>
|
||||
<p>Account Plan: <strong id="plan1">hobby</strong></p>
|
||||
<div class="composer-actions">
|
||||
<span id="upload-media-btn-1" class="ghost" title="Upload an image">Upload media</span>
|
||||
<input id="upload-media-input-1" type="file" accept="image/*" multiple style="display:none" />
|
||||
<button onclick="alert('Send button clicked')">Send</button>
|
||||
</div>
|
||||
<div id="attachment-preview-1" class="attachment-preview"></div>
|
||||
<div id="status-1" style="margin-top: 15px; padding: 10px; background: #f8f9fa; border-radius: 4px;"></div>
|
||||
</div>
|
||||
|
||||
<div class="test-section">
|
||||
<h2>Test 2: Paid Plan User (Should open file picker)</h2>
|
||||
<p>Account Plan: <strong id="plan2">business</strong></p>
|
||||
<div class="composer-actions">
|
||||
<span id="upload-media-btn-2" class="ghost" title="Upload an image">Upload media</span>
|
||||
<input id="upload-media-input-2" type="file" accept="image/*" multiple style="display:none" />
|
||||
<button onclick="alert('Send button clicked')">Send</button>
|
||||
</div>
|
||||
<div id="attachment-preview-2" class="attachment-preview"></div>
|
||||
<div id="status-2" style="margin-top: 15px; padding: 10px; background: #f8f9fa; border-radius: 4px;"></div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// Mock state object
|
||||
const state1 = {
|
||||
accountPlan: 'hobby',
|
||||
models: [{ name: 'test-model', supportsMedia: false }]
|
||||
};
|
||||
|
||||
const state2 = {
|
||||
accountPlan: 'business',
|
||||
models: [{ name: 'test-model', supportsMedia: true }]
|
||||
};
|
||||
|
||||
function setupUploadButton(buttonId, inputId, previewId, statusId, state) {
|
||||
const el = {
|
||||
uploadMediaBtn: document.getElementById(buttonId),
|
||||
uploadMediaInput: document.getElementById(inputId),
|
||||
attachmentPreview: document.getElementById(previewId),
|
||||
modelSelect: { value: 'test-model' }
|
||||
};
|
||||
|
||||
function isPaidPlanClient() {
|
||||
const plan = (state.accountPlan || '').toLowerCase();
|
||||
return plan === 'business' || plan === 'enterprise';
|
||||
}
|
||||
|
||||
function currentModelSupportsMedia() {
|
||||
const selectedModelId = el.modelSelect?.value;
|
||||
if (!selectedModelId) return false;
|
||||
const model = state.models.find((m) => (m.name || m.id || m) === selectedModelId);
|
||||
return model?.supportsMedia === true;
|
||||
}
|
||||
|
||||
function setStatus(msg) {
|
||||
document.getElementById(statusId).textContent = msg;
|
||||
}
|
||||
|
||||
function showUpgradeModal() {
|
||||
alert('You are already on the Enterprise plan with full access.');
|
||||
setStatus('Enterprise plan detected - no upgrade needed.');
|
||||
}
|
||||
|
||||
// THIS IS THE FIXED CODE
|
||||
if (el.uploadMediaBtn && el.uploadMediaInput) {
|
||||
console.log('Upload media elements found for ' + buttonId);
|
||||
el.uploadMediaBtn.addEventListener('click', (e) => {
|
||||
console.log('Upload media button clicked, isPaidPlanClient:', isPaidPlanClient());
|
||||
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
// Check if user is on free plan
|
||||
if (!isPaidPlanClient()) {
|
||||
showUpgradeModal();
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if model supports media
|
||||
if (!currentModelSupportsMedia()) {
|
||||
setStatus('This model does not support image uploads. Please select a different model that supports media.');
|
||||
return;
|
||||
}
|
||||
|
||||
// For paid users with media-supporting models, trigger file input click
|
||||
console.log('Triggering file input click');
|
||||
el.uploadMediaInput.value = '';
|
||||
el.uploadMediaInput.click();
|
||||
});
|
||||
|
||||
el.uploadMediaInput.addEventListener('change', () => {
|
||||
const files = el.uploadMediaInput.files ? Array.from(el.uploadMediaInput.files) : [];
|
||||
|
||||
// Reset input immediately to allow same file selection again
|
||||
el.uploadMediaInput.value = '';
|
||||
|
||||
if (files.length > 0) {
|
||||
setStatus(`✓ Selected ${files.length} file(s): ${files.map(f => f.name).join(', ')}`);
|
||||
el.attachmentPreview.innerHTML = `<p>Attached ${files.length} file(s)</p>`;
|
||||
el.attachmentPreview.classList.add('active');
|
||||
|
||||
// Briefly highlight the upload button to show feedback
|
||||
el.uploadMediaBtn.style.color = '#4ade80';
|
||||
el.uploadMediaBtn.style.fontWeight = 'bold';
|
||||
setTimeout(() => {
|
||||
el.uploadMediaBtn.style.color = '';
|
||||
el.uploadMediaBtn.style.fontWeight = '';
|
||||
}, 2000);
|
||||
} else {
|
||||
setStatus('No files selected');
|
||||
}
|
||||
});
|
||||
} else {
|
||||
console.log('Upload media elements NOT found');
|
||||
}
|
||||
}
|
||||
|
||||
// Setup both test scenarios
|
||||
setupUploadButton('upload-media-btn-1', 'upload-media-input-1', 'attachment-preview-1', 'status-1', state1);
|
||||
setupUploadButton('upload-media-btn-2', 'upload-media-input-2', 'attachment-preview-2', 'status-2', state2);
|
||||
|
||||
console.log('Test page loaded. Click the Upload media buttons to test the functionality.');
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
328
chat/public/test_token_usage.html
Normal file
@@ -0,0 +1,328 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Token Usage Test</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: system-ui, -apple-system, sans-serif;
|
||||
max-width: 800px;
|
||||
margin: 40px auto;
|
||||
padding: 20px;
|
||||
background: #f5f5f5;
|
||||
}
|
||||
.card {
|
||||
background: white;
|
||||
padding: 24px;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
h1 {
|
||||
margin: 0 0 20px;
|
||||
color: #333;
|
||||
}
|
||||
.controls {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
margin-bottom: 20px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
input[type="number"] {
|
||||
padding: 8px 12px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 6px;
|
||||
flex: 1;
|
||||
min-width: 150px;
|
||||
}
|
||||
button {
|
||||
padding: 8px 16px;
|
||||
background: #008060;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
font-weight: 600;
|
||||
}
|
||||
button:hover {
|
||||
background: #006e51;
|
||||
}
|
||||
button:disabled {
|
||||
background: #ccc;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
.usage-meter {
|
||||
margin: 20px 0;
|
||||
}
|
||||
.usage-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 8px;
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
}
|
||||
.usage-bar {
|
||||
height: 24px;
|
||||
background: #e0e0e0;
|
||||
border-radius: 12px;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
}
|
||||
.usage-fill {
|
||||
height: 100%;
|
||||
background: linear-gradient(90deg, #008060, #00a572);
|
||||
transition: width 0.3s ease;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: white;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
}
|
||||
.status {
|
||||
padding: 12px;
|
||||
border-radius: 6px;
|
||||
margin-top: 12px;
|
||||
font-size: 14px;
|
||||
}
|
||||
.status.success {
|
||||
background: #d4edda;
|
||||
color: #155724;
|
||||
border: 1px solid #c3e6cb;
|
||||
}
|
||||
.status.error {
|
||||
background: #f8d7da;
|
||||
color: #721c24;
|
||||
border: 1px solid #f5c6cb;
|
||||
}
|
||||
.status.info {
|
||||
background: #d1ecf1;
|
||||
color: #0c5460;
|
||||
border: 1px solid #bee5eb;
|
||||
}
|
||||
.stats {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
|
||||
gap: 16px;
|
||||
margin-top: 20px;
|
||||
}
|
||||
.stat {
|
||||
background: #f8f9fa;
|
||||
padding: 12px;
|
||||
border-radius: 8px;
|
||||
border: 1px solid #e9ecef;
|
||||
}
|
||||
.stat-label {
|
||||
font-size: 12px;
|
||||
color: #666;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
.stat-value {
|
||||
font-size: 20px;
|
||||
font-weight: 700;
|
||||
color: #333;
|
||||
}
|
||||
pre {
|
||||
background: #f8f9fa;
|
||||
padding: 12px;
|
||||
border-radius: 6px;
|
||||
overflow-x: auto;
|
||||
font-size: 12px;
|
||||
margin: 12px 0 0;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="card">
|
||||
<h1>Token Usage Test</h1>
|
||||
<p>Test the token usage tracking and progress bar updates. This simulates token consumption without actually running AI models.</p>
|
||||
|
||||
<div class="controls">
|
||||
<input type="number" id="tokenAmount" value="1000" min="1" step="100" placeholder="Tokens to add">
|
||||
<button id="simulateBtn">Simulate Token Usage</button>
|
||||
<button id="refreshBtn">Refresh Usage</button>
|
||||
</div>
|
||||
|
||||
<div class="usage-meter">
|
||||
<div class="usage-header">
|
||||
<span>Monthly Token Usage</span>
|
||||
<span id="usagePercent">—</span>
|
||||
</div>
|
||||
<div class="usage-bar">
|
||||
<div class="usage-fill" id="usageFill" style="width: 0%">
|
||||
<span id="usageText">0%</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="stats">
|
||||
<div class="stat">
|
||||
<div class="stat-label">Used</div>
|
||||
<div class="stat-value" id="usedTokens">—</div>
|
||||
</div>
|
||||
<div class="stat">
|
||||
<div class="stat-label">Limit</div>
|
||||
<div class="stat-value" id="limitTokens">—</div>
|
||||
</div>
|
||||
<div class="stat">
|
||||
<div class="stat-label">Remaining</div>
|
||||
<div class="stat-value" id="remainingTokens">—</div>
|
||||
</div>
|
||||
<div class="stat">
|
||||
<div class="stat-label">Plan</div>
|
||||
<div class="stat-value" id="planName">—</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="statusMessage" style="display: none;"></div>
|
||||
<pre id="debugOutput" style="display: none;"></pre>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const tokenAmountInput = document.getElementById('tokenAmount');
|
||||
const simulateBtn = document.getElementById('simulateBtn');
|
||||
const refreshBtn = document.getElementById('refreshBtn');
|
||||
const usageFill = document.getElementById('usageFill');
|
||||
const usageText = document.getElementById('usageText');
|
||||
const usagePercent = document.getElementById('usagePercent');
|
||||
const usedTokens = document.getElementById('usedTokens');
|
||||
const limitTokens = document.getElementById('limitTokens');
|
||||
const remainingTokens = document.getElementById('remainingTokens');
|
||||
const planName = document.getElementById('planName');
|
||||
const statusMessage = document.getElementById('statusMessage');
|
||||
const debugOutput = document.getElementById('debugOutput');
|
||||
|
||||
function showStatus(message, type = 'info') {
|
||||
statusMessage.textContent = message;
|
||||
statusMessage.className = `status ${type}`;
|
||||
statusMessage.style.display = 'block';
|
||||
setTimeout(() => {
|
||||
statusMessage.style.display = 'none';
|
||||
}, 5000);
|
||||
}
|
||||
|
||||
function updateUsageDisplay(summary) {
|
||||
if (!summary) {
|
||||
showStatus('No usage data available', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
const used = summary.used || 0;
|
||||
const limit = summary.limit || 0;
|
||||
const percent = summary.percent || 0;
|
||||
const remaining = summary.remaining || 0;
|
||||
const plan = summary.plan || 'unknown';
|
||||
|
||||
// Update progress bar
|
||||
usageFill.style.width = `${Math.min(100, percent)}%`;
|
||||
usageText.textContent = `${percent}%`;
|
||||
usagePercent.textContent = `${percent}% used`;
|
||||
|
||||
// Update stats
|
||||
usedTokens.textContent = used.toLocaleString();
|
||||
limitTokens.textContent = limit.toLocaleString();
|
||||
remainingTokens.textContent = remaining.toLocaleString();
|
||||
planName.textContent = plan.charAt(0).toUpperCase() + plan.slice(1);
|
||||
|
||||
// Change color based on usage
|
||||
if (percent >= 90) {
|
||||
usageFill.style.background = 'linear-gradient(90deg, #dc3545, #c82333)';
|
||||
} else if (percent >= 75) {
|
||||
usageFill.style.background = 'linear-gradient(90deg, #ffc107, #e0a800)';
|
||||
} else {
|
||||
usageFill.style.background = 'linear-gradient(90deg, #008060, #00a572)';
|
||||
}
|
||||
|
||||
debugOutput.textContent = JSON.stringify(summary, null, 2);
|
||||
debugOutput.style.display = 'block';
|
||||
}
|
||||
|
||||
async function fetchUsage() {
|
||||
try {
|
||||
const response = await fetch('/api/account/usage?_t=' + Date.now());
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
||||
}
|
||||
const data = await response.json();
|
||||
console.log('[TEST] Usage data received:', data);
|
||||
return data.summary;
|
||||
} catch (error) {
|
||||
console.error('[TEST] Failed to fetch usage:', error);
|
||||
showStatus(`Failed to fetch usage: ${error.message}`, 'error');
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async function simulateTokenUsage() {
|
||||
const tokens = parseInt(tokenAmountInput.value) || 1000;
|
||||
|
||||
simulateBtn.disabled = true;
|
||||
simulateBtn.textContent = 'Simulating...';
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/test/simulate-tokens', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({ tokens }),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
console.log('[TEST] Simulation response:', data);
|
||||
|
||||
showStatus(`✓ Simulated ${tokens.toLocaleString()} tokens successfully`, 'success');
|
||||
|
||||
// Update display with returned summary
|
||||
if (data.summary) {
|
||||
updateUsageDisplay(data.summary);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('[TEST] Failed to simulate:', error);
|
||||
showStatus(`Failed to simulate: ${error.message}`, 'error');
|
||||
} finally {
|
||||
simulateBtn.disabled = false;
|
||||
simulateBtn.textContent = 'Simulate Token Usage';
|
||||
}
|
||||
}
|
||||
|
||||
async function refreshUsage() {
|
||||
refreshBtn.disabled = true;
|
||||
refreshBtn.textContent = 'Refreshing...';
|
||||
|
||||
try {
|
||||
const summary = await fetchUsage();
|
||||
if (summary) {
|
||||
updateUsageDisplay(summary);
|
||||
showStatus('Usage data refreshed', 'success');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('[TEST] Refresh failed:', error);
|
||||
} finally {
|
||||
refreshBtn.disabled = false;
|
||||
refreshBtn.textContent = 'Refresh Usage';
|
||||
}
|
||||
}
|
||||
|
||||
// Event listeners
|
||||
simulateBtn.addEventListener('click', simulateTokenUsage);
|
||||
refreshBtn.addEventListener('click', refreshUsage);
|
||||
|
||||
// Load initial usage on page load
|
||||
window.addEventListener('DOMContentLoaded', async () => {
|
||||
console.log('[TEST] Page loaded, fetching initial usage...');
|
||||
const summary = await fetchUsage();
|
||||
if (summary) {
|
||||
updateUsageDisplay(summary);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
2117
chat/public/topup.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>
|
||||
100
chat/public/verify-email.html
Normal file
@@ -0,0 +1,100 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Verify Email | Plugin Compass</title>
|
||||
<link rel="icon" type="image/png" href="/assets/Plugin.png">
|
||||
<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">
|
||||
|
||||
<!-- PostHog Analytics -->
|
||||
<script src="/posthog.js"></script>
|
||||
</head>
|
||||
<body class="bg-amber-50 text-gray-900 font-sans antialiased min-h-screen flex flex-col">
|
||||
<nav class="w-full">
|
||||
<div class="max-w-4xl mx-auto px-4 py-6 flex items-center justify-between">
|
||||
<a href="/" class="flex items-center gap-2 font-bold text-lg text-gray-800">
|
||||
<img src="/assets/Plugin.png" alt="Plugin Compass" class="w-8 h-8">
|
||||
Plugin Compass
|
||||
</a>
|
||||
<a href="/login" class="text-sm text-green-700 hover:underline font-semibold">Back to sign in</a>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<main class="flex-grow flex items-center justify-center px-4 py-12">
|
||||
<div class="max-w-xl w-full bg-white/80 border border-gray-200 rounded-2xl shadow-xl shadow-green-900/5 p-8">
|
||||
<div class="text-center">
|
||||
<div class="w-14 h-14 rounded-full bg-green-700 text-white flex items-center justify-center mx-auto mb-4">
|
||||
<i class="fa-solid fa-envelope-circle-check text-2xl"></i>
|
||||
</div>
|
||||
<h1 class="text-2xl font-bold mb-2">Verify your email</h1>
|
||||
<p class="text-gray-600 mb-6">We sent you a verification link. Click it to finish setting up your account.</p>
|
||||
<div id="verify-status" class="text-sm text-gray-700 bg-amber-50 border border-amber-100 rounded-lg px-4 py-3"></div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<footer class="py-6 text-center text-gray-500 text-xs">
|
||||
<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>
|
||||
|
||||
<script>
|
||||
(async () => {
|
||||
const statusEl = document.getElementById('verify-status');
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
const token = params.get('token');
|
||||
|
||||
function setStatus(message, isError = false) {
|
||||
statusEl.textContent = message || '';
|
||||
statusEl.classList.toggle('text-red-700', isError);
|
||||
statusEl.classList.toggle('border-red-200', isError);
|
||||
statusEl.classList.toggle('bg-red-50', isError);
|
||||
statusEl.classList.toggle('text-green-700', !isError);
|
||||
statusEl.classList.toggle('border-green-100', !isError);
|
||||
statusEl.classList.toggle('bg-green-50', !isError);
|
||||
}
|
||||
|
||||
if (!token) {
|
||||
setStatus('Missing verification token. Please open the link from your email again.', true);
|
||||
return;
|
||||
}
|
||||
|
||||
setStatus('Verifying your email...', false);
|
||||
try {
|
||||
const resp = await fetch(`/api/verify-email?token=${encodeURIComponent(token)}`);
|
||||
const data = await resp.json().catch(() => ({}));
|
||||
if (!resp.ok) {
|
||||
setStatus(data.error || 'Verification failed. Please request a new link.', true);
|
||||
return;
|
||||
}
|
||||
setStatus(data.message || 'Email verified! Redirecting...', false);
|
||||
|
||||
// Store user session if provided
|
||||
if (data.token) {
|
||||
try {
|
||||
localStorage.setItem('wordpress_plugin_ai_user', JSON.stringify({
|
||||
email: data.user.email,
|
||||
accountId: data.user.id,
|
||||
sessionToken: data.token
|
||||
}));
|
||||
} catch (_) { }
|
||||
}
|
||||
|
||||
// Redirect to appropriate page
|
||||
const redirectUrl = data.redirect || (data.user?.hasPlan ? '/apps' : '/select-plan');
|
||||
setTimeout(() => {
|
||||
window.location.href = redirectUrl;
|
||||
}, 1500);
|
||||
} catch (err) {
|
||||
setStatus('Verification failed. Please try again.', true);
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||