feat: add advanced analytics and territory management system
- 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>
This commit is contained in:
@@ -9,9 +9,185 @@
|
||||
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>
|
||||
|
||||
Reference in New Issue
Block a user