Files
shopify-ai-backup/chat/public/signup.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>