feat: Complete platform enhancement with multi-tenant architecture
Major additions: - Territory manager system with application workflow - Custom pricing and page builder with Craft.js - Enhanced Stripe Connect onboarding - CodeReadr QR scanning integration - Kiosk mode for venue sales - Super admin dashboard and analytics - MCP integration for AI-powered operations Infrastructure improvements: - Centralized API client and routing system - Enhanced authentication with organization context - Comprehensive theme management system - Advanced event management with custom tabs - Performance monitoring and accessibility features Database schema updates: - Territory management tables - Custom pages and pricing structures - Kiosk PIN system - Enhanced organization profiles - CodeReadr integration tables 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -7,22 +7,29 @@ const csrfToken = generateCSRFToken();
|
||||
---
|
||||
|
||||
<LoginLayout title="Login - Black Canyon Tickets">
|
||||
<main class="h-screen relative overflow-hidden flex flex-col">
|
||||
<main class="min-h-screen relative flex flex-col">
|
||||
<!-- Premium Hero Background with Animated Gradients -->
|
||||
<div class="absolute inset-0 bg-gradient-to-br from-indigo-900 via-purple-900 to-slate-900">
|
||||
<div class="absolute inset-0" style="background: var(--bg-gradient);">
|
||||
<!-- Animated Background Elements -->
|
||||
<div class="absolute inset-0 opacity-20">
|
||||
<div class="absolute top-20 left-20 w-64 h-64 bg-gradient-to-br from-blue-400 to-purple-500 rounded-full blur-3xl animate-pulse"></div>
|
||||
<div class="absolute bottom-20 right-20 w-96 h-96 bg-gradient-to-br from-purple-400 to-pink-500 rounded-full blur-3xl animate-pulse delay-1000"></div>
|
||||
<div class="absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 w-80 h-80 bg-gradient-to-br from-cyan-400 to-blue-500 rounded-full blur-3xl animate-pulse delay-500"></div>
|
||||
<div class="absolute inset-0 opacity-25">
|
||||
<!-- Large flowing orbs -->
|
||||
<div class="absolute top-20 left-20 w-72 h-72 rounded-full blur-3xl animate-float" style="background: var(--bg-orb-1); animation-duration: 18s;"></div>
|
||||
<div class="absolute bottom-20 right-20 w-80 h-80 rounded-full blur-3xl animate-float" style="background: var(--bg-orb-2); animation-duration: 22s; animation-delay: -3s;"></div>
|
||||
<div class="absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 w-64 h-64 rounded-full blur-3xl animate-float" style="background: var(--bg-orb-3); animation-duration: 20s; animation-delay: -8s;"></div>
|
||||
<div class="absolute top-1/4 right-1/3 w-56 h-56 rounded-full blur-3xl animate-float" style="background: var(--bg-orb-4); animation-duration: 16s; animation-delay: -12s;"></div>
|
||||
<div class="absolute bottom-1/4 left-1/3 w-48 h-48 rounded-full blur-3xl animate-float" style="background: var(--bg-orb-5); animation-duration: 24s; animation-delay: -6s;"></div>
|
||||
|
||||
<!-- Accent elements -->
|
||||
<div class="absolute top-1/6 left-2/3 w-28 h-28 rounded-full blur-2xl animate-pulse" style="background: var(--bg-orb-1); animation-duration: 4s;"></div>
|
||||
<div class="absolute bottom-1/6 right-2/3 w-20 h-20 rounded-full blur-2xl animate-pulse" style="background: var(--bg-orb-2); animation-duration: 5s; animation-delay: -1.5s;"></div>
|
||||
</div>
|
||||
|
||||
<!-- Geometric Patterns -->
|
||||
<div class="absolute inset-0 opacity-10">
|
||||
<div class="absolute inset-0" style="opacity: var(--grid-opacity, 0.1);">
|
||||
<svg class="w-full h-full" viewBox="0 0 100 100" preserveAspectRatio="none">
|
||||
<defs>
|
||||
<pattern id="grid" width="10" height="10" patternUnits="userSpaceOnUse">
|
||||
<path d="M 10 0 L 0 0 0 10" fill="none" stroke="white" stroke-width="0.5"/>
|
||||
<path d="M 10 0 L 0 0 0 10" fill="none" stroke="currentColor" stroke-width="0.5" style="color: var(--grid-pattern);"/>
|
||||
</pattern>
|
||||
</defs>
|
||||
<rect width="100" height="100" fill="url(#grid)" />
|
||||
@@ -30,85 +37,102 @@ const csrfToken = generateCSRFToken();
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="relative z-10 flex-1 container mx-auto px-4 py-4 lg:py-6 flex items-center">
|
||||
<div class="grid lg:grid-cols-2 gap-8 lg:gap-12 items-center max-w-6xl mx-auto w-full">
|
||||
|
||||
<!-- Loading State -->
|
||||
<div id="auth-loading" class="fixed inset-0 z-50 bg-black bg-opacity-50 flex items-center justify-center">
|
||||
<div class="backdrop-blur-xl rounded-2xl p-8 shadow-2xl" style="background: var(--glass-bg-lg); border: 1px solid var(--glass-border);">
|
||||
<div class="flex flex-col items-center space-y-4">
|
||||
<div class="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-400"></div>
|
||||
<p class="text-lg font-medium" style="color: var(--glass-text-primary);">Checking authentication...</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="main-content" class="relative z-10 flex-1 container mx-auto px-4 py-4 lg:py-6 flex items-center" style="display: none;">
|
||||
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6 sm:gap-8 lg:gap-12 items-center max-w-6xl mx-auto w-full">
|
||||
|
||||
<!-- Left Column: Brand & Features -->
|
||||
<div class="lg:pr-8">
|
||||
<!-- Brand Header -->
|
||||
<div class="text-center lg:text-left mb-8">
|
||||
|
||||
<h1 class="text-4xl md:text-5xl lg:text-6xl font-light text-white mb-4 tracking-tight">
|
||||
<h1 class="text-3xl sm:text-4xl md:text-5xl lg:text-6xl font-light mb-4 tracking-tight" style="color: var(--glass-text-primary);">
|
||||
Black Canyon
|
||||
<span class="font-bold bg-gradient-to-r from-blue-400 via-purple-400 to-pink-400 bg-clip-text text-transparent">
|
||||
<span class="font-bold" style="color: var(--glass-text-accent);">
|
||||
Tickets
|
||||
</span>
|
||||
</h1>
|
||||
<p class="text-lg lg:text-xl text-white/80 mb-6 lg:mb-8 max-w-2xl leading-relaxed">
|
||||
<p class="text-base sm:text-lg lg:text-xl mb-6 lg:mb-8 max-w-2xl leading-relaxed" style="color: var(--glass-text-secondary);">
|
||||
Elegant ticketing platform for Colorado's most prestigious venues
|
||||
</p>
|
||||
<div class="flex flex-wrap justify-center lg:justify-start gap-4 text-xs lg:text-sm text-white/70 mb-6">
|
||||
<div class="flex flex-wrap justify-center lg:justify-start gap-4 text-xs lg:text-sm mb-6" style="color: var(--glass-text-tertiary);">
|
||||
<span class="flex items-center">
|
||||
<span class="w-2 h-2 bg-blue-400 rounded-full mr-2"></span>
|
||||
<span class="w-2 h-2 rounded-full mr-2" style="background: var(--glass-text-accent);"></span>
|
||||
Self-serve event setup
|
||||
</span>
|
||||
<span class="flex items-center">
|
||||
<span class="w-2 h-2 bg-purple-400 rounded-full mr-2"></span>
|
||||
<span class="w-2 h-2 rounded-full mr-2" style="background: var(--glass-text-accent);"></span>
|
||||
Automated Stripe payouts
|
||||
</span>
|
||||
<span class="flex items-center">
|
||||
<span class="w-2 h-2 bg-pink-400 rounded-full mr-2"></span>
|
||||
<span class="w-2 h-2 rounded-full mr-2" style="background: var(--glass-text-accent);"></span>
|
||||
Mobile QR scanning — no apps required
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Core Features -->
|
||||
<div class="grid sm:grid-cols-3 lg:grid-cols-3 gap-4 mb-6">
|
||||
<div class="bg-white/10 backdrop-blur-lg rounded-xl p-4 border border-white/20 text-center">
|
||||
<div class="w-10 h-10 bg-gradient-to-br from-blue-500 to-purple-500 rounded-lg flex items-center justify-center mx-auto mb-2">
|
||||
<span class="text-lg">💡</span>
|
||||
<div class="grid grid-cols-1 sm:grid-cols-3 lg:grid-cols-3 gap-4 mb-6">
|
||||
<div class="backdrop-blur-lg rounded-xl p-4 text-center" style="background: var(--glass-bg); border: 1px solid var(--glass-border);">
|
||||
<div class="w-10 h-10 rounded-lg flex items-center justify-center mx-auto mb-2" style="background: var(--glass-text-accent);">
|
||||
<svg class="w-5 h-5" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path d="M11 3a1 1 0 10-2 0v1a1 1 0 102 0V3zM15.657 5.757a1 1 0 00-1.414-1.414l-.707.707a1 1 0 001.414 1.414l.707-.707zM18 10a1 1 0 01-1 1h-1a1 1 0 110-2h1a1 1 0 011 1zM5.05 6.464A1 1 0 106.464 5.05l-.707-.707a1 1 0 00-1.414 1.414l.707.707zM5 10a1 1 0 01-1 1H3a1 1 0 110-2h1a1 1 0 011 1zM8 16v-1h4v1a2 2 0 11-4 0zM12 14c.015-.34.208-.646.477-.859a4 4 0 10-4.954 0c.27.213.462.519.476.859h4.002z" />
|
||||
</svg>
|
||||
</div>
|
||||
<h3 class="font-semibold text-white text-sm mb-1">Quick Setup</h3>
|
||||
<p class="text-xs text-white/70">Create events in minutes</p>
|
||||
<h3 class="font-semibold text-sm mb-1" style="color: var(--glass-text-primary);">Quick Setup</h3>
|
||||
<p class="text-xs" style="color: var(--glass-text-tertiary);">Create events in minutes</p>
|
||||
</div>
|
||||
|
||||
<div class="bg-white/10 backdrop-blur-lg rounded-xl p-4 border border-white/20 text-center">
|
||||
<div class="w-10 h-10 bg-gradient-to-br from-green-500 to-emerald-500 rounded-lg flex items-center justify-center mx-auto mb-2">
|
||||
<span class="text-lg">💸</span>
|
||||
<div class="backdrop-blur-lg rounded-xl p-4 text-center" style="background: var(--glass-bg); border: 1px solid var(--glass-border);">
|
||||
<div class="w-10 h-10 rounded-lg flex items-center justify-center mx-auto mb-2" style="background: var(--success-color);">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8c-1.657 0-3 .895-3 2s1.343 2 3 2 3 .895 3 2-1.343 2-3 2m0-8c1.11 0 2.08.402 2.599 1M12 8V7m0 1v8m0 0v1m0-1c-1.11 0-2.08-.402-2.599-1M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
</svg>
|
||||
</div>
|
||||
<h3 class="font-semibold text-white text-sm mb-1">Fast Payments</h3>
|
||||
<p class="text-xs text-white/70">Automated Stripe payouts</p>
|
||||
<h3 class="font-semibold text-sm mb-1" style="color: var(--glass-text-primary);">Fast Payments</h3>
|
||||
<p class="text-xs" style="color: var(--glass-text-tertiary);">Automated Stripe payouts</p>
|
||||
</div>
|
||||
|
||||
<div class="bg-white/10 backdrop-blur-lg rounded-xl p-4 border border-white/20 text-center">
|
||||
<div class="w-10 h-10 bg-gradient-to-br from-purple-500 to-pink-500 rounded-lg flex items-center justify-center mx-auto mb-2">
|
||||
<span class="text-lg">📊</span>
|
||||
<div class="backdrop-blur-lg rounded-xl p-4 text-center" style="background: var(--glass-bg); border: 1px solid var(--glass-border);">
|
||||
<div class="w-10 h-10 rounded-lg flex items-center justify-center mx-auto mb-2" style="background: var(--glass-text-accent);">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z" />
|
||||
</svg>
|
||||
</div>
|
||||
<h3 class="font-semibold text-white text-sm mb-1">Live Analytics</h3>
|
||||
<p class="text-xs text-white/70">Dashboard + exports</p>
|
||||
<h3 class="font-semibold text-sm mb-1" style="color: var(--glass-text-primary);">Live Analytics</h3>
|
||||
<p class="text-xs" style="color: var(--glass-text-tertiary);">Dashboard + exports</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- Right Column: Login Form -->
|
||||
<div class="max-w-md mx-auto w-full">
|
||||
<div class="w-full max-w-md mx-auto lg:max-w-lg">
|
||||
<div class="relative group">
|
||||
<div class="absolute inset-0 bg-gradient-to-r from-blue-600 to-purple-600 rounded-2xl blur opacity-20 group-hover:opacity-30 transition-opacity"></div>
|
||||
<div class="relative bg-white/10 backdrop-blur-xl border border-white/20 rounded-2xl p-6 lg:p-8 shadow-2xl">
|
||||
<div class="absolute inset-0 rounded-2xl blur opacity-20 group-hover:opacity-30 transition-opacity" style="background: var(--glass-text-accent);"></div>
|
||||
<div class="relative backdrop-blur-xl rounded-2xl p-6 sm:p-8 shadow-2xl" style="background: var(--glass-bg-lg); border: 1px solid var(--glass-border);">
|
||||
<!-- Form Header -->
|
||||
<div class="text-center mb-8">
|
||||
<h2 class="text-2xl font-bold text-white mb-2">Organizer Login</h2>
|
||||
<p class="text-sm text-white/70">Manage your events and track ticket sales</p>
|
||||
<div class="text-center mb-6 sm:mb-8">
|
||||
<h2 class="text-xl sm:text-2xl font-bold mb-2" style="color: var(--glass-text-primary);">Organizer Login</h2>
|
||||
<p class="text-sm sm:text-base" style="color: var(--glass-text-secondary);">Manage your events and track ticket sales</p>
|
||||
</div>
|
||||
|
||||
<!-- Login Form -->
|
||||
<div id="auth-form">
|
||||
<form id="login-form" class="space-y-6">
|
||||
<form id="login-form" class="space-y-5 sm:space-y-6">
|
||||
<input type="hidden" id="csrf-token" value={csrfToken} />
|
||||
<div>
|
||||
<label for="email" class="block text-sm font-medium text-white mb-2">
|
||||
<label for="email" class="block text-sm font-medium mb-2" style="color: var(--glass-text-primary);">
|
||||
Email address
|
||||
</label>
|
||||
<input
|
||||
@@ -117,7 +141,8 @@ const csrfToken = generateCSRFToken();
|
||||
type="email"
|
||||
autocomplete="email"
|
||||
required
|
||||
class="appearance-none block w-full px-4 py-3 bg-white/10 backdrop-blur-lg border border-white/20 rounded-lg shadow-sm placeholder-white/60 text-white focus:outline-none focus:ring-2 focus:ring-blue-400 focus:border-blue-400 transition-colors"
|
||||
class="appearance-none block w-full px-4 py-3 backdrop-blur-lg rounded-lg shadow-sm focus:outline-none focus:ring-2 transition-colors"
|
||||
style="background: var(--glass-bg-input); border: 1px solid var(--glass-border); color: var(--glass-text-primary); --tw-placeholder-opacity: 1; placeholder: var(--glass-placeholder);"
|
||||
placeholder="Enter your email"
|
||||
/>
|
||||
</div>
|
||||
@@ -154,7 +179,7 @@ const csrfToken = generateCSRFToken();
|
||||
<div>
|
||||
<button
|
||||
type="submit"
|
||||
class="w-full flex justify-center py-3 px-4 border border-transparent rounded-lg shadow-lg text-sm font-medium text-white bg-gradient-to-r from-blue-600 to-purple-600 hover:from-blue-700 hover:to-purple-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 transition-all duration-200 hover:shadow-xl"
|
||||
class="w-full flex justify-center py-3 sm:py-4 px-4 border border-transparent rounded-lg shadow-lg text-sm sm:text-base font-medium text-white bg-gradient-to-r from-blue-600 to-purple-600 hover:from-blue-700 hover:to-purple-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 transition-all duration-200 hover:shadow-xl touch-manipulation min-h-[44px]"
|
||||
>
|
||||
Sign in
|
||||
</button>
|
||||
@@ -164,7 +189,7 @@ const csrfToken = generateCSRFToken();
|
||||
<button
|
||||
type="button"
|
||||
id="toggle-mode"
|
||||
class="text-sm text-blue-400 hover:text-blue-300 font-medium transition-colors"
|
||||
class="text-sm sm:text-base text-blue-400 hover:text-blue-300 font-medium transition-colors touch-manipulation py-2 px-4 rounded-lg hover:bg-white/5 min-h-[44px]"
|
||||
>
|
||||
Don't have an account? Sign up
|
||||
</button>
|
||||
@@ -217,9 +242,49 @@ const csrfToken = generateCSRFToken();
|
||||
</footer>
|
||||
</LoginLayout>
|
||||
|
||||
<script>
|
||||
// Force dark mode for this page immediately to prevent flashing
|
||||
document.documentElement.setAttribute('data-theme', 'dark');
|
||||
document.documentElement.classList.add('dark');
|
||||
document.documentElement.classList.remove('light');
|
||||
|
||||
// Apply to body as well to prevent any flash
|
||||
document.body.classList.add('dark');
|
||||
document.body.classList.remove('light');
|
||||
|
||||
// Override any global theme logic for this page
|
||||
(window as any).__FORCE_DARK_MODE__ = true;
|
||||
|
||||
// Prevent theme changes on this page
|
||||
if (window.localStorage) {
|
||||
// Store original theme before forcing
|
||||
const originalTheme = localStorage.getItem('theme');
|
||||
if (originalTheme && originalTheme !== 'dark') {
|
||||
sessionStorage.setItem('originalTheme', originalTheme);
|
||||
}
|
||||
// Temporarily set to dark for consistency
|
||||
localStorage.setItem('theme', 'dark');
|
||||
}
|
||||
|
||||
// Block any theme change events on this page
|
||||
window.addEventListener('themeChanged', (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
// Force back to dark if anything tries to change it
|
||||
document.documentElement.setAttribute('data-theme', 'dark');
|
||||
document.documentElement.classList.add('dark');
|
||||
document.documentElement.classList.remove('light');
|
||||
document.body.classList.add('dark');
|
||||
document.body.classList.remove('light');
|
||||
}, true);
|
||||
</script>
|
||||
|
||||
<script>
|
||||
import { supabase } from '../lib/supabase';
|
||||
|
||||
// Get DOM elements
|
||||
const authLoading = document.getElementById('auth-loading') as HTMLDivElement;
|
||||
const mainContent = document.getElementById('main-content') as HTMLDivElement;
|
||||
const loginForm = document.getElementById('login-form') as HTMLFormElement;
|
||||
const toggleMode = document.getElementById('toggle-mode') as HTMLButtonElement;
|
||||
const nameField = document.getElementById('name-field') as HTMLDivElement;
|
||||
@@ -227,6 +292,13 @@ const csrfToken = generateCSRFToken();
|
||||
const submitButton = loginForm.querySelector('button[type="submit"]') as HTMLButtonElement;
|
||||
const errorMessage = document.getElementById('error-message') as HTMLDivElement;
|
||||
|
||||
// Debug logging
|
||||
console.log('[LOGIN] Page loaded, checking auth state...');
|
||||
|
||||
// Authentication state
|
||||
let authCheckInProgress = false;
|
||||
let redirectInProgress = false;
|
||||
|
||||
let isSignUpMode = false;
|
||||
|
||||
toggleMode.addEventListener('click', () => {
|
||||
@@ -270,25 +342,142 @@ const csrfToken = generateCSRFToken();
|
||||
|
||||
alert('Check your email for the confirmation link!');
|
||||
} else {
|
||||
const { error } = await supabase.auth.signInWithPassword({
|
||||
email,
|
||||
password,
|
||||
// Use the SSR-compatible login endpoint
|
||||
const response = await fetch('/api/auth/login', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({ email, password }),
|
||||
});
|
||||
|
||||
if (error) throw error;
|
||||
const result = await response.json();
|
||||
|
||||
window.location.pathname = '/dashboard';
|
||||
if (!response.ok) {
|
||||
throw new Error(result.error || 'Login failed');
|
||||
}
|
||||
|
||||
// Check for return URL parameter (support both 'returnTo' and 'redirect')
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
const returnTo = urlParams.get('returnTo') || urlParams.get('redirect');
|
||||
|
||||
// Use the redirectTo from server or fallback to returnTo
|
||||
const finalRedirect = returnTo || result.redirectTo || '/dashboard';
|
||||
|
||||
// Use window.location.href for full page reload to ensure cookies are set
|
||||
window.location.href = finalRedirect;
|
||||
}
|
||||
} catch (error) {
|
||||
errorMessage.textContent = error.message;
|
||||
errorMessage.textContent = (error as Error).message;
|
||||
errorMessage.classList.remove('hidden');
|
||||
}
|
||||
});
|
||||
|
||||
// Check if user is already logged in
|
||||
supabase.auth.getSession().then(({ data: { session } }) => {
|
||||
if (session) {
|
||||
window.location.pathname = '/dashboard';
|
||||
// Show loading state initially
|
||||
function showLoading() {
|
||||
authLoading.style.display = 'flex';
|
||||
mainContent.style.display = 'none';
|
||||
}
|
||||
|
||||
// Hide loading state and show content
|
||||
function hideLoading() {
|
||||
authLoading.style.display = 'none';
|
||||
mainContent.style.display = 'flex';
|
||||
}
|
||||
|
||||
// Safe redirect function with debouncing
|
||||
function safeRedirect(path: string, delay = 500) {
|
||||
if (redirectInProgress) {
|
||||
console.log('[LOGIN] Redirect already in progress, ignoring');
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
redirectInProgress = true;
|
||||
console.log(`[LOGIN] Redirecting to ${path} in ${delay}ms...`);
|
||||
|
||||
setTimeout(() => {
|
||||
window.location.pathname = path;
|
||||
}, delay);
|
||||
}
|
||||
|
||||
// Enhanced auth check with better error handling
|
||||
async function checkAuthState() {
|
||||
if (authCheckInProgress) {
|
||||
console.log('[LOGIN] Auth check already in progress');
|
||||
return;
|
||||
}
|
||||
|
||||
authCheckInProgress = true;
|
||||
console.log('[LOGIN] Starting auth check...');
|
||||
|
||||
try {
|
||||
// Get current session with timeout
|
||||
const authPromise = supabase.auth.getSession();
|
||||
const timeoutPromise = new Promise((_, reject) =>
|
||||
setTimeout(() => reject(new Error('Auth check timeout')), 5000)
|
||||
);
|
||||
|
||||
const { data: { session }, error } = await Promise.race([authPromise, timeoutPromise]) as any;
|
||||
|
||||
if (error) {
|
||||
console.log('[LOGIN] Auth error:', error.message);
|
||||
hideLoading();
|
||||
authCheckInProgress = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!session) {
|
||||
console.log('[LOGIN] No active session, showing login form');
|
||||
hideLoading();
|
||||
authCheckInProgress = false;
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('[LOGIN] Active session found, checking organization...');
|
||||
|
||||
// Check if user has an organization with timeout
|
||||
const userPromise = supabase
|
||||
.from('users')
|
||||
.select('organization_id')
|
||||
.eq('id', session.user.id)
|
||||
.single();
|
||||
|
||||
const { data: userData, error: userError } = await Promise.race([
|
||||
userPromise,
|
||||
new Promise((_, reject) =>
|
||||
setTimeout(() => reject(new Error('User data timeout')), 3000)
|
||||
)
|
||||
]) as any;
|
||||
|
||||
if (userError) {
|
||||
console.log('[LOGIN] User data error:', userError.message);
|
||||
hideLoading();
|
||||
authCheckInProgress = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// Check for return URL parameter (support both 'returnTo' and 'redirect')
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
const returnTo = urlParams.get('returnTo') || urlParams.get('redirect');
|
||||
|
||||
if (!userData?.organization_id) {
|
||||
console.log('[LOGIN] No organization found, redirecting to onboarding');
|
||||
safeRedirect('/onboarding/organization');
|
||||
} else {
|
||||
console.log('[LOGIN] Organization found, redirecting to', returnTo || '/dashboard');
|
||||
safeRedirect(returnTo || '/dashboard');
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('[LOGIN] Auth check failed:', error);
|
||||
hideLoading();
|
||||
authCheckInProgress = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Initial auth check with delay to prevent flashing
|
||||
setTimeout(() => {
|
||||
checkAuthState();
|
||||
}, 100);
|
||||
|
||||
</script>
|
||||
Reference in New Issue
Block a user