- Add comprehensive analytics components with export functionality - Implement territory management with manager performance tracking - Add seatmap components for venue layout management - Create customer management features with modal interface - Add advanced hooks for dashboard flags and territory data - Implement seat selection and venue management utilities - Add type definitions for ticketing and seatmap systems 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
194 lines
8.2 KiB
HTML
194 lines
8.2 KiB
HTML
<!doctype html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8" />
|
|
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
<title>Black Canyon Tickets - Premium Event Ticketing</title>
|
|
<meta
|
|
name="description"
|
|
content="Premium event ticketing platform with beautiful glassmorphism design"
|
|
/>
|
|
|
|
<!-- PWA Manifest -->
|
|
<link rel="manifest" href="/manifest.json?v=3" />
|
|
|
|
<!-- PWA Theme Colors -->
|
|
<meta name="theme-color" content="#6366f1" />
|
|
<meta name="background-color" content="#0f0f23" />
|
|
|
|
<!-- PWA Meta Tags -->
|
|
<meta name="mobile-web-app-capable" content="yes" />
|
|
<meta name="apple-mobile-web-app-capable" content="yes" />
|
|
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
|
|
<meta name="apple-mobile-web-app-title" content="BCT Scanner" />
|
|
<script>
|
|
// Prevent FOUC by applying cached branding and theme before page render
|
|
(function() {
|
|
const THEME_KEY = 'bct-theme';
|
|
const BRANDING_CACHE_KEY = 'bct_branding';
|
|
|
|
// Default theme
|
|
let theme = 'dark';
|
|
|
|
try {
|
|
// 1. Apply cached organization branding immediately (if available)
|
|
const cachedBranding = localStorage.getItem(BRANDING_CACHE_KEY);
|
|
if (cachedBranding) {
|
|
const brandingData = JSON.parse(cachedBranding);
|
|
|
|
// Check cache age (expire after 24 hours)
|
|
const cacheAge = Date.now() - (brandingData.timestamp || 0);
|
|
const maxCacheAge = 24 * 60 * 60 * 1000; // 24 hours
|
|
|
|
if (cacheAge <= maxCacheAge && brandingData.theme) {
|
|
// Apply cached organization colors immediately
|
|
const root = document.documentElement.style;
|
|
const theme = brandingData.theme;
|
|
|
|
// Apply core organization colors
|
|
if (theme.accent) root.setProperty('--color-accent', theme.accent);
|
|
if (theme.bgCanvas) root.setProperty('--color-bg-canvas', theme.bgCanvas);
|
|
if (theme.bgSurface) root.setProperty('--color-bg-surface', theme.bgSurface);
|
|
if (theme.textPrimary) root.setProperty('--color-text-primary', theme.textPrimary);
|
|
if (theme.textSecondary) root.setProperty('--color-text-secondary', theme.textSecondary);
|
|
if (theme.border) root.setProperty('--color-border-default', theme.border);
|
|
if (theme.ring) root.setProperty('--color-focus-ring', theme.ring);
|
|
|
|
// Apply derived colors
|
|
if (theme.accent) {
|
|
// Generate accent variants
|
|
const accentHover = adjustColorBrightness(theme.accent, -0.1);
|
|
const accentBg = addAlphaToHex(theme.accent, 0.1);
|
|
const accentBorder = addAlphaToHex(theme.accent, 0.3);
|
|
|
|
root.setProperty('--color-accent-hover', accentHover);
|
|
root.setProperty('--color-accent-bg', accentBg);
|
|
root.setProperty('--color-accent-border', accentBorder);
|
|
}
|
|
|
|
if (theme.bgSurface && theme.textSecondary) {
|
|
// Generate glass effect colors
|
|
const glassBg = addAlphaToHex(theme.bgSurface, 0.7);
|
|
const glassBorder = addAlphaToHex(theme.textSecondary, 0.15);
|
|
|
|
root.setProperty('--color-glass-bg', glassBg);
|
|
root.setProperty('--color-glass-border', glassBorder);
|
|
}
|
|
|
|
console.log('Applied cached organization branding:', brandingData.orgId);
|
|
} else {
|
|
// Cache expired, remove it
|
|
localStorage.removeItem(BRANDING_CACHE_KEY);
|
|
}
|
|
}
|
|
|
|
// 2. Apply light/dark theme preference
|
|
const stored = localStorage.getItem(THEME_KEY);
|
|
if (stored === 'light' || stored === 'dark') {
|
|
theme = stored;
|
|
} else {
|
|
// Check system preference
|
|
if (window.matchMedia('(prefers-color-scheme: light)').matches) {
|
|
theme = 'light';
|
|
}
|
|
}
|
|
} catch (e) {
|
|
console.warn('Error applying cached branding/theme:', e);
|
|
// Continue with defaults if localStorage fails
|
|
}
|
|
|
|
// Set theme class immediately on html element
|
|
document.documentElement.classList.toggle('dark', theme === 'dark');
|
|
document.documentElement.setAttribute('data-theme', theme);
|
|
|
|
// Utility functions for color manipulation (inline to avoid dependencies)
|
|
function adjustColorBrightness(hex, adjustment) {
|
|
try {
|
|
const color = hex.replace('#', '');
|
|
const r = Math.max(0, Math.min(255, parseInt(color.substr(0, 2), 16) + Math.round(255 * adjustment)));
|
|
const g = Math.max(0, Math.min(255, parseInt(color.substr(2, 2), 16) + Math.round(255 * adjustment)));
|
|
const b = Math.max(0, Math.min(255, parseInt(color.substr(4, 2), 16) + Math.round(255 * adjustment)));
|
|
return '#' + r.toString(16).padStart(2, '0') + g.toString(16).padStart(2, '0') + b.toString(16).padStart(2, '0');
|
|
} catch (e) {
|
|
return hex;
|
|
}
|
|
}
|
|
|
|
function addAlphaToHex(hex, alpha) {
|
|
try {
|
|
const color = hex.replace('#', '');
|
|
const r = parseInt(color.substr(0, 2), 16);
|
|
const g = parseInt(color.substr(2, 2), 16);
|
|
const b = parseInt(color.substr(4, 2), 16);
|
|
return `rgba(${r}, ${g}, ${b}, ${alpha})`;
|
|
} catch (e) {
|
|
return hex;
|
|
}
|
|
}
|
|
})();
|
|
</script>
|
|
</head>
|
|
<body>
|
|
<div id="root"></div>
|
|
<!-- Early organization bootstrap -->
|
|
<script type="module">
|
|
// Import and run organization bootstrap as early as possible with global timeout
|
|
const bootstrapTimeout = setTimeout(() => {
|
|
console.warn('Organization bootstrap took too long, continuing without it');
|
|
}, 3000); // 3 second global timeout
|
|
|
|
import('./src/theme/orgBootstrap.ts').then(module => {
|
|
module.bootstrapOrganization()
|
|
.then(() => {
|
|
clearTimeout(bootstrapTimeout);
|
|
console.log('Organization bootstrap completed');
|
|
})
|
|
.catch(error => {
|
|
clearTimeout(bootstrapTimeout);
|
|
console.error('Organization bootstrap failed:', error);
|
|
});
|
|
}).catch(error => {
|
|
clearTimeout(bootstrapTimeout);
|
|
console.error('Failed to load organization bootstrap:', error);
|
|
});
|
|
</script>
|
|
<script>
|
|
window.addEventListener('error', e => {
|
|
const msg = (e && e.message) || 'Unknown error';
|
|
const el = document.createElement('div');
|
|
el.style.cssText='position:fixed;inset:0;background:#1b1d1f;color:#f2f3f4;padding:24px;z-index:999999;font-family:ui-sans-serif';
|
|
el.innerHTML = '<h3>App crashed</h3><pre style="white-space:pre-wrap">'+msg+'</pre>';
|
|
document.body.appendChild(el);
|
|
});
|
|
</script>
|
|
<script type="module" src="/src/main.tsx"></script>
|
|
|
|
<!-- Service Worker Registration -->
|
|
<script>
|
|
if ('serviceWorker' in navigator) {
|
|
window.addEventListener('load', () => {
|
|
navigator.serviceWorker.register('/sw.js')
|
|
.then((registration) => {
|
|
console.log('SW registered: ', registration);
|
|
|
|
// Listen for SW messages
|
|
navigator.serviceWorker.addEventListener('message', (event) => {
|
|
if (event.data && event.data.type === 'SYNC_COMPLETE') {
|
|
console.log('Background sync completed at:', new Date(event.data.timestamp));
|
|
// Dispatch custom event for scan queue to listen to
|
|
window.dispatchEvent(new CustomEvent('sw-sync-complete', {
|
|
detail: event.data
|
|
}));
|
|
}
|
|
});
|
|
})
|
|
.catch((registrationError) => {
|
|
console.log('SW registration failed: ', registrationError);
|
|
});
|
|
});
|
|
}
|
|
</script>
|
|
</body>
|
|
</html>
|