502 lines
22 KiB
HTML
502 lines
22 KiB
HTML
<!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> |