Files
blackcanyontickets/eslint-output.json
dzinesco 26a87d0d00 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>
2025-07-12 18:21:40 -06:00

2 lines
1.5 MiB
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
[{"filePath":"/home/tyler/apps/bct-whitelabel/docs/astro.config.mjs","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/home/tyler/apps/bct-whitelabel/setup-schema.mjs","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/home/tyler/apps/bct-whitelabel/src/components/AccountStatusBanner.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/home/tyler/apps/bct-whitelabel/src/components/Calendar.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/home/tyler/apps/bct-whitelabel/src/components/ChatWidget.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/home/tyler/apps/bct-whitelabel/src/components/CustomPageRenderer.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/home/tyler/apps/bct-whitelabel/src/components/CustomPricingManager.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/home/tyler/apps/bct-whitelabel/src/components/EventManagement.tsx","messages":[{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'_err' is defined but never used.","line":125,"column":16,"nodeType":null,"messageId":"unusedVar","endLine":125,"endColumn":20}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":1,"fixableErrorCount":0,"fixableWarningCount":0,"source":"import { useState, useEffect } from 'react';\nimport TabNavigation from './manage/TabNavigation';\nimport TicketingAccessTab from './manage/TicketingAccessTab';\nimport OrdersTab from './manage/OrdersTab';\nimport AttendeesTab from './manage/AttendeesTab';\nimport EventSettingsTab from './manage/EventSettingsTab';\nimport CustomPageTab from './manage/CustomPageTab';\nimport { api } from '../lib/api-router.js';\nimport type { EventData } from '../lib/event-management.js';\n\ninterface EventManagementProps {\n eventId: string;\n organizationId?: string;\n eventSlug?: string;\n}\n\nexport default function EventManagement({ eventId, _organizationId, eventSlug }: EventManagementProps) {\n const [activeTab, setActiveTab] = useState('ticketing');\n const [eventData, setEventData] = useState<EventData | null>(null);\n const [loading, setLoading] = useState(true);\n const [error, setError] = useState<string | null>(null);\n const [_user, setUser] = useState<unknown>(null);\n const [userOrganizationId, setUserOrganizationId] = useState<string | null>(null);\n const [actualEventSlug, setActualEventSlug] = useState<string | null>(eventSlug || null);\n\n const tabs = [\n {\n id: 'ticketing',\n name: 'Ticketing & Access',\n icon: (\n <svg className=\"w-5 h-5\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M15 5v2m0 4v2m0 4v2M5 5a2 2 0 00-2 2v3a2 2 0 110 4v3a2 2 0 002 2h14a2 2 0 002-2v-3a2 2 0 110-4V7a2 2 0 00-2-2H5z\" />\n </svg>\n ),\n component: TicketingAccessTab\n },\n {\n id: 'custom-pages',\n name: 'Custom Pages',\n icon: (\n <svg className=\"w-5 h-5\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M4 5a1 1 0 011-1h14a1 1 0 011 1v2a1 1 0 01-1 1H5a1 1 0 01-1-1V5zM4 13a1 1 0 011-1h6a1 1 0 011 1v6a1 1 0 01-1 1H5a1 1 0 01-1-1v-6zM16 13a1 1 0 011-1h2a1 1 0 011 1v6a1 1 0 01-1 1h-2a1 1 0 01-1-1v-6z\" />\n </svg>\n ),\n component: CustomPageTab\n },\n {\n id: 'sales',\n name: 'Sales',\n icon: (\n <svg className=\"w-5 h-5\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-3 7h3m-3 4h3m-6-4h.01M9 16h.01\" />\n </svg>\n ),\n component: OrdersTab\n },\n {\n id: 'attendees',\n name: 'Attendees',\n icon: (\n <svg className=\"w-5 h-5\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z\" />\n </svg>\n ),\n component: AttendeesTab\n },\n {\n id: 'settings',\n name: 'Event Settings',\n icon: (\n <svg className=\"w-5 h-5\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z\" />\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M15 12a3 3 0 11-6 0 3 3 0 016 0z\" />\n </svg>\n ),\n component: EventSettingsTab\n }\n ];\n\n useEffect(() => {\n // Check authentication and load data\n const initializeComponent = async () => {\n try {\n setLoading(true);\n setError(null);\n \n // Check authentication status\n const authStatus = await api.checkAuth();\n \n if (!authStatus.authenticated) {\n // Give a bit more time for auth to load on page refresh\n // Auth check failed, retrying in 1 second...\n setTimeout(async () => {\n const retryAuthStatus = await api.checkAuth();\n if (!retryAuthStatus.authenticated) {\n // Still not authenticated, redirect to login\n // Auth retry failed, redirecting to login\n window.location.href = '/login';\n return;\n }\n // Retry loading with successful auth\n // Auth retry succeeded, reinitializing component\n initializeComponent();\n }, 1000);\n return;\n }\n \n setUser(authStatus.user);\n \n if (!authStatus.organizationId) {\n setError('User not associated with any organization');\n return;\n }\n \n setUserOrganizationId(authStatus.organizationId);\n \n // Load event data using centralized API\n const data = await api.loadEventData(eventId);\n if (data) {\n setEventData(data);\n setActualEventSlug(data.slug);\n } else {\n setError(`Event not found or access denied. Event ID: ${eventId}`);\n }\n } catch (_err) {\n setError('Failed to load event data. Please try again.');\n } finally {\n setLoading(false);\n }\n };\n\n initializeComponent();\n }, [eventId]);\n\n // Loading state\n if (loading) {\n return (\n <div className=\"flex items-center justify-center py-12\">\n <div className=\"text-center\">\n <div className=\"animate-spin rounded-full h-8 w-8 border-2 mx-auto mb-4\" style={{borderColor: 'var(--glass-text-primary)', borderTopColor: 'transparent'}}></div>\n <p style={{color: 'var(--glass-text-secondary)'}}>Loading event data...</p>\n </div>\n </div>\n );\n }\n\n // Error state\n if (error) {\n return (\n <div className=\"flex items-center justify-center py-12\">\n <div className=\"text-center\">\n <div className=\"w-12 h-12 rounded-xl flex items-center justify-center mx-auto mb-4\" style={{background: 'var(--error-bg)'}}>\n <svg className=\"w-6 h-6\" style={{color: 'var(--error-color)'}} fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-1.964-.833-2.732 0L3.732 16.5c-.77.833.192 2.5 1.732 2.5z\" />\n </svg>\n </div>\n <p className=\"font-medium mb-2\" style={{color: 'var(--error-color)'}}>Error Loading Event</p>\n <p className=\"text-sm mb-4\" style={{color: 'var(--glass-text-secondary)'}}>{error}</p>\n <button\n onClick={() => window.location.reload()}\n className=\"px-4 py-2 border rounded-lg text-sm transition-colors backdrop-blur-lg\"\n style={{\n background: 'var(--ui-bg-elevated)',\n borderColor: 'var(--ui-border-primary)',\n color: 'var(--ui-text-primary)'\n }}\n onMouseEnter={(e) => e.target.style.background = 'var(--ui-bg-secondary)'}\n onMouseLeave={(e) => e.target.style.background = 'var(--ui-bg-elevated)'}\n >\n Try Again\n </button>\n </div>\n </div>\n );\n }\n\n return (\n <TabNavigation\n tabs={tabs}\n activeTab={activeTab}\n onTabChange={setActiveTab}\n eventId={eventId}\n organizationId={userOrganizationId || ''}\n eventData={eventData}\n eventSlug={actualEventSlug}\n />\n );\n}","usedDeprecatedRules":[]},{"filePath":"/home/tyler/apps/bct-whitelabel/src/components/ImageUploadCropper.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/home/tyler/apps/bct-whitelabel/src/components/LocationInput.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/home/tyler/apps/bct-whitelabel/src/components/PageBuilder.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/home/tyler/apps/bct-whitelabel/src/components/QuickTicketPurchase.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/home/tyler/apps/bct-whitelabel/src/components/StripeEmbeddedOnboarding.tsx","messages":[{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'currentStep' is assigned a value but never used. Allowed unused vars must match /^_/u.","line":68,"column":10,"nodeType":null,"messageId":"unusedVar","endLine":68,"endColumn":21},{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":70,"column":40,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":70,"endColumn":43,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[1952,1955],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[1952,1955],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":107,"column":33,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":107,"endColumn":36,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[3218,3221],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[3218,3221],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'e' is defined but never used. Allowed unused args must match /^_/u.","line":118,"column":35,"nodeType":null,"messageId":"unusedVar","endLine":118,"endColumn":36},{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":118,"column":38,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":118,"endColumn":41,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[3539,3542],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[3539,3542],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'index' is defined but never used. Allowed unused args must match /^_/u.","line":276,"column":33,"nodeType":null,"messageId":"unusedVar","endLine":276,"endColumn":38}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":6,"fixableErrorCount":0,"fixableWarningCount":0,"source":"import React, { useEffect, useState, useRef } from 'react';\nimport { loadConnectAndInitialize } from '@stripe/connect-js';\n\ninterface StripeEmbeddedOnboardingProps {\n accountId: string;\n clientSecret: string;\n onComplete?: () => void;\n onError?: (error: Error) => void;\n}\n\ninterface OnboardingStep {\n id: number;\n title: string;\n icon: string;\n status: 'completed' | 'current' | 'pending';\n description: string;\n securityNote: string;\n estimatedTime: string;\n}\n\nconst onboardingSteps: OnboardingStep[] = [\n {\n id: 1,\n title: \"Business Information\",\n icon: \"🏢\",\n status: \"current\",\n description: \"Basic details about your organization\",\n securityNote: \"All information is encrypted in transit and at rest\",\n estimatedTime: \"2-3 minutes\"\n },\n {\n id: 2,\n title: \"Identity Verification\",\n icon: \"🔐\",\n status: \"pending\",\n description: \"Secure verification required by financial regulations\",\n securityNote: \"Documents are processed by Stripe's secure systems\",\n estimatedTime: \"5-10 minutes\"\n },\n {\n id: 3,\n title: \"Bank Account Setup\",\n icon: \"🏦\",\n status: \"pending\",\n description: \"Connect your bank account for automated payouts\",\n securityNote: \"Bank details are never stored on our servers\",\n estimatedTime: \"2-3 minutes\"\n },\n {\n id: 4,\n title: \"Final Review\",\n icon: \"✅\",\n status: \"pending\",\n description: \"Review and complete your secure account setup\",\n securityNote: \"Your account will be activated immediately\",\n estimatedTime: \"1-2 minutes\"\n }\n];\n\nconst StripeEmbeddedOnboarding: React.FC<StripeEmbeddedOnboardingProps> = ({\n accountId,\n clientSecret,\n onComplete,\n onError\n}) => {\n const [loading, setLoading] = useState(true);\n const [error, setError] = useState<string | null>(null);\n const [currentStep, setCurrentStep] = useState(1);\n const [steps, setSteps] = useState(onboardingSteps);\n const stripeConnectInstance = useRef<any>(null);\n const containerRef = useRef<HTMLDivElement>(null);\n\n useEffect(() => {\n const initializeStripe = async () => {\n try {\n setLoading(true);\n \n // Initialize Stripe Connect\n const publishableKey = import.meta.env.PUBLIC_STRIPE_PUBLISHABLE_KEY;\n if (!publishableKey) {\n throw new Error('Stripe publishable key not configured');\n }\n\n stripeConnectInstance.current = loadConnectAndInitialize({\n publishableKey,\n clientSecret,\n appearance: {\n variables: {\n colorPrimary: '#1f2937',\n colorBackground: '#ffffff',\n colorText: '#1f2937',\n colorDanger: '#ef4444',\n fontFamily: '-apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, sans-serif',\n spacingUnit: '4px',\n borderRadius: '8px',\n fontSizeBase: '14px',\n fontWeightNormal: '400',\n fontWeightBold: '600'\n }\n }\n });\n\n // Create and mount the onboarding component\n const component = stripeConnectInstance.current.create('account-onboarding');\n \n // Set up event listeners before mounting\n component.setOnExit((e: any) => {\n\n if (e.reason === 'account_onboarding_completed') {\n updateStepStatus(4, 'completed');\n onComplete?.();\n } else if (e.reason === 'account_onboarding_closed') {\n // User closed the onboarding flow\n\n }\n });\n\n component.setOnLoadError((e: any) => {\n\n onError?.(new Error('Failed to load onboarding component'));\n });\n\n // Mount the component\n if (containerRef.current) {\n containerRef.current.appendChild(component);\n }\n\n setLoading(false);\n } catch (error) {\n // Stripe initialization error\n const errorMessage = error instanceof Error ? error.message : 'Failed to initialize Stripe Connect';\n setError(errorMessage);\n onError?.(error as Error);\n setLoading(false);\n }\n };\n\n if (accountId && clientSecret) {\n initializeStripe();\n }\n\n return () => {\n // Cleanup if needed\n if (stripeConnectInstance.current) {\n stripeConnectInstance.current = null;\n }\n };\n }, [accountId, clientSecret, onComplete, onError]);\n\n const updateStepStatus = (stepId: number, status: 'completed' | 'current' | 'pending') => {\n setSteps(prev => prev.map(step => \n step.id === stepId ? { ...step, status } : step\n ));\n setCurrentStep(stepId);\n };\n\n const getStepStatusColor = (status: string) => {\n switch (status) {\n case 'completed':\n return 'bg-green-100 border-green-500 text-green-700';\n case 'current':\n return 'bg-blue-100 border-blue-500 text-blue-700';\n case 'pending':\n return 'bg-gray-100 border-gray-300 text-gray-500';\n default:\n return 'bg-gray-100 border-gray-300 text-gray-500';\n }\n };\n\n const getStepIcon = (status: string) => {\n switch (status) {\n case 'completed':\n return (\n <svg className=\"w-5 h-5 text-green-600\" fill=\"currentColor\" viewBox=\"0 0 20 20\">\n <path fillRule=\"evenodd\" d=\"M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z\" clipRule=\"evenodd\" />\n </svg>\n );\n case 'current':\n return (\n <div className=\"w-5 h-5 border-2 border-blue-600 rounded-full flex items-center justify-center\">\n <div className=\"w-2 h-2 bg-blue-600 rounded-full animate-pulse\"></div>\n </div>\n );\n default:\n return <div className=\"w-5 h-5 border-2 border-gray-300 rounded-full\"></div>;\n }\n };\n\n if (error) {\n return (\n <div className=\"min-h-screen bg-gray-50\">\n <div className=\"max-w-4xl mx-auto px-6 py-8\">\n <div className=\"bg-white rounded-lg shadow-sm border p-8\">\n <div className=\"text-center\">\n <div className=\"w-12 h-12 bg-red-100 rounded-full flex items-center justify-center mx-auto mb-4\">\n <svg className=\"w-6 h-6 text-red-600\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-1.964-.833-2.732 0L3.732 16.5c-.77.833.192 2.5 1.732 2.5z\" />\n </svg>\n </div>\n <h2 className=\"text-xl font-semibold text-gray-900 mb-2\">Setup Error</h2>\n <p className=\"text-gray-600 mb-6\">{error}</p>\n <div className=\"space-y-2\">\n <button \n onClick={() => {\n setError(null);\n setLoading(true);\n window.location.reload();\n }}\n className=\"w-full bg-blue-600 hover:bg-blue-700 text-white py-2 px-4 rounded-md font-medium transition-colors\"\n >\n Try Again\n </button>\n <a href=\"/dashboard\" className=\"block w-full bg-gray-600 hover:bg-gray-700 text-white py-2 px-4 rounded-md font-medium transition-colors\">\n Return to Dashboard\n </a>\n </div>\n </div>\n </div>\n </div>\n </div>\n );\n }\n\n if (loading) {\n return (\n <div className=\"min-h-screen bg-gray-50\">\n <div className=\"max-w-4xl mx-auto px-6 py-8\">\n <div className=\"bg-white rounded-lg shadow-sm border p-8\">\n <div className=\"text-center\">\n <div className=\"animate-spin rounded-full h-12 w-12 border-b-2 border-blue-600 mx-auto mb-4\"></div>\n <h2 className=\"text-xl font-semibold text-gray-900 mb-2\">Initializing Secure Setup</h2>\n <p className=\"text-gray-600\">Preparing your encrypted onboarding experience...</p>\n </div>\n </div>\n </div>\n </div>\n );\n }\n\n return (\n <div className=\"min-h-screen bg-gray-50\">\n {/* Security Header */}\n <header className=\"bg-gray-900 border-b border-gray-700\">\n <div className=\"max-w-4xl mx-auto px-6 py-4\">\n <div className=\"flex items-center justify-between\">\n <div className=\"flex items-center space-x-3\">\n <div className=\"w-8 h-8 bg-gradient-to-br from-blue-500 to-purple-500 rounded-lg flex items-center justify-center\">\n <span className=\"text-white font-bold text-sm\">BCT</span>\n </div>\n <h1 className=\"text-xl font-semibold text-white\">Secure Account Setup</h1>\n </div>\n <div className=\"flex items-center space-x-4 text-sm text-gray-400\">\n <span className=\"flex items-center\">\n <svg className=\"w-4 h-4 mr-1\" fill=\"currentColor\" viewBox=\"0 0 20 20\">\n <path fillRule=\"evenodd\" d=\"M5 9V7a5 5 0 0110 0v2a2 2 0 012 2v5a2 2 0 01-2 2H5a2 2 0 01-2-2v-5a2 2 0 012-2zm8-2v2H7V7a3 3 0 016 0z\" clipRule=\"evenodd\" />\n </svg>\n 256-bit SSL\n </span>\n <span className=\"text-gray-500\">•</span>\n <span>Powered by Stripe</span>\n </div>\n </div>\n </div>\n </header>\n\n <main className=\"max-w-4xl mx-auto px-6 py-8\">\n {/* Progress Steps */}\n <div className=\"mb-8\">\n <div className=\"bg-white rounded-lg shadow-sm border p-6\">\n <div className=\"mb-6\">\n <h2 className=\"text-lg font-semibold text-gray-900 mb-2\">Account Setup Progress</h2>\n <p className=\"text-sm text-gray-600\">Complete each step to activate your payment processing account</p>\n </div>\n \n <div className=\"space-y-4\">\n {steps.map((step, index) => (\n <div key={step.id} className={`flex items-start p-4 rounded-lg border-2 ${getStepStatusColor(step.status)}`}>\n <div className=\"flex-shrink-0 mr-4\">\n {getStepIcon(step.status)}\n </div>\n <div className=\"flex-1\">\n <div className=\"flex items-center justify-between mb-1\">\n <h3 className=\"font-medium\">{step.title}</h3>\n <span className=\"text-xs font-medium\">{step.estimatedTime}</span>\n </div>\n <p className=\"text-sm mb-2\">{step.description}</p>\n <div className=\"flex items-center text-xs\">\n <svg className=\"w-3 h-3 mr-1\" fill=\"currentColor\" viewBox=\"0 0 20 20\">\n <path fillRule=\"evenodd\" d=\"M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z\" clipRule=\"evenodd\" />\n </svg>\n {step.securityNote}\n </div>\n </div>\n </div>\n ))}\n </div>\n </div>\n </div>\n\n {/* Main Onboarding Container */}\n <div className=\"bg-white rounded-lg shadow-sm border\">\n <div className=\"p-6 border-b border-gray-200\">\n <div className=\"flex items-center justify-center space-x-2 text-sm text-gray-600\">\n <svg className=\"w-4 h-4 text-green-600\" fill=\"currentColor\" viewBox=\"0 0 20 20\">\n <path fillRule=\"evenodd\" d=\"M5 9V7a5 5 0 0110 0v2a2 2 0 012 2v5a2 2 0 01-2 2H5a2 2 0 01-2-2v-5a2 2 0 012-2zm8-2v2H7V7a3 3 0 016 0z\" clipRule=\"evenodd\" />\n </svg>\n <span className=\"text-green-600 font-medium\">Secure Connection</span>\n <span className=\"text-gray-400\">•</span>\n <span>Stripe Connect</span>\n <span className=\"text-gray-400\">•</span>\n <span>PCI DSS Compliant</span>\n </div>\n </div>\n \n <div className=\"p-8\">\n <div className=\"mb-6 text-center\">\n <h2 className=\"text-2xl font-bold text-gray-900 mb-2\">Payment Account Setup</h2>\n <p className=\"text-gray-600 max-w-2xl mx-auto\">\n Complete your secure payment processing setup to start accepting payments. \n All information is encrypted and processed by Stripe's bank-level security infrastructure.\n </p>\n </div>\n\n {/* Trust Indicators */}\n <div className=\"mb-8 p-4 bg-blue-50 rounded-lg border border-blue-200\">\n <div className=\"flex items-center justify-center space-x-8 text-sm\">\n <div className=\"flex items-center text-blue-700\">\n <svg className=\"w-5 h-5 mr-2\" fill=\"currentColor\" viewBox=\"0 0 20 20\">\n <path fillRule=\"evenodd\" d=\"M2.166 4.999A11.954 11.954 0 0010 1.944 11.954 11.954 0 0017.834 5c.11.65.166 1.32.166 2.001 0 5.225-3.34 9.67-8 11.317C5.34 16.67 2 12.225 2 7c0-.682.057-1.35.166-2.001zm11.541 3.708a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z\" clipRule=\"evenodd\" />\n </svg>\n Bank-level Security\n </div>\n <div className=\"flex items-center text-blue-700\">\n <svg className=\"w-5 h-5 mr-2\" fill=\"currentColor\" viewBox=\"0 0 20 20\">\n <path fillRule=\"evenodd\" d=\"M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z\" clipRule=\"evenodd\" />\n </svg>\n PCI DSS Level 1\n </div>\n <div className=\"flex items-center text-blue-700\">\n <svg className=\"w-5 h-5 mr-2\" fill=\"currentColor\" viewBox=\"0 0 20 20\">\n <path d=\"M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z\" />\n </svg>\n Trusted by Millions\n </div>\n </div>\n </div>\n\n {/* Stripe Embedded Component Container */}\n <div className=\"stripe-onboarding-container\">\n <div ref={containerRef} className=\"min-h-[400px]\" />\n </div>\n\n {/* Security Footer */}\n <div className=\"mt-8 pt-6 border-t border-gray-200\">\n <div className=\"text-center text-sm text-gray-500\">\n <p className=\"mb-2\">\n 🔒 Your data is protected by industry-leading security measures\n </p>\n <p>\n Questions? Contact our support team at{' '}\n <a href=\"mailto:support@blackcanyontickets.com\" className=\"text-blue-600 hover:text-blue-800\">\n support@blackcanyontickets.com\n </a>\n </p>\n </div>\n </div>\n </div>\n </div>\n </main>\n </div>\n );\n};\n\nexport default StripeEmbeddedOnboarding;","usedDeprecatedRules":[]},{"filePath":"/home/tyler/apps/bct-whitelabel/src/components/SuperAdminDashboard.tsx","messages":[{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":31,"column":36,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":31,"endColumn":39,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[1158,1161],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[1158,1161],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'err' is defined but never used.","line":107,"column":14,"nodeType":null,"messageId":"unusedVar","endLine":107,"endColumn":17},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":122,"column":14,"nodeType":null,"messageId":"unusedVar","endLine":122,"endColumn":19},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":145,"column":14,"nodeType":null,"messageId":"unusedVar","endLine":145,"endColumn":19},{"ruleId":"no-empty","severity":1,"message":"Empty block statement.","line":145,"column":21,"nodeType":"BlockStatement","messageId":"unexpected","endLine":147,"endColumn":6,"suggestions":[{"messageId":"suggestComment","data":{"type":"block"},"fix":{"range":[5693,5699],"text":" /* empty */ "},"desc":"Add comment inside empty block statement."}]}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":5,"fixableErrorCount":0,"fixableWarningCount":0,"source":"import { useState, useEffect } from 'react';\nimport SuperAdminTabNavigation from './admin/SuperAdminTabNavigation';\nimport AnalyticsTab from './admin/AnalyticsTab';\nimport RevenueTab from './admin/RevenueTab';\nimport EventsTab from './admin/EventsTab';\nimport OrganizersTab from './admin/OrganizersTab';\nimport ManagementTab from './admin/ManagementTab';\nimport { makeAuthenticatedRequest } from '../lib/api-client';\n\ninterface SuperAdminDashboardProps {\n // No props needed for now - keeping interface for future expansion\n readonly _placeholder?: never;\n}\n\ninterface PlatformMetrics {\n totalRevenue: number;\n totalFees: number;\n activeOrganizers: number;\n totalTickets: number;\n revenueChange: number;\n feesChange: number;\n organizersChange: number;\n ticketsChange: number;\n}\n\nexport default function SuperAdminDashboard(_props: SuperAdminDashboardProps) {\n const [activeTab, setActiveTab] = useState('analytics');\n const [platformMetrics, setPlatformMetrics] = useState<PlatformMetrics | null>(null);\n const [loading, setLoading] = useState(true);\n const [error, setError] = useState<string | null>(null);\n const [user, setUser] = useState<any>(null);\n\n const tabs = [\n {\n id: 'analytics',\n name: 'Analytics',\n icon: (\n <svg className=\"w-5 h-5\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={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 002 2h2a2 2 0 002-2V5a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2z\" />\n </svg>\n ),\n component: AnalyticsTab\n },\n {\n id: 'revenue',\n name: 'Revenue & Performance',\n icon: (\n <svg className=\"w-5 h-5\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={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-1\" />\n </svg>\n ),\n component: RevenueTab\n },\n {\n id: 'events',\n name: 'Events & Tickets',\n icon: (\n <svg className=\"w-5 h-5\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M15 5v2m0 4v2m0 4v2M5 5a2 2 0 00-2 2v3a2 2 0 110 4v3a2 2 0 002 2h14a2 2 0 002-2v-3a2 2 0 110-4V7a2 2 0 00-2-2H5z\" />\n </svg>\n ),\n component: EventsTab\n },\n {\n id: 'organizers',\n name: 'Organizers',\n icon: (\n <svg className=\"w-5 h-5\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z\" />\n </svg>\n ),\n component: OrganizersTab\n },\n {\n id: 'management',\n name: 'Management',\n icon: (\n <svg className=\"w-5 h-5\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z\" />\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M15 12a3 3 0 11-6 0 3 3 0 016 0z\" />\n </svg>\n ),\n component: ManagementTab\n }\n ];\n\n useEffect(() => {\n initializeComponent();\n }, []);\n\n const initializeComponent = async () => {\n try {\n setLoading(true);\n setError(null);\n \n // Check super admin authentication\n const authResult = await checkSuperAdminAuth();\n if (!authResult) {\n window.location.href = '/login';\n return;\n }\n \n setUser(authResult.user);\n \n // Load platform metrics\n await loadPlatformMetrics();\n } catch (err) {\n\n setError('Failed to load dashboard data. Please try again.');\n } finally {\n setLoading(false);\n }\n };\n\n const checkSuperAdminAuth = async () => {\n try {\n const result = await makeAuthenticatedRequest('/api/admin/check-super-admin');\n if (result.success && result.data.isSuperAdmin) {\n return result.data;\n }\n return null;\n } catch (error) {\n\n return null;\n }\n };\n\n const loadPlatformMetrics = async () => {\n try {\n const result = await makeAuthenticatedRequest('/api/admin/super-analytics?metric=platform_overview');\n \n if (result.success) {\n const data = result.data.summary;\n setPlatformMetrics({\n totalRevenue: data.totalRevenue || 0,\n totalFees: data.totalPlatformFees || 0,\n activeOrganizers: data.activeOrganizers || 0,\n totalTickets: data.totalTickets || 0,\n revenueChange: data.revenueGrowth || 0,\n feesChange: data.feesGrowth || 0,\n organizersChange: data.organizersThisMonth || 0,\n ticketsChange: data.ticketsThisMonth || 0\n });\n }\n } catch (error) {\n\n }\n };\n\n // Loading state\n if (loading) {\n return (\n <div className=\"flex items-center justify-center py-12\">\n <div className=\"text-center\">\n <div className=\"animate-spin rounded-full h-8 w-8 border-2 border-white border-t-transparent mx-auto mb-4\"></div>\n <p className=\"text-white/80\">Loading super admin dashboard...</p>\n </div>\n </div>\n );\n }\n\n // Error state\n if (error) {\n return (\n <div className=\"flex items-center justify-center py-12\">\n <div className=\"text-center\">\n <div className=\"w-12 h-12 bg-red-500/20 rounded-xl flex items-center justify-center mx-auto mb-4\">\n <svg className=\"w-6 h-6 text-red-400\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-1.964-.833-2.732 0L3.732 16.5c-.77.833.192 2.5 1.732 2.5z\" />\n </svg>\n </div>\n <p className=\"text-red-400 font-medium mb-2\">Error Loading Dashboard</p>\n <p className=\"text-white/70 text-sm mb-4\">{error}</p>\n <button\n onClick={() => window.location.reload()}\n className=\"px-4 py-2 bg-white/10 hover:bg-white/20 border border-white/20 rounded-lg text-white text-sm transition-colors\"\n >\n Try Again\n </button>\n </div>\n </div>\n );\n }\n\n return (\n <div className=\"space-y-6\">\n {/* Page Header */}\n <div className=\"mb-8\">\n <h2 className=\"text-4xl md:text-5xl font-light text-white tracking-wide\">Business Intelligence</h2>\n <p className=\"text-white/80 mt-2 text-lg font-light\">Platform-wide analytics and performance insights</p>\n </div>\n\n {/* Key Metrics Dashboard */}\n {platformMetrics && (\n <div className=\"grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 mb-8\">\n <div className=\"bg-white/10 backdrop-blur-xl border border-white/20 rounded-2xl shadow-lg p-6\">\n <div className=\"flex items-center justify-between mb-4\">\n <div className=\"w-12 h-12 bg-gradient-to-br from-green-500/20 to-emerald-500/20 rounded-xl flex items-center justify-center\">\n <svg className=\"w-6 h-6 text-green-400\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth=\"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-1\"></path>\n </svg>\n </div>\n <div className=\"text-right\">\n <p className=\"text-sm font-medium text-white/80\">Platform Revenue</p>\n <p className=\"text-2xl font-light text-white\">${platformMetrics.totalRevenue.toLocaleString()}</p>\n <p className=\"text-sm text-green-400\">+{platformMetrics.revenueChange}% vs last month</p>\n </div>\n </div>\n </div>\n\n <div className=\"bg-white/10 backdrop-blur-xl border border-white/20 rounded-2xl shadow-lg p-6\">\n <div className=\"flex items-center justify-between mb-4\">\n <div className=\"w-12 h-12 bg-gradient-to-br from-purple-500/20 to-pink-500/20 rounded-xl flex items-center justify-center\">\n <svg className=\"w-6 h-6 text-purple-400\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth=\"2\" d=\"M9 7h6m0 10v-3m-3 3h.01M9 17h.01M9 14h.01M12 14h.01M15 11h.01M12 11h.01M9 11h.01M7 21h10a2 2 0 002-2V5a2 2 0 00-2-2H7a2 2 0 00-2 2v14a2 2 0 002 2z\"></path>\n </svg>\n </div>\n <div className=\"text-right\">\n <p className=\"text-sm font-medium text-white/80\">Platform Fees</p>\n <p className=\"text-2xl font-light text-white\">${platformMetrics.totalFees.toLocaleString()}</p>\n <p className=\"text-sm text-purple-400\">+{platformMetrics.feesChange}% vs last month</p>\n </div>\n </div>\n </div>\n\n <div className=\"bg-white/10 backdrop-blur-xl border border-white/20 rounded-2xl shadow-lg p-6\">\n <div className=\"flex items-center justify-between mb-4\">\n <div className=\"w-12 h-12 bg-gradient-to-br from-blue-500/20 to-cyan-500/20 rounded-xl flex items-center justify-center\">\n <svg className=\"w-6 h-6 text-blue-400\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth=\"2\" d=\"M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z\"></path>\n </svg>\n </div>\n <div className=\"text-right\">\n <p className=\"text-sm font-medium text-white/80\">Active Organizers</p>\n <p className=\"text-2xl font-light text-white\">{platformMetrics.activeOrganizers}</p>\n <p className=\"text-sm text-blue-400\">+{platformMetrics.organizersChange} this month</p>\n </div>\n </div>\n </div>\n\n <div className=\"bg-white/10 backdrop-blur-xl border border-white/20 rounded-2xl shadow-lg p-6\">\n <div className=\"flex items-center justify-between mb-4\">\n <div className=\"w-12 h-12 bg-gradient-to-br from-yellow-500/20 to-orange-500/20 rounded-xl flex items-center justify-center\">\n <svg className=\"w-6 h-6 text-yellow-400\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth=\"2\" d=\"M15 5v2m0 4v2m0 4v2M5 5a2 2 0 00-2 2v3a2 2 0 110 4v3a2 2 0 002 2h14a2 2 0 002-2v-3a2 2 0 110-4V7a2 2 0 00-2-2H5z\"></path>\n </svg>\n </div>\n <div className=\"text-right\">\n <p className=\"text-sm font-medium text-white/80\">Tickets Sold</p>\n <p className=\"text-2xl font-light text-white\">{platformMetrics.totalTickets}</p>\n <p className=\"text-sm text-yellow-400\">+{platformMetrics.ticketsChange} this month</p>\n </div>\n </div>\n </div>\n </div>\n )}\n\n <SuperAdminTabNavigation\n tabs={tabs}\n activeTab={activeTab}\n onTabChange={setActiveTab}\n platformMetrics={platformMetrics}\n user={user}\n />\n </div>\n );\n}","usedDeprecatedRules":[]},{"filePath":"/home/tyler/apps/bct-whitelabel/src/components/TemplateManager.tsx","messages":[{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":42,"column":14,"nodeType":null,"messageId":"unusedVar","endLine":42,"endColumn":19},{"ruleId":"no-empty","severity":1,"message":"Empty block statement.","line":42,"column":21,"nodeType":"BlockStatement","messageId":"unexpected","endLine":44,"endColumn":6,"suggestions":[{"messageId":"suggestComment","data":{"type":"block"},"fix":{"range":[1225,1231],"text":" /* empty */ "},"desc":"Add comment inside empty block statement."}]},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":72,"column":14,"nodeType":null,"messageId":"unusedVar","endLine":72,"endColumn":19},{"ruleId":"no-empty","severity":1,"message":"Empty block statement.","line":72,"column":21,"nodeType":"BlockStatement","messageId":"unexpected","endLine":74,"endColumn":6,"suggestions":[{"messageId":"suggestComment","data":{"type":"block"},"fix":{"range":[2012,2018],"text":" /* empty */ "},"desc":"Add comment inside empty block statement."}]},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":93,"column":14,"nodeType":null,"messageId":"unusedVar","endLine":93,"endColumn":19},{"ruleId":"no-empty","severity":1,"message":"Empty block statement.","line":93,"column":21,"nodeType":"BlockStatement","messageId":"unexpected","endLine":95,"endColumn":6,"suggestions":[{"messageId":"suggestComment","data":{"type":"block"},"fix":{"range":[2523,2529],"text":" /* empty */ "},"desc":"Add comment inside empty block statement."}]},{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":98,"column":47,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":98,"endColumn":50,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[2583,2586],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[2583,2586],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":115,"column":14,"nodeType":null,"messageId":"unusedVar","endLine":115,"endColumn":19},{"ruleId":"no-empty","severity":1,"message":"Empty block statement.","line":115,"column":21,"nodeType":"BlockStatement","messageId":"unexpected","endLine":117,"endColumn":6,"suggestions":[{"messageId":"suggestComment","data":{"type":"block"},"fix":{"range":[3054,3060],"text":" /* empty */ "},"desc":"Add comment inside empty block statement."}]},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'pageData' is defined but never used. Allowed unused args must match /^_/u.","line":120,"column":34,"nodeType":null,"messageId":"unusedVar","endLine":120,"endColumn":42},{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":120,"column":44,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":120,"endColumn":47,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[3111,3114],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[3111,3114],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":11,"fixableErrorCount":0,"fixableWarningCount":0,"source":"import React, { useState, useEffect } from 'react';\nimport PageBuilder from './PageBuilder';\n\ninterface Template {\n id: string;\n name: string;\n description: string;\n preview_image_url?: string;\n created_at: string;\n updated_at: string;\n is_active: boolean;\n}\n\ninterface TemplateManagerProps {\n organizationId: string;\n onTemplateSelect?: (template: Template) => void;\n}\n\nconst TemplateManager: React.FC<TemplateManagerProps> = ({\n organizationId,\n onTemplateSelect\n}) => {\n const [templates, setTemplates] = useState<Template[]>([]);\n const [loading, setLoading] = useState(true);\n const [showBuilder, setShowBuilder] = useState(false);\n const [selectedTemplate, setSelectedTemplate] = useState<Template | null>(null);\n const [showCreateModal, setShowCreateModal] = useState(false);\n const [newTemplate, setNewTemplate] = useState({ name: '', description: '' });\n\n useEffect(() => {\n loadTemplates();\n }, [organizationId]);\n\n const loadTemplates = async () => {\n try {\n const response = await fetch(`/api/templates?organization_id=${organizationId}`);\n const data = await response.json();\n \n if (data.success) {\n setTemplates(data.templates);\n }\n } catch (error) {\n\n } finally {\n setLoading(false);\n }\n };\n\n const handleCreateTemplate = async () => {\n if (!newTemplate.name.trim()) return;\n\n try {\n const response = await fetch('/api/templates', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n name: newTemplate.name,\n description: newTemplate.description,\n organization_id: organizationId\n })\n });\n\n const data = await response.json();\n \n if (data.success) {\n setTemplates([...templates, data.template]);\n setShowCreateModal(false);\n setNewTemplate({ name: '', description: '' });\n setSelectedTemplate(data.template);\n setShowBuilder(true);\n }\n } catch (error) {\n\n }\n };\n\n const handleEditTemplate = (template: Template) => {\n setSelectedTemplate(template);\n setShowBuilder(true);\n };\n\n const handleDeleteTemplate = async (templateId: string) => {\n if (!confirm('Are you sure you want to delete this template?')) return;\n\n try {\n const response = await fetch(`/api/templates/${templateId}`, {\n method: 'DELETE'\n });\n\n if (response.ok) {\n setTemplates(templates.filter(t => t.id !== templateId));\n }\n } catch (error) {\n\n }\n };\n\n const handleSaveTemplate = async (pageData: any) => {\n if (!selectedTemplate) return;\n\n try {\n const response = await fetch(`/api/templates/${selectedTemplate.id}`, {\n method: 'PUT',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n page_data: pageData,\n updated_at: new Date().toISOString()\n })\n });\n\n if (response.ok) {\n alert('Template saved successfully!');\n loadTemplates();\n }\n } catch (error) {\n\n }\n };\n\n const handlePreviewTemplate = (pageData: any) => {\n // Open preview in new window\n const previewWindow = window.open('', '_blank', 'width=1200,height=800');\n if (previewWindow) {\n previewWindow.document.write(`\n <html>\n <head>\n <title>Template Preview</title>\n <link href=\"https://cdn.jsdelivr.net/npm/tailwindcss@2.2.19/dist/tailwind.min.css\" rel=\"stylesheet\">\n </head>\n <body class=\"bg-gray-100\">\n <div class=\"p-8\">\n <div class=\"max-w-4xl mx-auto bg-white rounded-lg shadow-lg\">\n <div class=\"p-6\">\n <h1 class=\"text-2xl font-bold mb-4\">Template Preview</h1>\n <div id=\"preview-content\">\n <!-- Preview content would be rendered here -->\n <p class=\"text-gray-600\">Preview functionality coming soon...</p>\n </div>\n </div>\n </div>\n </div>\n </body>\n </html>\n `);\n }\n };\n\n if (loading) {\n return (\n <div className=\"flex items-center justify-center h-64\">\n <div className=\"animate-spin rounded-full h-8 w-8 border-b-2 border-blue-500\"></div>\n </div>\n );\n }\n\n if (showBuilder) {\n return (\n <div className=\"h-screen\">\n <div className=\"bg-white border-b border-gray-200 p-4 flex items-center justify-between\">\n <div className=\"flex items-center space-x-4\">\n <button\n onClick={() => setShowBuilder(false)}\n className=\"flex items-center px-3 py-2 text-sm text-gray-600 hover:text-gray-900 border border-gray-300 rounded-md hover:bg-gray-50\"\n >\n ← Back to Templates\n </button>\n <h1 className=\"text-lg font-semibold\">\n {selectedTemplate ? `Editing: ${selectedTemplate.name}` : 'New Template'}\n </h1>\n </div>\n </div>\n \n <PageBuilder\n eventId=\"\" // Templates don't have specific event IDs\n templateId={selectedTemplate?.id}\n onSave={handleSaveTemplate}\n onPreview={handlePreviewTemplate}\n />\n </div>\n );\n }\n\n return (\n <div className=\"p-6\">\n <div className=\"flex items-center justify-between mb-6\">\n <h1 className=\"text-2xl font-bold text-gray-900\">Page Templates</h1>\n <button\n onClick={() => setShowCreateModal(true)}\n className=\"px-4 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700 transition-colors\"\n >\n Create New Template\n </button>\n </div>\n\n {/* Templates Grid */}\n <div className=\"grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6\">\n {templates.map((template) => (\n <div\n key={template.id}\n className=\"bg-white rounded-lg shadow-md border border-gray-200 overflow-hidden hover:shadow-lg transition-shadow\"\n >\n {/* Template Preview */}\n <div className=\"aspect-video bg-gray-100 flex items-center justify-center\">\n {template.preview_image_url ? (\n <img\n src={template.preview_image_url}\n alt={template.name}\n className=\"w-full h-full object-cover\"\n />\n ) : (\n <div className=\"text-gray-400\">\n <div className=\"text-4xl mb-2\">🎨</div>\n <p className=\"text-sm\">No Preview</p>\n </div>\n )}\n </div>\n\n {/* Template Info */}\n <div className=\"p-4\">\n <h3 className=\"font-semibold text-gray-900 mb-1\">{template.name}</h3>\n {template.description && (\n <p className=\"text-sm text-gray-600 mb-3\">{template.description}</p>\n )}\n \n <div className=\"flex items-center justify-between text-xs text-gray-500 mb-3\">\n <span>Updated {new Date(template.updated_at).toLocaleDateString()}</span>\n <span className={`px-2 py-1 rounded-full ${\n template.is_active \n ? 'bg-green-100 text-green-800' \n : 'bg-gray-100 text-gray-600'\n }`}>\n {template.is_active ? 'Active' : 'Inactive'}\n </span>\n </div>\n\n {/* Actions */}\n <div className=\"flex space-x-2\">\n <button\n onClick={() => handleEditTemplate(template)}\n className=\"flex-1 px-3 py-2 text-sm text-blue-600 hover:text-blue-800 border border-blue-300 rounded-md hover:bg-blue-50\"\n >\n Edit\n </button>\n <button\n onClick={() => onTemplateSelect?.(template)}\n className=\"flex-1 px-3 py-2 text-sm text-green-600 hover:text-green-800 border border-green-300 rounded-md hover:bg-green-50\"\n >\n Use\n </button>\n <button\n onClick={() => handleDeleteTemplate(template.id)}\n className=\"px-3 py-2 text-sm text-red-600 hover:text-red-800 border border-red-300 rounded-md hover:bg-red-50\"\n >\n Delete\n </button>\n </div>\n </div>\n </div>\n ))}\n </div>\n\n {/* Empty State */}\n {templates.length === 0 && (\n <div className=\"text-center py-12\">\n <div className=\"text-6xl mb-4\">🎨</div>\n <h3 className=\"text-lg font-medium text-gray-900 mb-2\">No Templates Yet</h3>\n <p className=\"text-gray-600 mb-4\">Create your first template to get started</p>\n <button\n onClick={() => setShowCreateModal(true)}\n className=\"px-4 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700 transition-colors\"\n >\n Create Your First Template\n </button>\n </div>\n )}\n\n {/* Create Template Modal */}\n {showCreateModal && (\n <div className=\"fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center p-4 z-50\">\n <div className=\"bg-white rounded-lg max-w-md w-full p-6\">\n <h2 className=\"text-xl font-bold mb-4\">Create New Template</h2>\n \n <div className=\"space-y-4\">\n <div>\n <label className=\"block text-sm font-medium text-gray-700 mb-1\">\n Template Name\n </label>\n <input\n type=\"text\"\n value={newTemplate.name}\n onChange={(e) => setNewTemplate({ ...newTemplate, name: e.target.value })}\n className=\"w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500\"\n placeholder=\"Enter template name\"\n />\n </div>\n \n <div>\n <label className=\"block text-sm font-medium text-gray-700 mb-1\">\n Description (Optional)\n </label>\n <textarea\n value={newTemplate.description}\n onChange={(e) => setNewTemplate({ ...newTemplate, description: e.target.value })}\n className=\"w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500\"\n rows={3}\n placeholder=\"Describe your template\"\n />\n </div>\n </div>\n \n <div className=\"flex justify-end space-x-2 mt-6\">\n <button\n onClick={() => setShowCreateModal(false)}\n className=\"px-4 py-2 text-gray-600 hover:text-gray-800 border border-gray-300 rounded-md hover:bg-gray-50\"\n >\n Cancel\n </button>\n <button\n onClick={handleCreateTemplate}\n className=\"px-4 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700 transition-colors\"\n >\n Create Template\n </button>\n </div>\n </div>\n </div>\n )}\n </div>\n );\n};\n\nexport default TemplateManager;","usedDeprecatedRules":[]},{"filePath":"/home/tyler/apps/bct-whitelabel/src/components/ThemeToggle.tsx","messages":[{"ruleId":"no-undef","severity":2,"message":"'CustomEvent' is not defined.","line":10,"column":21,"nodeType":"Identifier","messageId":"undef","endLine":10,"endColumn":32},{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":26,"column":37,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":26,"endColumn":40,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[748,751],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[748,751],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"no-undef","severity":2,"message":"'CustomEvent' is not defined.","line":31,"column":32,"nodeType":"Identifier","messageId":"undef","endLine":31,"endColumn":43}],"suppressedMessages":[],"errorCount":2,"fatalErrorCount":0,"warningCount":1,"fixableErrorCount":0,"fixableWarningCount":0,"source":"import React, { useState, useEffect } from 'react';\nimport { getCurrentTheme, toggleTheme as toggleThemeUtil } from '../lib/theme';\n\n// Extend Window interface for theme events\ndeclare global {\n interface Window {\n __INITIAL_THEME__?: 'light' | 'dark';\n }\n interface WindowEventMap {\n 'themeChanged': CustomEvent<{ theme: 'light' | 'dark' }>;\n }\n}\n\ninterface ThemeToggleProps {\n className?: string;\n}\n\nexport default function ThemeToggle({ className = '' }: ThemeToggleProps) {\n const [theme, setTheme] = useState<'light' | 'dark'>('dark');\n const [mounted, setMounted] = useState(false);\n\n useEffect(() => {\n setMounted(true);\n \n // Get current theme - use the initial theme if available\n const currentTheme = (window as any).__INITIAL_THEME__ || getCurrentTheme();\n setTheme(currentTheme);\n \n // Listen for theme changes\n const handleThemeChange = (e: Event) => {\n const customEvent = e as CustomEvent<{ theme: 'light' | 'dark' }>;\n setTheme(customEvent.detail.theme);\n };\n \n window.addEventListener('themeChanged', handleThemeChange);\n \n return () => {\n window.removeEventListener('themeChanged', handleThemeChange);\n };\n }, []);\n\n const handleToggle = () => {\n const newTheme = toggleThemeUtil();\n setTheme(newTheme);\n };\n\n // Don't render until mounted to avoid hydration mismatch\n if (!mounted) {\n return (\n <button \n disabled \n className={`px-3 py-2 rounded-lg cursor-not-allowed opacity-50 ${className}`}\n style={{\n background: 'var(--glass-bg-button)',\n color: 'var(--glass-text-tertiary)',\n border: '1px solid var(--glass-border)',\n }}\n >\n <svg className=\"w-5 h-5\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <circle cx=\"12\" cy=\"12\" r=\"5\"/>\n <path d=\"M12 1v2M12 21v2M4.22 4.22l1.42 1.42M18.36 18.36l1.42 1.42M1 12h2M21 12h2M4.22 19.78l1.42-1.42M18.36 5.64l1.42-1.42\"/>\n </svg>\n </button>\n );\n }\n\n return (\n <button\n onClick={handleToggle}\n className={`px-3 py-2 rounded-lg transition-all duration-200 hover:scale-105 ${className}`}\n style={{\n background: 'var(--glass-bg-button)',\n color: 'var(--glass-text-primary)',\n border: '1px solid var(--glass-border)',\n }}\n title={`Switch to ${theme === 'light' ? 'dark' : 'light'} mode`}\n aria-label={`Switch to ${theme === 'light' ? 'dark' : 'light'} mode`}\n >\n {theme === 'light' ? (\n <svg className=\"w-5 h-5\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M12 3v1m0 16v1m9-9h-1M4 12H3m15.364 6.364l-.707-.707M6.343 6.343l-.707-.707m12.728 0l-.707.707M6.343 17.657l-.707.707M16 12a4 4 0 11-8 0 4 4 0 018 0z\" />\n </svg>\n ) : (\n <svg className=\"w-5 h-5\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M20.354 15.354A9 9 0 018.646 3.646 9.003 9.003 0 0012 21a9.003 9.003 0 008.354-5.646z\" />\n </svg>\n )}\n </button>\n );\n}","usedDeprecatedRules":[]},{"filePath":"/home/tyler/apps/bct-whitelabel/src/components/TicketCheckout.tsx","messages":[{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":51,"column":70,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":51,"endColumn":73,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[1320,1323],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[1320,1323],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":52,"column":78,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":52,"endColumn":81,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[1415,1418],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[1415,1418],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":60,"column":58,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":60,"endColumn":61,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[1909,1912],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[1909,1912],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":112,"column":18,"nodeType":null,"messageId":"unusedVar","endLine":112,"endColumn":23},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":156,"column":18,"nodeType":null,"messageId":"unusedVar","endLine":156,"endColumn":23},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'releaseError' is defined but never used.","line":182,"column":18,"nodeType":null,"messageId":"unusedVar","endLine":182,"endColumn":30},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'purchaseAttempt' is assigned a value but never used. Allowed unused vars must match /^_/u.","line":280,"column":13,"nodeType":null,"messageId":"unusedVar","endLine":280,"endColumn":28},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":328,"column":14,"nodeType":null,"messageId":"unusedVar","endLine":328,"endColumn":19},{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":469,"column":34,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":469,"endColumn":37,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[18247,18250],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[18247,18250],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":663,"column":35,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":663,"endColumn":38,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[29765,29768],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[29765,29768],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":10,"fixableErrorCount":0,"fixableWarningCount":0,"source":"import React, { useState, useEffect } from 'react';\nimport { inventoryManager } from '../lib/inventory';\nimport { calculateFeeBreakdown } from '../lib/stripe';\nimport { \n formatAvailabilityDisplay, \n shouldShowTicketType, \n defaultAvailabilitySettings,\n type EventAvailabilitySettings,\n type AvailabilityInfo \n} from '../lib/availabilityDisplay';\n\ninterface TicketType {\n id: string;\n name: string;\n description?: string;\n price: number;\n quantity_available?: number;\n is_active: boolean;\n requires_presale_code?: boolean;\n presale_start_time?: string;\n presale_end_time?: string;\n general_sale_start_time?: string;\n}\n\ninterface EventData {\n id: string;\n title: string;\n ticket_types: TicketType[];\n availability_display_mode?: 'available_only' | 'show_quantity' | 'smart_threshold';\n availability_threshold?: number;\n show_sold_out?: boolean;\n low_stock_threshold?: number;\n availability_messages?: {\n available: string;\n low_stock: string;\n sold_out: string;\n unlimited: string;\n };\n organizations: {\n platform_fee_type?: string;\n platform_fee_percentage?: number;\n platform_fee_fixed?: number;\n };\n}\n\ninterface Props {\n event: EventData;\n}\n\nexport default function TicketCheckout({ event }: Props) {\n const [selectedTickets, setSelectedTickets] = useState<Map<string, any>>(new Map());\n const [currentReservations, setCurrentReservations] = useState<Map<string, any>>(new Map());\n const [availability, setAvailability] = useState<Map<string, AvailabilityInfo>>(new Map());\n const [loading, setLoading] = useState(true);\n const [timeRemaining, setTimeRemaining] = useState<string>('');\n const [email, setEmail] = useState('');\n const [name, setName] = useState('');\n const [presaleCode, setPresaleCode] = useState('');\n const [presaleCodeValidated, setPresaleCodeValidated] = useState(false);\n const [presaleCodeData, setPresaleCodeData] = useState<any>(null);\n const [presaleCodeError, setPresaleCodeError] = useState('');\n const [expandedDescriptions, setExpandedDescriptions] = useState<Set<string>>(new Set());\n\n // Check if presale is currently active\n const hasActivePresale = event.ticket_types?.some(ticketType => {\n if (!ticketType.requires_presale_code) return false;\n \n const now = new Date();\n const presaleStart = ticketType.presale_start_time ? new Date(ticketType.presale_start_time) : null;\n const presaleEnd = ticketType.presale_end_time ? new Date(ticketType.presale_end_time) : null;\n const generalSaleStart = ticketType.general_sale_start_time ? new Date(ticketType.general_sale_start_time) : null;\n \n // If general sale hasn't started yet, check if we're in presale period\n if (generalSaleStart && now < generalSaleStart) {\n // If presale has specific timing, check if we're in the window\n if (presaleStart && presaleEnd) {\n return now >= presaleStart && now <= presaleEnd;\n } else if (presaleStart) {\n return now >= presaleStart;\n }\n return true; // Presale required but no specific timing - assume active\n }\n \n // If general sale has started, presale is no longer active\n return false;\n }) || false;\n\n const feeStructure = event?.organizations ? {\n fee_type: event.organizations.platform_fee_type,\n fee_percentage: event.organizations.platform_fee_percentage,\n fee_fixed: event.organizations.platform_fee_fixed\n } : null;\n\n // Get availability settings with defaults\n const availabilitySettings: EventAvailabilitySettings = {\n availability_display_mode: event.availability_display_mode || defaultAvailabilitySettings.availability_display_mode,\n availability_threshold: event.availability_threshold || defaultAvailabilitySettings.availability_threshold,\n show_sold_out: event.show_sold_out ?? defaultAvailabilitySettings.show_sold_out,\n low_stock_threshold: event.low_stock_threshold || defaultAvailabilitySettings.low_stock_threshold,\n availability_messages: event.availability_messages || defaultAvailabilitySettings.availability_messages\n };\n\n // Load availability for all ticket types\n useEffect(() => {\n async function loadAvailability() {\n const availabilityMap = new Map();\n \n for (const ticketType of event.ticket_types?.filter(tt => tt.is_active) || []) {\n try {\n const avail = await inventoryManager.getAvailability(ticketType.id);\n availabilityMap.set(ticketType.id, avail);\n } catch (error) {\n availabilityMap.set(ticketType.id, { is_available: false, error: true });\n }\n }\n \n setAvailability(availabilityMap);\n setLoading(false);\n }\n\n loadAvailability();\n }, [event.ticket_types]);\n\n // Timer effect\n useEffect(() => {\n if (currentReservations.size === 0) return;\n\n const timer = setInterval(() => {\n const firstReservation = Array.from(currentReservations.values())[0];\n if (firstReservation) {\n const now = new Date().getTime();\n const expiry = new Date(firstReservation.expires_at).getTime();\n const timeLeft = expiry - now;\n\n if (timeLeft <= 0) {\n alert('Your ticket reservation has expired. Please select your tickets again.');\n window.location.reload();\n } else {\n const minutes = Math.floor(timeLeft / 60000);\n const seconds = Math.floor((timeLeft % 60000) / 1000);\n setTimeRemaining(`${minutes}:${seconds.toString().padStart(2, '0')}`);\n }\n }\n }, 1000);\n\n return () => clearInterval(timer);\n }, [currentReservations]);\n\n // Cleanup effect - release reservations when component unmounts or on page unload\n useEffect(() => {\n const handleBeforeUnload = () => {\n // Release all active reservations\n currentReservations.forEach(async (reservation) => {\n try {\n await inventoryManager.releaseReservation(reservation.id);\n } catch (error) {\n // Silently fail - page is unloading\n }\n });\n };\n\n window.addEventListener('beforeunload', handleBeforeUnload);\n\n return () => {\n window.removeEventListener('beforeunload', handleBeforeUnload);\n // Also release reservations on component unmount\n handleBeforeUnload();\n };\n }, [currentReservations]);\n\n const handleQuantityChange = async (ticketTypeId: string, newQuantity: number) => {\n const currentQuantity = selectedTickets.get(ticketTypeId)?.quantity || 0;\n \n if (newQuantity === currentQuantity) return;\n\n try {\n // Release existing reservation if any\n if (currentReservations.has(ticketTypeId)) {\n const existingReservation = currentReservations.get(ticketTypeId);\n try {\n await inventoryManager.releaseReservation(existingReservation.id);\n } catch (releaseError) {\n // Continue anyway - the reservation might have already expired\n }\n \n // Always remove from local state regardless of API result\n const newReservations = new Map(currentReservations);\n newReservations.delete(ticketTypeId);\n setCurrentReservations(newReservations);\n }\n\n if (newQuantity > 0) {\n // Reserve new tickets\n const reservation = await inventoryManager.reserveTickets(ticketTypeId, newQuantity, 15);\n \n const newReservations = new Map(currentReservations);\n newReservations.set(ticketTypeId, reservation);\n setCurrentReservations(newReservations);\n\n // Update selected tickets\n const ticketType = event.ticket_types?.find(tt => tt.id === ticketTypeId);\n const newSelected = new Map(selectedTickets);\n newSelected.set(ticketTypeId, {\n quantity: newQuantity,\n price: typeof ticketType?.price === 'string' ? Math.round(parseFloat(ticketType.price) * 100) : ticketType?.price,\n name: ticketType?.name,\n reservation_id: reservation.id\n });\n setSelectedTickets(newSelected);\n } else {\n // Remove from selected tickets\n const newSelected = new Map(selectedTickets);\n newSelected.delete(ticketTypeId);\n setSelectedTickets(newSelected);\n }\n } catch (error) {\n // If it's a reservation error, still update the UI but show a warning\n if (error.message && error.message.includes('Reservation not found')) {\n \n // Update selected tickets even if reservation failed\n if (newQuantity > 0) {\n const ticketType = event.ticket_types?.find(tt => tt.id === ticketTypeId);\n const newSelected = new Map(selectedTickets);\n newSelected.set(ticketTypeId, {\n quantity: newQuantity,\n price: typeof ticketType?.price === 'string' ? Math.round(parseFloat(ticketType.price) * 100) : ticketType?.price,\n name: ticketType?.name,\n reservation_id: null // No reservation ID if it failed\n });\n setSelectedTickets(newSelected);\n } else {\n const newSelected = new Map(selectedTickets);\n newSelected.delete(ticketTypeId);\n setSelectedTickets(newSelected);\n }\n } else {\n alert(error.message || 'Error reserving tickets. Please try again.');\n }\n }\n };\n\n const calculateTotals = () => {\n let subtotal = 0;\n let totalQuantity = 0;\n\n for (const ticket of selectedTickets.values()) {\n subtotal += ticket.quantity * ticket.price;\n totalQuantity += ticket.quantity;\n }\n\n if (totalQuantity === 0) {\n return { subtotal: 0, platformFee: 0, total: 0 };\n }\n\n const avgPrice = subtotal / totalQuantity;\n const breakdown = calculateFeeBreakdown(avgPrice / 100, totalQuantity, feeStructure);\n\n return {\n subtotal,\n platformFee: breakdown.totalPlatformFee,\n total: subtotal + breakdown.totalPlatformFee\n };\n };\n\n const handleSubmit = async (e: React.FormEvent) => {\n e.preventDefault();\n \n if (selectedTickets.size === 0) return;\n\n try {\n // Create purchase attempt\n const items = Array.from(selectedTickets.entries()).map(([ticketTypeId, ticket]) => ({\n ticket_type_id: ticketTypeId,\n quantity: ticket.quantity,\n unit_price: ticket.price / 100\n }));\n\n const totals = calculateTotals();\n \n const purchaseAttempt = await inventoryManager.createPurchaseAttempt(\n event.id,\n email,\n name,\n items,\n totals.platformFee / 100\n );\n\n alert('Checkout integration coming soon! Your tickets are reserved.');\n \n } catch (error) {\n alert(error.message || 'Error processing purchase. Please try again.');\n }\n };\n\n const validatePresaleCode = async () => {\n if (!presaleCode.trim()) {\n setPresaleCodeError('Please enter a presale code');\n return;\n }\n\n try {\n const response = await fetch('/api/presale/validate', {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({\n code: presaleCode.trim(),\n event_id: event.id,\n customer_email: email || null,\n customer_session: sessionStorage.getItem('checkout_session') || null\n }),\n });\n\n const data = await response.json();\n\n if (data.success) {\n setPresaleCodeValidated(true);\n setPresaleCodeData(data);\n setPresaleCodeError('');\n // Store session for future validation\n if (!sessionStorage.getItem('checkout_session')) {\n sessionStorage.setItem('checkout_session', Math.random().toString(36));\n }\n } else {\n setPresaleCodeError(data.error || 'Invalid presale code');\n }\n } catch (error) {\n setPresaleCodeError('Error validating code. Please try again.');\n }\n };\n\n const toggleDescription = (ticketTypeId: string) => {\n const newExpanded = new Set(expandedDescriptions);\n if (newExpanded.has(ticketTypeId)) {\n newExpanded.delete(ticketTypeId);\n } else {\n newExpanded.add(ticketTypeId);\n }\n setExpandedDescriptions(newExpanded);\n };\n\n const truncateDescription = (description: string, maxLength: number = 100) => {\n if (description.length <= maxLength) return description;\n return description.substring(0, maxLength) + '...';\n };\n\n const totals = calculateTotals();\n\n if (loading) {\n return <div className=\"text-center py-8\" style={{color: 'var(--ui-text-secondary)'}}>Loading ticket availability...</div>;\n }\n\n return (\n <div className=\"space-y-6\">\n {/* Note: Header moved to parent component */}\n \n {/* Presale Code Entry - Only show if presale is active */}\n {hasActivePresale && !presaleCodeValidated && (\n <div className=\"mb-6 p-4 sm:p-6 rounded-2xl backdrop-blur-lg\" style={{background: 'linear-gradient(to bottom right, var(--glass-bg), var(--glass-bg-lg))', border: '2px solid var(--glass-border)'}}>\n <div className=\"flex flex-col sm:flex-row sm:items-end gap-3 sm:gap-4\">\n <div className=\"flex-1\">\n <label htmlFor=\"presale-code\" className=\"block text-sm font-semibold mb-2\" style={{color: 'var(--glass-text-accent)'}}>\n Presale Code Required\n </label>\n <input\n id=\"presale-code\"\n type=\"text\"\n value={presaleCode}\n onChange={(e) => {\n setPresaleCode(e.target.value.toUpperCase());\n setPresaleCodeError('');\n }}\n placeholder=\"Enter your presale code\"\n className=\"w-full px-4 py-3 border-2 rounded-xl focus:ring-2 transition-all duration-200 backdrop-blur-lg\"\n style={{\n borderColor: 'var(--glass-border)',\n focusRingColor: 'var(--glass-border-focus)',\n focusBorderColor: 'var(--glass-border-focus)',\n color: 'var(--ui-text-primary)',\n background: 'var(--glass-bg-input)',\n '::placeholder': { color: 'var(--glass-placeholder)' }\n }}\n onFocus={(e) => {\n e.target.style.borderColor = 'var(--glass-border-focus)';\n e.target.style.boxShadow = '0 0 0 2px var(--glass-border-focus-shadow)';\n }}\n onBlur={(e) => {\n e.target.style.borderColor = 'var(--glass-border)';\n e.target.style.boxShadow = 'none';\n }}\n onMouseEnter={(e) => e.target.style.borderColor = 'var(--glass-border-focus)'}\n onMouseLeave={(e) => e.target.style.borderColor = 'var(--glass-border)'}\n />\n {presaleCodeError && (\n <p className=\"text-sm mt-2 font-medium\" style={{color: 'var(--error-color)'}}>{presaleCodeError}</p>\n )}\n </div>\n <button\n type=\"button\"\n onClick={validatePresaleCode}\n className=\"w-full sm:w-auto px-6 py-3 rounded-xl font-semibold text-sm whitespace-nowrap transition-all duration-200 shadow-lg hover:shadow-xl touch-manipulation min-h-[44px]\"\n style={{\n background: 'linear-gradient(to right, var(--glass-text-accent), var(--premium-gold))',\n color: 'var(--glass-text-primary)'\n }}\n onMouseEnter={(e) => {\n e.target.style.background = 'linear-gradient(to right, var(--glass-border-focus), var(--premium-gold-border))';\n e.target.style.transform = 'scale(1.02)';\n }}\n onMouseLeave={(e) => {\n e.target.style.background = 'linear-gradient(to right, var(--glass-text-accent), var(--premium-gold))';\n e.target.style.transform = 'scale(1)';\n }}\n >\n Apply Code\n </button>\n </div>\n </div>\n )}\n\n {/* Presale Code Success - Compact version */}\n {presaleCodeValidated && presaleCodeData && (\n <div className=\"mb-4 p-3 rounded-lg backdrop-blur-lg\" style={{background: 'var(--success-bg)', border: '1px solid var(--success-border)'}}>\n <div className=\"flex items-center justify-between\">\n <div className=\"flex items-center gap-2\">\n <svg className=\"w-4 h-4\" style={{color: 'var(--success-color)'}} fill=\"currentColor\" viewBox=\"0 0 20 20\">\n <path fillRule=\"evenodd\" d=\"M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z\" clipRule=\"evenodd\" />\n </svg>\n <span className=\"text-sm font-medium\" style={{color: 'var(--success-color)'}}>\n Presale access granted\n </span>\n </div>\n <button\n type=\"button\"\n onClick={() => {\n setPresaleCodeValidated(false);\n setPresaleCodeData(null);\n setPresaleCode('');\n }}\n className=\"text-sm font-medium transition-colors\"\n style={{color: 'var(--success-color)'}}\n onMouseEnter={(e) => e.target.style.color = 'var(--ui-text-primary)'}\n onMouseLeave={(e) => e.target.style.color = 'var(--success-color)'}\n >\n Remove\n </button>\n </div>\n </div>\n )}\n \n {/* Ticket Type Selection */}\n <div className=\"space-y-4 mb-6\">\n {event.ticket_types\n ?.filter(tt => tt.is_active)\n ?.filter(ticketType => {\n const avail = availability.get(ticketType.id);\n return avail ? shouldShowTicketType(avail, availabilitySettings) : true;\n })\n ?.filter(ticketType => {\n // If ticket type requires presale code, check if user has validated one\n // and if the presale code gives access to this ticket type\n if (ticketType.requires_presale_code) {\n if (!presaleCodeValidated || !presaleCodeData) {\n return false;\n }\n // Check if presale code gives access to this ticket type\n const hasAccess = presaleCodeData.accessible_ticket_types?.some(\n (accessibleType: any) => accessibleType.id === ticketType.id\n );\n if (!hasAccess) {\n return false;\n }\n }\n return true;\n })\n ?.map(ticketType => {\n const avail = availability.get(ticketType.id);\n const selectedQuantity = selectedTickets.get(ticketType.id)?.quantity || 0;\n const price = typeof ticketType.price === 'string' ? parseFloat(ticketType.price) : (ticketType.price / 100);\n \n // Get formatted availability display\n const availabilityDisplay = avail \n ? formatAvailabilityDisplay(avail, availabilitySettings)\n : { text: 'Loading...', className: '', showExactCount: false, isLowStock: false, isSoldOut: false };\n \n return (\n <div \n key={ticketType.id} \n className=\"border-2 rounded-2xl p-4 sm:p-6 transition-all duration-200 backdrop-blur-lg\"\n style={{\n background: availabilityDisplay.isSoldOut \n ? 'var(--ui-bg-secondary)'\n : selectedQuantity > 0\n ? 'linear-gradient(to bottom right, var(--success-bg), var(--glass-bg-lg))'\n : 'var(--ui-bg-elevated)',\n borderColor: availabilityDisplay.isSoldOut \n ? 'var(--ui-border-secondary)'\n : selectedQuantity > 0\n ? 'var(--success-border)'\n : 'var(--ui-border-primary)',\n opacity: availabilityDisplay.isSoldOut ? 0.75 : 1,\n boxShadow: selectedQuantity > 0 ? '0 10px 25px var(--ui-shadow)' : undefined\n }}\n onMouseEnter={(e) => {\n if (!availabilityDisplay.isSoldOut && selectedQuantity === 0) {\n e.target.style.borderColor = 'var(--glass-border-focus)';\n e.target.style.boxShadow = '0 4px 12px var(--ui-shadow)'\n }\n }}\n onMouseLeave={(e) => {\n if (!availabilityDisplay.isSoldOut && selectedQuantity === 0) {\n e.target.style.borderColor = 'var(--ui-border-primary)';\n e.target.style.boxShadow = 'none';\n }\n }}\n >\n <div className=\"flex flex-col sm:flex-row sm:justify-between sm:items-start gap-4\">\n <div className=\"flex-1\">\n <div className=\"flex flex-col sm:flex-row sm:items-center gap-2 sm:gap-3 mb-3\">\n <h3 className=\"text-lg sm:text-xl font-semibold\" style={{color: 'var(--ui-text-primary)'}}>{ticketType.name}</h3>\n <div className=\"flex gap-2\">\n {availabilityDisplay.isLowStock && (\n <span className=\"inline-flex items-center px-2 sm:px-3 py-1 rounded-full text-xs font-semibold\" style={{background: 'linear-gradient(to right, var(--warning-color), var(--premium-gold))', color: 'var(--glass-text-primary)'}}>\n Low Stock\n </span>\n )}\n {selectedQuantity > 0 && (\n <span className=\"inline-flex items-center px-2 sm:px-3 py-1 rounded-full text-xs font-semibold\" style={{background: 'linear-gradient(to right, var(--success-color), var(--success-color))', color: 'var(--glass-text-primary)'}}>\n {selectedQuantity} Selected\n </span>\n )}\n </div>\n </div>\n {ticketType.description && (\n <div className=\"mb-4 p-3 rounded-xl backdrop-blur-lg\" style={{background: 'var(--ui-bg-secondary)', border: '1px solid var(--ui-border-secondary)'}}>\n <p className=\"text-sm leading-relaxed whitespace-pre-line\" style={{color: 'var(--ui-text-secondary)'}}>\n {expandedDescriptions.has(ticketType.id) \n ? ticketType.description \n : truncateDescription(ticketType.description)\n }\n </p>\n {ticketType.description.length > 100 && (\n <button\n type=\"button\"\n onClick={() => toggleDescription(ticketType.id)}\n className=\"mt-2 text-xs font-medium transition-colors\"\n style={{color: 'var(--glass-text-accent)'}}\n onMouseEnter={(e) => e.target.style.color = 'var(--ui-text-primary)'}\n onMouseLeave={(e) => e.target.style.color = 'var(--glass-text-accent)'}\n >\n {expandedDescriptions.has(ticketType.id) ? 'Show less' : 'Show more'}\n </button>\n )}\n </div>\n )}\n <div className=\"flex flex-col sm:flex-row sm:items-center sm:justify-between gap-3\">\n <div>\n <span className=\"text-xl sm:text-2xl font-bold\" style={{color: 'var(--ui-text-primary)'}}>\n ${price.toFixed(2)}\n </span>\n <span className=\"text-sm block sm:inline sm:ml-3 font-medium\" style={{color: availabilityDisplay.isSoldOut ? 'var(--error-color)' : availabilityDisplay.isLowStock ? 'var(--warning-color)' : 'var(--ui-text-secondary)'}}>\n {availabilityDisplay.text}\n </span>\n </div>\n </div>\n </div>\n <div className=\"flex-shrink-0\">\n <div className=\"flex items-center justify-center sm:justify-start space-x-3\">\n <button\n type=\"button\"\n onClick={() => handleQuantityChange(ticketType.id, Math.max(0, selectedQuantity - 1))}\n disabled={selectedQuantity <= 0 || availabilityDisplay.isSoldOut}\n className=\"w-12 h-12 rounded-xl border-2 font-bold text-lg transition-all duration-200 touch-manipulation\"\n style={{\n borderColor: selectedQuantity <= 0 || availabilityDisplay.isSoldOut \n ? 'var(--ui-border-secondary)' \n : 'var(--ui-border-primary)',\n color: selectedQuantity <= 0 || availabilityDisplay.isSoldOut \n ? 'var(--ui-text-muted)' \n : 'var(--ui-text-secondary)',\n background: selectedQuantity <= 0 || availabilityDisplay.isSoldOut \n ? 'var(--ui-bg-secondary)' \n : 'var(--ui-bg-elevated)',\n cursor: selectedQuantity <= 0 || availabilityDisplay.isSoldOut ? 'not-allowed' : 'pointer'\n }}\n onMouseEnter={(e) => {\n if (!(selectedQuantity <= 0 || availabilityDisplay.isSoldOut)) {\n e.target.style.borderColor = 'var(--error-border)';\n e.target.style.color = 'var(--error-color)';\n e.target.style.background = 'var(--error-bg)';\n }\n }}\n onMouseLeave={(e) => {\n if (!(selectedQuantity <= 0 || availabilityDisplay.isSoldOut)) {\n e.target.style.borderColor = 'var(--ui-border-primary)';\n e.target.style.color = 'var(--ui-text-secondary)';\n e.target.style.background = 'var(--ui-bg-elevated)';\n }\n }}\n >\n \n </button>\n \n <div className=\"w-12 text-center\">\n <span className=\"text-lg font-semibold\" style={{color: 'var(--ui-text-primary)'}}>{selectedQuantity}</span>\n </div>\n \n <button\n type=\"button\"\n onClick={() => handleQuantityChange(ticketType.id, selectedQuantity + 1)}\n disabled={selectedQuantity >= (avail?.available || 0) || availabilityDisplay.isSoldOut}\n className=\"w-12 h-12 rounded-xl border-2 font-bold text-lg transition-all duration-200 touch-manipulation\"\n style={{\n borderColor: selectedQuantity >= (avail?.available || 0) || availabilityDisplay.isSoldOut \n ? 'var(--ui-border-secondary)' \n : 'var(--ui-border-primary)',\n color: selectedQuantity >= (avail?.available || 0) || availabilityDisplay.isSoldOut \n ? 'var(--ui-text-muted)' \n : 'var(--ui-text-secondary)',\n background: selectedQuantity >= (avail?.available || 0) || availabilityDisplay.isSoldOut \n ? 'var(--ui-bg-secondary)' \n : 'var(--ui-bg-elevated)',\n cursor: selectedQuantity >= (avail?.available || 0) || availabilityDisplay.isSoldOut ? 'not-allowed' : 'pointer'\n }}\n onMouseEnter={(e) => {\n if (!(selectedQuantity >= (avail?.available || 0) || availabilityDisplay.isSoldOut)) {\n e.target.style.borderColor = 'var(--success-border)';\n e.target.style.color = 'var(--success-color)';\n e.target.style.background = 'var(--success-bg)';\n }\n }}\n onMouseLeave={(e) => {\n if (!(selectedQuantity >= (avail?.available || 0) || availabilityDisplay.isSoldOut)) {\n e.target.style.borderColor = 'var(--ui-border-primary)';\n e.target.style.color = 'var(--ui-text-secondary)';\n e.target.style.background = 'var(--ui-bg-elevated)';\n }\n }}\n >\n +\n </button>\n </div>\n </div>\n </div>\n </div>\n );\n })}\n \n {/* Show message if no tickets available without presale code */}\n {event.ticket_types?.filter(tt => tt.is_active).length > 0 && \n event.ticket_types?.filter(tt => tt.is_active)\n ?.filter(ticketType => {\n const avail = availability.get(ticketType.id);\n return avail ? shouldShowTicketType(avail, availabilitySettings) : true;\n })\n ?.filter(ticketType => {\n if (ticketType.requires_presale_code) {\n if (!presaleCodeValidated || !presaleCodeData) {\n return false;\n }\n const hasAccess = presaleCodeData.accessible_ticket_types?.some(\n (accessibleType: any) => accessibleType.id === ticketType.id\n );\n if (!hasAccess) {\n return false;\n }\n }\n return true;\n }).length === 0 && (\n <div className=\"text-center py-6 rounded-lg backdrop-blur-lg\" style={{background: 'var(--warning-bg)', border: '1px solid var(--warning-border)'}}>\n <div className=\"w-12 h-12 mx-auto mb-3\" style={{color: 'var(--warning-color)'}}>\n <svg fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth=\"2\" d=\"M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z\" />\n </svg>\n </div>\n <h3 className=\"text-lg font-medium mb-2\" style={{color: 'var(--warning-color)'}}>Presale Access Required</h3>\n <p className=\"text-sm\" style={{color: 'var(--ui-text-secondary)'}}>\n This event is currently in presale. Enter your presale code above to access tickets.\n </p>\n </div>\n )}\n </div>\n\n {/* Reservation Timer */}\n {currentReservations.size > 0 && (\n <div className=\"rounded-2xl p-4 backdrop-blur-lg\" style={{background: 'linear-gradient(to right, var(--warning-bg), var(--premium-gold-bg))', border: '2px solid var(--warning-border)'}}>\n <div className=\"flex items-center\">\n <svg className=\"h-6 w-6 mr-3\" style={{color: 'var(--warning-color)'}} fill=\"currentColor\" viewBox=\"0 0 20 20\">\n <path fillRule=\"evenodd\" d=\"M10 18a8 8 0 100-16 8 8 0 000 16zm1-12a1 1 0 10-2 0v4a1 1 0 00.293.707l2.828 2.829a1 1 0 101.415-1.415L11 9.586V6z\" clipRule=\"evenodd\" />\n </svg>\n <span className=\"text-sm font-semibold\" style={{color: 'var(--warning-color)'}}>\n Tickets reserved for {timeRemaining}\n </span>\n </div>\n </div>\n )}\n\n {/* Order Summary */}\n {selectedTickets.size > 0 && (\n <div className=\"rounded-2xl p-4 sm:p-6 shadow-lg backdrop-blur-lg\" style={{background: 'linear-gradient(to bottom right, var(--ui-bg-secondary), var(--ui-bg-elevated))', border: '2px solid var(--ui-border-primary)'}}>\n <h3 className=\"text-lg sm:text-xl font-semibold mb-4 flex items-center\" style={{color: 'var(--ui-text-primary)'}}>\n <div className=\"w-3 h-3 rounded-full mr-3\" style={{background: 'linear-gradient(to right, var(--success-color), var(--success-color))'}}></div>\n Order Summary\n </h3>\n <div className=\"space-y-3 mb-4\">\n {Array.from(selectedTickets.entries()).map(([ticketTypeId, ticket]) => (\n <div key={ticketTypeId} className=\"flex justify-between items-center p-3 rounded-xl backdrop-blur-lg\" style={{background: 'var(--ui-bg-elevated)', border: '1px solid var(--ui-border-secondary)'}}>\n <span className=\"font-medium truncate mr-2\" style={{color: 'var(--ui-text-primary)'}}>{ticket.quantity}x {ticket.name}</span>\n <span className=\"font-semibold whitespace-nowrap\" style={{color: 'var(--ui-text-primary)'}}>${((ticket.quantity * ticket.price) / 100).toFixed(2)}</span>\n </div>\n ))}\n </div>\n <div className=\"pt-4\" style={{borderTop: '2px solid var(--ui-border-secondary)'}}>\n <div className=\"flex justify-between mb-2\" style={{color: 'var(--ui-text-secondary)'}}>\n <span>Subtotal:</span>\n <span>${(totals.subtotal / 100).toFixed(2)}</span>\n </div>\n <div className=\"flex justify-between mb-3\" style={{color: 'var(--ui-text-secondary)'}}>\n <span>Platform fee:</span>\n <span>${(totals.platformFee / 100).toFixed(2)}</span>\n </div>\n <div className=\"flex justify-between text-lg sm:text-xl font-bold pt-3\" style={{color: 'var(--ui-text-primary)', borderTop: '1px solid var(--ui-border-secondary)'}}>\n <span>Total:</span>\n <span>${(totals.total / 100).toFixed(2)}</span>\n </div>\n </div>\n \n {/* Customer Information - Only show when tickets are selected */}\n <form onSubmit={handleSubmit} className=\"mt-4 sm:mt-6 space-y-4\">\n <div className=\"space-y-4\">\n <div>\n <label htmlFor=\"email\" className=\"block text-sm font-semibold mb-2\" style={{color: 'var(--ui-text-secondary)'}}>\n Email Address\n </label>\n <input\n type=\"email\"\n id=\"email\"\n value={email}\n onChange={(e) => setEmail(e.target.value)}\n required\n className=\"block w-full px-4 py-3 border-2 rounded-xl shadow-sm focus:ring-2 transition-all duration-200 backdrop-blur-lg\"\n style={{\n borderColor: 'var(--ui-border-primary)',\n background: 'var(--ui-bg-elevated)',\n color: 'var(--ui-text-primary)',\n '::placeholder': { color: 'var(--glass-placeholder)' }\n }}\n onFocus={(e) => {\n e.target.style.borderColor = 'var(--glass-border-focus)';\n e.target.style.boxShadow = '0 0 0 2px var(--glass-border-focus-shadow)';\n }}\n onBlur={(e) => {\n e.target.style.borderColor = 'var(--ui-border-primary)';\n e.target.style.boxShadow = 'none';\n }}\n onMouseEnter={(e) => e.target.style.borderColor = 'var(--glass-border-focus)'}\n onMouseLeave={(e) => e.target.style.borderColor = 'var(--ui-border-primary)'}\n placeholder=\"your@email.com\"\n />\n </div>\n\n <div>\n <label htmlFor=\"name\" className=\"block text-sm font-semibold mb-2\" style={{color: 'var(--ui-text-secondary)'}}>\n Full Name\n </label>\n <input\n type=\"text\"\n id=\"name\"\n value={name}\n onChange={(e) => setName(e.target.value)}\n required\n className=\"block w-full px-4 py-3 border-2 rounded-xl shadow-sm focus:ring-2 transition-all duration-200 backdrop-blur-lg\"\n style={{\n borderColor: 'var(--ui-border-primary)',\n background: 'var(--ui-bg-elevated)',\n color: 'var(--ui-text-primary)',\n '::placeholder': { color: 'var(--glass-placeholder)' }\n }}\n onFocus={(e) => {\n e.target.style.borderColor = 'var(--glass-border-focus)';\n e.target.style.boxShadow = '0 0 0 2px var(--glass-border-focus-shadow)';\n }}\n onBlur={(e) => {\n e.target.style.borderColor = 'var(--ui-border-primary)';\n e.target.style.boxShadow = 'none';\n }}\n onMouseEnter={(e) => e.target.style.borderColor = 'var(--glass-border-focus)'}\n onMouseLeave={(e) => e.target.style.borderColor = 'var(--ui-border-primary)'}\n placeholder=\"Your Name\"\n />\n </div>\n </div>\n\n <button\n type=\"submit\"\n className=\"w-full py-4 px-6 rounded-2xl font-semibold text-base sm:text-lg transition-all duration-200 shadow-xl hover:shadow-2xl transform hover:scale-[1.02] touch-manipulation min-h-[44px]\"\n style={{\n background: 'linear-gradient(to right, var(--success-color), var(--success-color))',\n color: 'var(--glass-text-primary)'\n }}\n onMouseEnter={(e) => {\n e.target.style.background = 'linear-gradient(to right, var(--success-color), var(--success-border))';\n e.target.style.transform = 'scale(1.02)';\n }}\n onMouseLeave={(e) => {\n e.target.style.background = 'linear-gradient(to right, var(--success-color), var(--success-color))';\n e.target.style.transform = 'scale(1)';\n }}\n >\n Complete Purchase\n </button>\n </form>\n </div>\n )}\n\n {/* Call to Action - Show when no tickets selected */}\n {selectedTickets.size === 0 && (\n <div className=\"text-center py-8 px-6 rounded-2xl border-2 border-dashed backdrop-blur-lg\" style={{background: 'linear-gradient(to bottom right, var(--ui-bg-secondary), var(--ui-bg-elevated))', borderColor: 'var(--ui-border-primary)'}}>\n <div className=\"w-16 h-16 mx-auto mb-4 rounded-full flex items-center justify-center\" style={{background: 'linear-gradient(to bottom right, var(--ui-text-muted), var(--ui-text-secondary))'}}>\n <svg className=\"w-8 h-8\" style={{color: 'var(--glass-text-primary)'}} fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth=\"2\" d=\"M15 5v2m0 4v2m0 4v2M5 5a2 2 0 00-2 2v3a2 2 0 110 4v3a2 2 0 002 2h14a2 2 0 002-2v-3a2 2 0 110-4V7a2 2 0 00-2-2H5z\" />\n </svg>\n </div>\n <h3 className=\"text-lg font-semibold mb-2\" style={{color: 'var(--ui-text-secondary)'}}>Select Your Tickets</h3>\n <p style={{color: 'var(--ui-text-tertiary)'}}>Choose your preferred seating and quantity above to continue</p>\n </div>\n )}\n\n <div className=\"mt-4 text-center\">\n <p className=\"text-xs\" style={{color: 'var(--ui-text-tertiary)'}}>\n Secure checkout powered by Stripe • Tickets reserved for 15 minutes\n </p>\n </div>\n </div>\n );\n}","usedDeprecatedRules":[]},{"filePath":"/home/tyler/apps/bct-whitelabel/src/components/WhatsHotEvents.tsx","messages":[{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'err' is defined but never used.","line":53,"column":14,"nodeType":null,"messageId":"unusedVar","endLine":53,"endColumn":17},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'index' is defined but never used. Allowed unused args must match /^_/u.","line":180,"column":39,"nodeType":null,"messageId":"unusedVar","endLine":180,"endColumn":44}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":2,"fixableErrorCount":0,"fixableWarningCount":0,"source":"import React, { useState, useEffect } from 'react';\nimport { TrendingEvent, trendingAnalyticsService } from '../lib/analytics';\nimport { geolocationService, LocationData } from '../lib/geolocation';\n\ninterface WhatsHotEventsProps {\n userLocation?: LocationData | null;\n radius?: number;\n limit?: number;\n onEventClick?: (event: TrendingEvent) => void;\n className?: string;\n}\n\nconst WhatsHotEvents: React.FC<WhatsHotEventsProps> = ({\n userLocation,\n radius = 50,\n limit = 8,\n onEventClick,\n className = ''\n}) => {\n const [trendingEvents, setTrendingEvents] = useState<TrendingEvent[]>([]);\n const [isLoading, setIsLoading] = useState(true);\n const [error, setError] = useState<string | null>(null);\n\n useEffect(() => {\n loadTrendingEvents();\n }, [userLocation, radius, limit]);\n\n const loadTrendingEvents = async () => {\n setIsLoading(true);\n setError(null);\n\n try {\n let lat = userLocation?.latitude;\n let lng = userLocation?.longitude;\n\n // If no user location provided, try to get IP location\n if (!lat || !lng) {\n const ipLocation = await geolocationService.getLocationFromIP();\n if (ipLocation) {\n lat = ipLocation.latitude;\n lng = ipLocation.longitude;\n }\n }\n\n const trending = await trendingAnalyticsService.getTrendingEvents(\n lat,\n lng,\n radius,\n limit\n );\n\n setTrendingEvents(trending);\n } catch (err) {\n setError('Failed to load trending events');\n\n } finally {\n setIsLoading(false);\n }\n };\n\n const handleEventClick = (event: TrendingEvent) => {\n // Track the click event\n trendingAnalyticsService.trackEvent({\n eventId: event.eventId,\n metricType: 'page_view',\n sessionId: sessionStorage.getItem('sessionId') || 'anonymous',\n locationData: userLocation ? {\n latitude: userLocation.latitude,\n longitude: userLocation.longitude,\n city: userLocation.city,\n state: userLocation.state\n } : undefined\n });\n\n if (onEventClick) {\n onEventClick(event);\n } else {\n // Navigate to event page\n window.location.href = `/e/${event.slug}`;\n }\n };\n\n const formatEventTime = (startTime: string) => {\n const date = new Date(startTime);\n const now = new Date();\n const diffTime = date.getTime() - now.getTime();\n const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24));\n\n if (diffDays === 0) {\n return 'Today';\n } else if (diffDays === 1) {\n return 'Tomorrow';\n } else if (diffDays <= 7) {\n return `${diffDays} days`;\n } else {\n return date.toLocaleDateString('en-US', {\n month: 'short',\n day: 'numeric'\n });\n }\n };\n\n const getPopularityBadge = (score: number) => {\n if (score >= 100) return { text: 'Super Hot', color: 'var(--error-color)' };\n if (score >= 50) return { text: 'Hot', color: 'var(--warning-color)' };\n if (score >= 25) return { text: 'Trending', color: 'var(--premium-gold)' };\n return { text: 'Popular', color: 'var(--glass-text-accent)' };\n };\n\n if (isLoading) {\n return (\n <div className={`rounded-lg shadow-md p-6 ${className}`} style={{background: 'var(--ui-bg-elevated)'}}>\n <div className=\"flex items-center justify-center h-32\">\n <div className=\"animate-spin rounded-full h-8 w-8 border-b-2\" style={{borderColor: 'var(--glass-text-accent)'}}></div>\n <span className=\"ml-3\" style={{color: 'var(--ui-text-secondary)'}}>Loading hot events...</span>\n </div>\n </div>\n );\n }\n\n if (error) {\n return (\n <div className={`rounded-lg shadow-md p-6 ${className}`} style={{background: 'var(--ui-bg-elevated)'}}>\n <div className=\"text-center\">\n <svg className=\"mx-auto h-12 w-12\" style={{color: 'var(--ui-text-muted)'}} fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-1.964-.833-2.732 0L3.732 16.5c-.77.833.192 2.5 1.732 2.5z\" />\n </svg>\n <h3 className=\"mt-2 text-sm font-medium\" style={{color: 'var(--ui-text-primary)'}}>Unable to load events</h3>\n <p className=\"mt-1 text-sm\" style={{color: 'var(--ui-text-secondary)'}}>{error}</p>\n <button\n onClick={loadTrendingEvents}\n className=\"mt-2 text-sm font-medium transition-colors\"\n style={{color: 'var(--glass-text-accent)'}}\n onMouseEnter={(e) => e.target.style.color = 'var(--premium-primary)'}\n onMouseLeave={(e) => e.target.style.color = 'var(--glass-text-accent)'}\n >\n Try again\n </button>\n </div>\n </div>\n );\n }\n\n if (trendingEvents.length === 0) {\n return (\n <div className={`rounded-lg shadow-md p-6 ${className}`} style={{background: 'var(--ui-bg-elevated)'}}>\n <div className=\"text-center\">\n <svg className=\"mx-auto h-12 w-12\" style={{color: 'var(--ui-text-muted)'}} fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M8 7V3a4 4 0 118 0v4m-4 12v-6m-4 0h8m-8 0v6a4 4 0 108 0v-6\" />\n </svg>\n <h3 className=\"mt-2 text-sm font-medium\" style={{color: 'var(--ui-text-primary)'}}>No trending events found</h3>\n <p className=\"mt-1 text-sm\" style={{color: 'var(--ui-text-secondary)'}}>\n Try expanding your search radius or check back later\n </p>\n </div>\n </div>\n );\n }\n\n return (\n <div className={`rounded-lg shadow-md overflow-hidden ${className}`} style={{background: 'var(--ui-bg-elevated)'}}>\n {/* Header */}\n <div className=\"px-6 py-4\" style={{background: 'linear-gradient(to right, var(--warning-color), var(--error-color))'}}>\n <div className=\"flex items-center justify-between\">\n <div className=\"flex items-center space-x-2\">\n <div className=\"text-2xl\">🔥</div>\n <h2 className=\"text-xl font-bold\" style={{color: 'var(--glass-text-primary)'}}>What's Hot</h2>\n </div>\n {userLocation && (\n <span className=\"text-sm\" style={{color: 'var(--glass-text-secondary)'}}>\n Within {radius} miles\n </span>\n )}\n </div>\n </div>\n\n {/* Events Grid */}\n <div className=\"p-6\">\n <div className=\"grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4\">\n {trendingEvents.map((event, index) => {\n const popularityBadge = getPopularityBadge(event.popularityScore);\n return (\n <div\n key={event.eventId}\n onClick={() => handleEventClick(event)}\n className=\"group cursor-pointer rounded-lg p-4 transition-colors duration-200 border relative overflow-hidden backdrop-blur-lg\"\n style={{\n background: 'var(--ui-bg-secondary)',\n borderColor: 'var(--ui-border-primary)'\n }}\n onMouseEnter={(e) => {\n e.target.style.background = 'var(--ui-bg-elevated)';\n e.target.style.borderColor = 'var(--ui-border-secondary)';\n }}\n onMouseLeave={(e) => {\n e.target.style.background = 'var(--ui-bg-secondary)';\n e.target.style.borderColor = 'var(--ui-border-primary)';\n }}\n >\n {/* Popularity Badge */}\n <div className={`absolute top-2 right-2 px-2 py-1 rounded-full text-xs font-medium`} style={{color: 'var(--glass-text-primary)', backgroundColor: popularityBadge.color}}>\n {popularityBadge.text}\n </div>\n\n {/* Event Image */}\n {event.imageUrl && (\n <div className=\"w-full h-32 rounded-lg mb-3 overflow-hidden\" style={{background: 'var(--ui-bg-muted)'}}>\n <img\n src={event.imageUrl}\n alt={event.title}\n className=\"w-full h-full object-cover group-hover:scale-105 transition-transform duration-200\"\n />\n </div>\n )}\n\n {/* Event Content */}\n <div className=\"space-y-2\">\n <div className=\"flex items-start justify-between\">\n <h3 className=\"text-sm font-semibold line-clamp-2 pr-8\" style={{color: 'var(--ui-text-primary)'}}>\n {event.title}\n </h3>\n </div>\n\n <div className=\"text-xs space-y-1\" style={{color: 'var(--ui-text-secondary)'}}>\n <div className=\"flex items-center\">\n <svg className=\"h-3 w-3 mr-1\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M17.657 16.657L13.414 20.9a1.998 1.998 0 01-2.827 0l-4.244-4.243a8 8 0 1111.314 0z\" />\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M15 11a3 3 0 11-6 0 3 3 0 016 0z\" />\n </svg>\n <span className=\"truncate\">{event.venue}</span>\n </div>\n \n {event.distanceMiles && (\n <div className=\"flex items-center\">\n <svg className=\"h-3 w-3 mr-1\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z\" />\n </svg>\n <span>{event.distanceMiles.toFixed(1)} miles away</span>\n </div>\n )}\n \n <div className=\"flex items-center\">\n <svg className=\"h-3 w-3 mr-1\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z\" />\n </svg>\n <span>{formatEventTime(event.startTime)}</span>\n </div>\n </div>\n\n {/* Event Stats */}\n <div className=\"flex items-center justify-between text-xs pt-2 border-t\" style={{color: 'var(--ui-text-tertiary)', borderColor: 'var(--ui-border-secondary)'}}>\n <div className=\"flex items-center space-x-3\">\n <div className=\"flex items-center\">\n <svg className=\"h-3 w-3 mr-1\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M15 12a3 3 0 11-6 0 3 3 0 016 0z\" />\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z\" />\n </svg>\n <span>{event.viewCount || 0}</span>\n </div>\n <div className=\"flex items-center\">\n <svg className=\"h-3 w-3 mr-1\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M16 11V7a4 4 0 00-8 0v4M5 9h14l1 12H4L5 9z\" />\n </svg>\n <span>{event.ticketsSold}</span>\n </div>\n </div>\n \n {event.isFeature && (\n <div className=\"flex items-center\">\n <svg className=\"h-3 w-3 mr-1\" style={{color: 'var(--premium-gold)'}} fill=\"currentColor\" viewBox=\"0 0 20 20\">\n <path d=\"M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z\" />\n </svg>\n <span className=\"font-medium\" style={{color: 'var(--premium-gold)'}}>Featured</span>\n </div>\n )}\n </div>\n </div>\n </div>\n );\n })}\n </div>\n\n {/* View More Button */}\n <div className=\"mt-6 text-center\">\n <button\n onClick={() => window.location.href = '/calendar'}\n className=\"inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md transition-colors duration-200\"\n style={{\n color: 'var(--glass-text-primary)',\n background: 'linear-gradient(to right, var(--warning-color), var(--error-color))'\n }}\n onMouseEnter={(e) => e.target.style.background = 'linear-gradient(to right, var(--warning-color-dark), var(--error-color-dark))'}\n onMouseLeave={(e) => e.target.style.background = 'linear-gradient(to right, var(--warning-color), var(--error-color))'}\n >\n View All Events\n <svg className=\"ml-2 h-4 w-4\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M9 5l7 7-7 7\" />\n </svg>\n </button>\n </div>\n </div>\n </div>\n );\n};\n\nexport default WhatsHotEvents;","usedDeprecatedRules":[]},{"filePath":"/home/tyler/apps/bct-whitelabel/src/components/__tests__/modular-components.test.ts","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/home/tyler/apps/bct-whitelabel/src/components/admin/AnalyticsTab.tsx","messages":[{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":5,"column":20,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":5,"endColumn":23,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[160,163],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[160,163],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":6,"column":9,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":6,"endColumn":12,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[173,176],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[173,176],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'platformMetrics' is defined but never used. Allowed unused args must match /^_/u.","line":9,"column":40,"nodeType":null,"messageId":"unusedVar","endLine":9,"endColumn":55},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'user' is defined but never used. Allowed unused args must match /^_/u.","line":9,"column":57,"nodeType":null,"messageId":"unusedVar","endLine":9,"endColumn":61},{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":11,"column":60,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":11,"endColumn":63,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[374,377],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[374,377],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":12,"column":54,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":12,"endColumn":57,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[439,442],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[439,442],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":13,"column":56,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":13,"endColumn":59,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[506,509],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[506,509],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":27,"column":14,"nodeType":null,"messageId":"unusedVar","endLine":27,"endColumn":19},{"ruleId":"no-empty","severity":1,"message":"Empty block statement.","line":27,"column":21,"nodeType":"BlockStatement","messageId":"unexpected","endLine":29,"endColumn":6,"suggestions":[{"messageId":"suggestComment","data":{"type":"block"},"fix":{"range":[792,798],"text":" /* empty */ "},"desc":"Add comment inside empty block statement."}]},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":40,"column":14,"nodeType":null,"messageId":"unusedVar","endLine":40,"endColumn":19},{"ruleId":"no-empty","severity":1,"message":"Empty block statement.","line":40,"column":21,"nodeType":"BlockStatement","messageId":"unexpected","endLine":42,"endColumn":6,"suggestions":[{"messageId":"suggestComment","data":{"type":"block"},"fix":{"range":[1100,1106],"text":" /* empty */ "},"desc":"Add comment inside empty block statement."}]},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":51,"column":14,"nodeType":null,"messageId":"unusedVar","endLine":51,"endColumn":19},{"ruleId":"no-empty","severity":1,"message":"Empty block statement.","line":51,"column":21,"nodeType":"BlockStatement","messageId":"unexpected","endLine":53,"endColumn":6,"suggestions":[{"messageId":"suggestComment","data":{"type":"block"},"fix":{"range":[1399,1405],"text":" /* empty */ "},"desc":"Add comment inside empty block statement."}]},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":62,"column":14,"nodeType":null,"messageId":"unusedVar","endLine":62,"endColumn":19},{"ruleId":"no-empty","severity":1,"message":"Empty block statement.","line":62,"column":21,"nodeType":"BlockStatement","messageId":"unexpected","endLine":64,"endColumn":6,"suggestions":[{"messageId":"suggestComment","data":{"type":"block"},"fix":{"range":[1686,1692],"text":" /* empty */ "},"desc":"Add comment inside empty block statement."}]},{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":67,"column":37,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":67,"endColumn":40,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[1736,1739],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[1736,1739],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":16,"fixableErrorCount":0,"fixableWarningCount":0,"source":"import { useState, useEffect } from 'react';\nimport { makeAuthenticatedRequest } from '../../lib/api-client';\n\ninterface AnalyticsTabProps {\n platformMetrics: any;\n user: any;\n}\n\nexport default function AnalyticsTab({ platformMetrics, user }: AnalyticsTabProps) {\n const [loading, setLoading] = useState(false);\n const [revenueChartData, setRevenueChartData] = useState<any>(null);\n const [topOrganizers, setTopOrganizers] = useState<any[]>([]);\n const [recentActivity, setRecentActivity] = useState<any[]>([]);\n\n useEffect(() => {\n loadAnalyticsData();\n }, []);\n\n const loadAnalyticsData = async () => {\n setLoading(true);\n try {\n await Promise.all([\n loadRevenueChart(),\n loadTopOrganizers(),\n loadRecentActivity()\n ]);\n } catch (error) {\n\n } finally {\n setLoading(false);\n }\n };\n\n const loadRevenueChart = async () => {\n try {\n const result = await makeAuthenticatedRequest('/api/admin/super-analytics?metric=sales_trends');\n if (result.success) {\n setRevenueChartData(result.data);\n }\n } catch (error) {\n\n }\n };\n\n const loadTopOrganizers = async () => {\n try {\n const result = await makeAuthenticatedRequest('/api/admin/super-analytics?metric=organizer_performance&limit=5');\n if (result.success) {\n setTopOrganizers(result.data.organizers || []);\n }\n } catch (error) {\n\n }\n };\n\n const loadRecentActivity = async () => {\n try {\n const result = await makeAuthenticatedRequest('/api/admin/super-analytics?metric=recent_activity');\n if (result.success) {\n setRecentActivity(result.data.activities || []);\n }\n } catch (error) {\n\n }\n };\n\n const formatActivity = (activity: any) => {\n let icon = '📊';\n let title = '';\n let subtitle = '';\n\n switch (activity.type) {\n case 'ticket_sale':\n icon = '🎫';\n title = `New ticket sale: $${activity.amount}`;\n subtitle = `${activity.event_name} by ${activity.organization_name}`;\n break;\n case 'event_created':\n icon = '📅';\n title = `New event: ${activity.name}`;\n subtitle = `Created by ${activity.organization_name}`;\n break;\n case 'organizer_joined':\n icon = '🏢';\n title = `New organizer: ${activity.name}`;\n subtitle = `Organization registered`;\n break;\n default:\n title = activity.description || 'Platform activity';\n subtitle = activity.details || '';\n }\n\n return { icon, title, subtitle };\n };\n\n if (loading) {\n return (\n <div className=\"p-6\">\n <div className=\"flex items-center justify-center py-12\">\n <div className=\"animate-spin rounded-full h-8 w-8 border-2 border-white border-t-transparent\"></div>\n </div>\n </div>\n );\n }\n\n return (\n <div className=\"p-6 space-y-6\">\n <div className=\"flex justify-between items-center\">\n <h3 className=\"text-2xl font-light text-white\">Platform Analytics</h3>\n <button\n onClick={loadAnalyticsData}\n className=\"flex items-center gap-2 px-4 py-2 bg-white/10 hover:bg-white/20 border border-white/20 rounded-lg text-white text-sm transition-colors\"\n >\n <svg className=\"w-4 h-4\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15\" />\n </svg>\n Refresh Data\n </button>\n </div>\n\n <div className=\"grid grid-cols-1 lg:grid-cols-2 gap-4 md:gap-6\">\n {/* Revenue Trends Chart */}\n <div className=\"bg-white/10 backdrop-blur-xl border border-white/20 rounded-xl p-4 md:p-6 shadow-lg\">\n <h4 className=\"text-base md:text-lg font-medium text-white mb-3 md:mb-4\">Revenue Trends</h4>\n {revenueChartData ? (\n <div className=\"space-y-4\">\n <div className=\"flex justify-between items-center p-3 bg-white/5 rounded-lg\">\n <span className=\"text-white\">Total Revenue</span>\n <span className=\"text-white font-bold\">\n ${revenueChartData.summary?.totalRevenue?.toLocaleString() || '0'}\n </span>\n </div>\n <div className=\"flex justify-between items-center p-3 bg-white/5 rounded-lg\">\n <span className=\"text-white\">Average Monthly</span>\n <span className=\"text-white font-bold\">\n ${revenueChartData.summary?.averageMonthlyRevenue?.toFixed(2) || '0.00'}\n </span>\n </div>\n <div className=\"flex justify-between items-center p-3 bg-white/5 rounded-lg\">\n <span className=\"text-white\">Growth Rate</span>\n <span className={`font-bold ${\n (revenueChartData.summary?.growth || 0) > 0 ? 'text-green-400' : 'text-red-400'\n }`}>\n {(revenueChartData.summary?.growth || 0) > 0 ? '+' : ''}\n {revenueChartData.summary?.growth?.toFixed(1) || '0.0'}%\n </span>\n </div>\n </div>\n ) : (\n <div className=\"text-white/60 text-center py-8\">No revenue data available</div>\n )}\n </div>\n\n {/* Top Organizers */}\n <div className=\"bg-white/10 backdrop-blur-xl border border-white/20 rounded-xl p-4 md:p-6 shadow-lg\">\n <h4 className=\"text-base md:text-lg font-medium text-white mb-3 md:mb-4\">Top Organizers</h4>\n {topOrganizers.length > 0 ? (\n <div className=\"space-y-3\">\n {topOrganizers.slice(0, 5).map((organizer, index) => (\n <div key={organizer.id} className=\"flex items-center justify-between p-3 bg-white/5 rounded-lg\">\n <div className=\"flex items-center gap-3\">\n <div className=\"w-8 h-8 bg-gradient-to-br from-blue-500/20 to-purple-500/20 rounded-lg flex items-center justify-center\">\n <span className=\"text-white font-bold text-sm\">#{index + 1}</span>\n </div>\n <div>\n <div className=\"text-white font-medium\">{organizer.name}</div>\n <div className=\"text-white/60 text-sm\">{organizer.eventCount} events</div>\n </div>\n </div>\n <div className=\"text-right\">\n <div className=\"text-white font-bold\">${organizer.totalRevenue?.toLocaleString() || '0'}</div>\n <div className=\"text-white/60 text-sm\">{organizer.ticketsSold || 0} tickets</div>\n </div>\n </div>\n ))}\n </div>\n ) : (\n <div className=\"text-white/60 text-center py-8\">No organizer data available</div>\n )}\n </div>\n\n {/* Recent Activity */}\n <div className=\"bg-white/10 backdrop-blur-xl border border-white/20 rounded-xl p-6 shadow-lg lg:col-span-2\">\n <h4 className=\"text-base md:text-lg font-medium text-white mb-3 md:mb-4\">Recent Activity</h4>\n {recentActivity.length > 0 ? (\n <div className=\"space-y-3 max-h-64 overflow-y-auto\">\n {recentActivity.slice(0, 10).map((activity, index) => {\n const { icon, title, subtitle } = formatActivity(activity);\n return (\n <div key={index} className=\"flex items-start space-x-3 p-3 bg-white/5 rounded-lg\">\n <div className=\"text-lg\">{icon}</div>\n <div className=\"flex-1\">\n <p className=\"text-white font-medium text-sm\">{title}</p>\n <p className=\"text-white/60 text-xs\">{subtitle}</p>\n <p className=\"text-white/40 text-xs mt-1\">\n {new Date(activity.date || activity.created_at).toLocaleDateString()}\n </p>\n </div>\n </div>\n );\n })}\n </div>\n ) : (\n <div className=\"text-white/60 text-center py-8\">No recent activity</div>\n )}\n </div>\n </div>\n </div>\n );\n}","usedDeprecatedRules":[]},{"filePath":"/home/tyler/apps/bct-whitelabel/src/components/admin/EventsTab.tsx","messages":[{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":5,"column":20,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":5,"endColumn":23,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[157,160],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[157,160],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":6,"column":9,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":6,"endColumn":12,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[170,173],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[170,173],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'platformMetrics' is defined but never used. Allowed unused args must match /^_/u.","line":9,"column":37,"nodeType":null,"messageId":"unusedVar","endLine":9,"endColumn":52},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'user' is defined but never used. Allowed unused args must match /^_/u.","line":9,"column":54,"nodeType":null,"messageId":"unusedVar","endLine":9,"endColumn":58},{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":12,"column":40,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":12,"endColumn":43,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[409,412],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[409,412],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":13,"column":42,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":13,"endColumn":45,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[462,465],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[462,465],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":14,"column":50,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":14,"endColumn":53,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[523,526],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[523,526],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'filters' is assigned a value but never used. Allowed unused vars must match /^_/u.","line":15,"column":10,"nodeType":null,"messageId":"unusedVar","endLine":15,"endColumn":17},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'setFilters' is assigned a value but never used. Allowed unused vars must match /^_/u.","line":15,"column":19,"nodeType":null,"messageId":"unusedVar","endLine":15,"endColumn":29},{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":15,"column":42,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":15,"endColumn":45,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[576,579],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[576,579],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":32,"column":14,"nodeType":null,"messageId":"unusedVar","endLine":32,"endColumn":19},{"ruleId":"no-empty","severity":1,"message":"Empty block statement.","line":32,"column":21,"nodeType":"BlockStatement","messageId":"unexpected","endLine":34,"endColumn":6,"suggestions":[{"messageId":"suggestComment","data":{"type":"block"},"fix":{"range":[1004,1010],"text":" /* empty */ "},"desc":"Add comment inside empty block statement."}]},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":47,"column":14,"nodeType":null,"messageId":"unusedVar","endLine":47,"endColumn":19},{"ruleId":"no-empty","severity":1,"message":"Empty block statement.","line":47,"column":21,"nodeType":"BlockStatement","messageId":"unexpected","endLine":49,"endColumn":6,"suggestions":[{"messageId":"suggestComment","data":{"type":"block"},"fix":{"range":[1396,1402],"text":" /* empty */ "},"desc":"Add comment inside empty block statement."}]},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'deleteEvent' is assigned a value but never used. Allowed unused vars must match /^_/u.","line":62,"column":9,"nodeType":null,"messageId":"unusedVar","endLine":62,"endColumn":20},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'eventId' is defined but never used. Allowed unused args must match /^_/u.","line":62,"column":24,"nodeType":null,"messageId":"unusedVar","endLine":62,"endColumn":31},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'isPast' is assigned a value but never used. Allowed unused vars must match /^_/u.","line":160,"column":27,"nodeType":null,"messageId":"unusedVar","endLine":160,"endColumn":33}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":17,"fixableErrorCount":0,"fixableWarningCount":0,"source":"import { useState, useEffect } from 'react';\nimport { makeAuthenticatedRequest } from '../../lib/api-client';\n\ninterface EventsTabProps {\n platformMetrics: any;\n user: any;\n}\n\nexport default function EventsTab({ platformMetrics, user }: EventsTabProps) {\n const [activeSection, setActiveSection] = useState('events');\n const [loading, setLoading] = useState(false);\n const [events, setEvents] = useState<any[]>([]);\n const [tickets, setTickets] = useState<any[]>([]);\n const [ticketStats, setTicketStats] = useState<any>(null);\n const [filters, setFilters] = useState<any>({});\n\n useEffect(() => {\n if (activeSection === 'events') {\n loadEvents();\n } else {\n loadTickets();\n }\n }, [activeSection]);\n\n const loadEvents = async () => {\n setLoading(true);\n try {\n const result = await makeAuthenticatedRequest('/api/admin/super-analytics?metric=event_analytics');\n if (result.success) {\n setEvents(result.data.events || []);\n }\n } catch (error) {\n\n } finally {\n setLoading(false);\n }\n };\n\n const loadTickets = async () => {\n setLoading(true);\n try {\n const result = await makeAuthenticatedRequest('/api/admin/super-analytics?metric=ticket_analytics&limit=50');\n if (result.success) {\n setTickets(result.data.tickets || []);\n setTicketStats(result.data.stats || {});\n }\n } catch (error) {\n\n } finally {\n setLoading(false);\n }\n };\n\n const editEvent = (eventId: string) => {\n window.open(`/events/${eventId}/manage`, '_blank');\n };\n\n const viewOrganizerDashboard = (organizationId: string) => {\n window.open(`/admin/organizer-view/${organizationId}`, '_blank');\n };\n\n const deleteEvent = (eventId: string) => {\n if (confirm('Are you sure you want to delete this event? This action cannot be undone.')) {\n\n // Implement event deletion API call\n }\n };\n\n const getStatusColor = (status: string) => {\n switch (status) {\n case 'active': return 'status-pill status-success';\n case 'used': return 'status-pill status-info';\n case 'refunded': return 'status-pill status-error';\n case 'cancelled': return 'status-pill status-neutral';\n default: return 'status-pill status-neutral';\n }\n };\n\n if (loading) {\n return (\n <div className=\"p-4 md:p-6\">\n <div className=\"flex items-center justify-center py-12\">\n <div className=\"animate-spin rounded-full h-8 w-8 border-2 border-white border-t-transparent\"></div>\n </div>\n </div>\n );\n }\n\n return (\n <div className=\"p-4 md:p-6 space-y-6\">\n <div className=\"flex flex-col sm:flex-row sm:justify-between sm:items-center gap-4\">\n <h3 className=\"text-xl md:text-2xl font-light text-white\">Events & Tickets Management</h3>\n \n {/* Section Toggle */}\n <div className=\"flex items-center bg-white/10 rounded-lg p-1\">\n <button\n onClick={() => setActiveSection('events')}\n className={`px-3 py-1 rounded-lg text-sm transition-all ${\n activeSection === 'events' \n ? 'bg-white/20 text-white' \n : 'text-white/60 hover:text-white'\n }`}\n >\n Events\n </button>\n <button\n onClick={() => setActiveSection('tickets')}\n className={`px-3 py-1 rounded-lg text-sm transition-all ${\n activeSection === 'tickets' \n ? 'bg-white/20 text-white' \n : 'text-white/60 hover:text-white'\n }`}\n >\n Tickets\n </button>\n </div>\n </div>\n\n {activeSection === 'events' ? (\n <div className=\"space-y-6\">\n {/* Events Actions */}\n <div className=\"flex flex-col sm:flex-row gap-3\">\n <button\n onClick={() => window.open('/events/new', '_blank')}\n className=\"flex items-center gap-2 px-4 py-2 bg-gradient-to-r from-blue-600 to-cyan-600 hover:from-blue-700 hover:to-cyan-700 text-white rounded-lg text-sm font-medium transition-all\"\n >\n <svg className=\"w-4 h-4\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M12 6v6m0 0v6m0-6h6m-6 0H6\" />\n </svg>\n Create Event\n </button>\n <button\n onClick={loadEvents}\n className=\"flex items-center gap-2 px-4 py-2 bg-white/10 hover:bg-white/20 border border-white/20 rounded-lg text-white text-sm transition-colors\"\n >\n <svg className=\"w-4 h-4\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15\" />\n </svg>\n Refresh\n </button>\n </div>\n\n {/* Events Table */}\n <div className=\"bg-white/10 backdrop-blur-xl border border-white/20 rounded-xl overflow-hidden\">\n <div className=\"overflow-x-auto\">\n <table className=\"w-full min-w-[700px]\">\n <thead className=\"bg-white/10 backdrop-blur-xl\">\n <tr>\n <th className=\"text-left p-3 md:p-4 text-white font-medium text-sm\">Event</th>\n <th className=\"text-left p-3 md:p-4 text-white font-medium text-sm hidden md:table-cell\">Organizer</th>\n <th className=\"text-left p-3 md:p-4 text-white font-medium text-sm\">Date</th>\n <th className=\"text-left p-3 md:p-4 text-white font-medium text-sm\">Revenue</th>\n <th className=\"text-left p-3 md:p-4 text-white font-medium text-sm\">Tickets</th>\n <th className=\"text-left p-3 md:p-4 text-white font-medium text-sm\">Actions</th>\n </tr>\n </thead>\n <tbody className=\"divide-y divide-white/10\">\n {events.map(event => {\n const eventDate = new Date(event.startTime);\n const isPast = eventDate < new Date();\n \n return (\n <tr key={event.id} className=\"hover:bg-white/10\">\n <td className=\"p-3 md:p-4\">\n <div className=\"text-white font-medium text-sm\">{event.title}</div>\n <div className=\"text-white/60 text-xs\">{event.sellThroughRate?.toFixed(1) || 0}% sold</div>\n <div className=\"md:hidden text-white/60 text-xs mt-1\">{event.organizerName || 'Unknown'}</div>\n </td>\n <td className=\"p-3 md:p-4 text-white/80 text-sm hidden md:table-cell\">{event.organizerName || 'Unknown'}</td>\n <td className=\"p-3 md:p-4 text-white/80 text-sm\">\n <div>{eventDate.toLocaleDateString()}</div>\n <div className=\"text-xs text-white/60\">{eventDate.toLocaleTimeString()}</div>\n </td>\n <td className=\"p-3 md:p-4 text-white/80 text-sm\">${(event.totalRevenue || 0).toLocaleString()}</td>\n <td className=\"p-3 md:p-4 text-white/80 text-sm\">{event.ticketsSold || 0}</td>\n <td className=\"p-3 md:p-4\">\n <div className=\"flex flex-col sm:flex-row gap-1\">\n <button\n onClick={() => editEvent(event.id)}\n className=\"bg-gradient-to-r from-blue-600 to-cyan-600 hover:from-blue-700 hover:to-cyan-700 text-white px-2 py-1 rounded text-xs\"\n >\n Edit\n </button>\n <button\n onClick={() => viewOrganizerDashboard(event.organizationId)}\n className=\"bg-gradient-to-r from-purple-600 to-pink-600 hover:from-purple-700 hover:to-pink-700 text-white px-2 py-1 rounded text-xs\"\n >\n Org\n </button>\n </div>\n </td>\n </tr>\n );\n })}\n {events.length === 0 && (\n <tr>\n <td colSpan={6} className=\"p-4 text-white/60 text-center\">No events found</td>\n </tr>\n )}\n </tbody>\n </table>\n </div>\n </div>\n </div>\n ) : (\n <div className=\"space-y-6\">\n {/* Ticket Stats */}\n {ticketStats && (\n <div className=\"grid grid-cols-2 lg:grid-cols-4 gap-3 md:gap-4\">\n <div className=\"bg-white/10 backdrop-blur-xl border border-white/20 rounded-xl p-3 md:p-4\">\n <div className=\"text-xs md:text-sm text-white/60\">Total</div>\n <div className=\"text-lg md:text-2xl font-bold text-white\">{ticketStats.total || 0}</div>\n </div>\n <div className=\"bg-white/10 backdrop-blur-xl border border-white/20 rounded-xl p-3 md:p-4\">\n <div className=\"text-xs md:text-sm text-white/60\">Active</div>\n <div className=\"text-lg md:text-2xl font-bold\" style={{ color: 'var(--success-color)' }}>{ticketStats.active || 0}</div>\n </div>\n <div className=\"bg-white/10 backdrop-blur-xl border border-white/20 rounded-xl p-3 md:p-4\">\n <div className=\"text-xs md:text-sm text-white/60\">Used</div>\n <div className=\"text-lg md:text-2xl font-bold\" style={{ color: 'var(--glass-text-accent)' }}>{ticketStats.used || 0}</div>\n </div>\n <div className=\"bg-white/10 backdrop-blur-xl border border-white/20 rounded-xl p-3 md:p-4\">\n <div className=\"text-xs md:text-sm text-white/60\">Refunded</div>\n <div className=\"text-lg md:text-2xl font-bold\" style={{ color: 'var(--error-color)' }}>{ticketStats.refunded || 0}</div>\n </div>\n </div>\n )}\n\n {/* Tickets Actions */}\n <div className=\"flex flex-col sm:flex-row gap-3\">\n <button\n onClick={loadTickets}\n className=\"flex items-center gap-2 px-4 py-2 bg-white/10 hover:bg-white/20 border border-white/20 rounded-lg text-white text-sm transition-colors\"\n >\n <svg className=\"w-4 h-4\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15\" />\n </svg>\n Refresh Tickets\n </button>\n </div>\n\n {/* Tickets Table */}\n <div className=\"bg-white/10 backdrop-blur-xl border border-white/20 rounded-xl overflow-hidden\">\n <div className=\"overflow-x-auto\">\n <table className=\"w-full min-w-[600px]\">\n <thead className=\"bg-white/5\">\n <tr>\n <th className=\"text-left p-3 text-white font-medium text-sm\">Customer</th>\n <th className=\"text-left p-3 text-white font-medium text-sm hidden md:table-cell\">Event</th>\n <th className=\"text-left p-3 text-white font-medium text-sm\">Price</th>\n <th className=\"text-left p-3 text-white font-medium text-sm\">Status</th>\n <th className=\"text-left p-3 text-white font-medium text-sm\">Date</th>\n </tr>\n </thead>\n <tbody className=\"divide-y divide-white/10\">\n {tickets.map((ticket, index) => (\n <tr key={ticket.id || index} className=\"hover:bg-white/10\">\n <td className=\"p-3\">\n <div className=\"text-white font-medium text-sm\">{ticket.customerName || 'N/A'}</div>\n <div className=\"text-white/60 text-xs\">{ticket.customerEmail || 'N/A'}</div>\n <div className=\"md:hidden text-white/60 text-xs mt-1\">{ticket.event?.title || 'N/A'}</div>\n </td>\n <td className=\"p-3 hidden md:table-cell\">\n <div className=\"text-white font-medium text-sm\">{ticket.event?.title || 'N/A'}</div>\n <div className=\"text-white/60 text-xs\">{ticket.event?.organizationName || 'N/A'}</div>\n </td>\n <td className=\"p-3 text-white/80 text-sm\">${(ticket.price || 0).toFixed(2)}</td>\n <td className=\"p-3\">\n <span className={`px-2 py-1 rounded-full text-xs ${getStatusColor(ticket.status || 'unknown')}`}>\n {(ticket.status || 'unknown').charAt(0).toUpperCase() + (ticket.status || 'unknown').slice(1)}\n </span>\n </td>\n <td className=\"p-3 text-white/80 text-xs\">\n {ticket.createdAt ? new Date(ticket.createdAt).toLocaleDateString() : 'N/A'}\n </td>\n </tr>\n ))}\n {tickets.length === 0 && (\n <tr>\n <td colSpan={5} className=\"p-4 text-white/60 text-center\">No tickets found</td>\n </tr>\n )}\n </tbody>\n </table>\n </div>\n </div>\n </div>\n )}\n </div>\n );\n}","usedDeprecatedRules":[]},{"filePath":"/home/tyler/apps/bct-whitelabel/src/components/admin/ManagementTab.tsx","messages":[{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":5,"column":20,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":5,"endColumn":23,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[161,164],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[161,164],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":6,"column":9,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":6,"endColumn":12,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[174,177],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[174,177],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'platformMetrics' is defined but never used. Allowed unused args must match /^_/u.","line":9,"column":41,"nodeType":null,"messageId":"unusedVar","endLine":9,"endColumn":56},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'user' is defined but never used. Allowed unused args must match /^_/u.","line":9,"column":58,"nodeType":null,"messageId":"unusedVar","endLine":9,"endColumn":62},{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":12,"column":70,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":12,"endColumn":73,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[453,456],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[453,456],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":13,"column":56,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":13,"endColumn":59,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[518,521],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[518,521],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":28,"column":14,"nodeType":null,"messageId":"unusedVar","endLine":28,"endColumn":19},{"ruleId":"no-empty","severity":1,"message":"Empty block statement.","line":28,"column":21,"nodeType":"BlockStatement","messageId":"unexpected","endLine":30,"endColumn":6,"suggestions":[{"messageId":"suggestComment","data":{"type":"block"},"fix":{"range":[841,847],"text":" /* empty */ "},"desc":"Add comment inside empty block statement."}]},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":43,"column":14,"nodeType":null,"messageId":"unusedVar","endLine":43,"endColumn":19},{"ruleId":"no-empty","severity":1,"message":"Empty block statement.","line":43,"column":21,"nodeType":"BlockStatement","messageId":"unexpected","endLine":45,"endColumn":6,"suggestions":[{"messageId":"suggestComment","data":{"type":"block"},"fix":{"range":[1171,1177],"text":" /* empty */ "},"desc":"Add comment inside empty block statement."}]},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":54,"column":14,"nodeType":null,"messageId":"unusedVar","endLine":54,"endColumn":19},{"ruleId":"no-empty","severity":1,"message":"Empty block statement.","line":54,"column":21,"nodeType":"BlockStatement","messageId":"unexpected","endLine":56,"endColumn":6,"suggestions":[{"messageId":"suggestComment","data":{"type":"block"},"fix":{"range":[1401,1407],"text":" /* empty */ "},"desc":"Add comment inside empty block statement."}]},{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":73,"column":17,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":73,"endColumn":20,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[1859,1862],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[1859,1862],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":95,"column":72,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":95,"endColumn":75,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[2848,2851],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[2848,2851],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":112,"column":66,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":112,"endColumn":69,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[3571,3574],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[3571,3574],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"no-case-declarations","severity":2,"message":"Unexpected lexical declaration in case block.","line":128,"column":11,"nodeType":"VariableDeclaration","messageId":"unexpected","endLine":128,"endColumn":120,"suggestions":[{"messageId":"addBrackets","fix":{"range":[4139,4768],"text":"{ const ticketResponse = await makeAuthenticatedRequest('/api/admin/super-analytics?metric=platform_overview');\n if (ticketResponse.success) {\n const summary = ticketResponse.data.summary || {};\n data = [{\n total_tickets: summary.totalTickets || 0,\n total_revenue: summary.totalRevenue || 0,\n platform_fees: summary.totalPlatformFees || 0,\n export_note: 'Summary data only - detailed ticket data available on request'\n }];\n }\n filename = `tickets-summary-${new Date().toISOString().split('T')[0]}.csv`;\n break; }"},"desc":"Add {} brackets around the case block."}]},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":146,"column":14,"nodeType":null,"messageId":"unusedVar","endLine":146,"endColumn":19},{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":154,"column":31,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":154,"endColumn":34,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[5045,5048],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[5045,5048],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"no-undef","severity":2,"message":"'URL' is not defined.","line":169,"column":17,"nodeType":"Identifier","messageId":"undef","endLine":169,"endColumn":20},{"ruleId":"no-undef","severity":2,"message":"'URL' is not defined.","line":176,"column":5,"nodeType":"Identifier","messageId":"undef","endLine":176,"endColumn":8}],"suppressedMessages":[],"errorCount":3,"fatalErrorCount":0,"warningCount":17,"fixableErrorCount":0,"fixableWarningCount":0,"source":"import { useState, useEffect } from 'react';\nimport { makeAuthenticatedRequest } from '../../lib/api-client';\n\ninterface ManagementTabProps {\n platformMetrics: any;\n user: any;\n}\n\nexport default function ManagementTab({ platformMetrics, user }: ManagementTabProps) {\n const [activeSection, setActiveSection] = useState('features');\n const [loading, setLoading] = useState(false);\n const [territoryManagerStats, setTerritoryManagerStats] = useState<any>({});\n const [featureToggles, setFeatureToggles] = useState<any>({});\n\n useEffect(() => {\n if (activeSection === 'features') {\n loadFeaturesData();\n }\n }, [activeSection]);\n\n const loadFeaturesData = async () => {\n setLoading(true);\n try {\n await Promise.all([\n loadTerritoryManagerStats(),\n loadFeatureToggles()\n ]);\n } catch (error) {\n\n } finally {\n setLoading(false);\n }\n };\n\n const loadTerritoryManagerStats = async () => {\n try {\n // Mock data for now - replace with actual API call\n setTerritoryManagerStats({\n activeManagers: 3,\n pendingApplications: 2,\n totalCommissions: 5910.00\n });\n } catch (error) {\n\n }\n };\n\n const loadFeatureToggles = async () => {\n try {\n // For now, Territory Manager is always enabled (development)\n setFeatureToggles({\n territoryManagerEnabled: true\n });\n } catch (error) {\n\n }\n };\n\n const handleTerritoryManagerToggle = (enabled: boolean) => {\n // Show development warning\n alert('⚠️ Development Feature: The Territory Manager system is currently in development. Toggle changes will not persist between sessions.');\n \n setFeatureToggles(prev => ({\n ...prev,\n territoryManagerEnabled: enabled\n }));\n\n };\n\n const exportData = async (type: string) => {\n setLoading(true);\n try {\n let data: any[] = [];\n let filename = '';\n\n switch (type) {\n case 'revenue': {\n const revenueResponse = await makeAuthenticatedRequest('/api/admin/super-analytics?metric=revenue_breakdown');\n if (revenueResponse.success) {\n const totals = revenueResponse.data.totals || {};\n data = [{\n gross_revenue: totals.grossRevenue || 0,\n platform_fees: totals.platformFees || 0,\n net_revenue: (totals.grossRevenue || 0) - (totals.platformFees || 0),\n export_date: new Date().toISOString()\n }];\n }\n filename = `revenue-report-${new Date().toISOString().split('T')[0]}.csv`;\n break;\n }\n\n case 'organizers': {\n const organizerResponse = await makeAuthenticatedRequest('/api/admin/super-analytics?metric=organizer_performance');\n if (organizerResponse.success) {\n data = (organizerResponse.data.organizers || []).map((org: any) => ({\n name: org.name,\n events: org.eventCount || 0,\n tickets_sold: org.ticketsSold || 0,\n total_revenue: org.totalRevenue || 0,\n platform_fees: org.platformFees || 0,\n avg_ticket_price: org.avgTicketPrice || 0,\n join_date: org.joinDate || ''\n }));\n }\n filename = `organizers-${new Date().toISOString().split('T')[0]}.csv`;\n break;\n }\n\n case 'events': {\n const eventResponse = await makeAuthenticatedRequest('/api/admin/super-analytics?metric=event_analytics');\n if (eventResponse.success) {\n data = (eventResponse.data.events || []).map((event: any) => ({\n title: event.title,\n organization_id: event.organizationId,\n start_time: event.startTime,\n is_published: event.isPublished,\n category: event.category || '',\n tickets_sold: event.ticketsSold || 0,\n total_revenue: event.totalRevenue || 0,\n sell_through_rate: event.sellThroughRate || 0\n }));\n }\n filename = `events-${new Date().toISOString().split('T')[0]}.csv`;\n break;\n }\n\n case 'tickets':\n const ticketResponse = await makeAuthenticatedRequest('/api/admin/super-analytics?metric=platform_overview');\n if (ticketResponse.success) {\n const summary = ticketResponse.data.summary || {};\n data = [{\n total_tickets: summary.totalTickets || 0,\n total_revenue: summary.totalRevenue || 0,\n platform_fees: summary.totalPlatformFees || 0,\n export_note: 'Summary data only - detailed ticket data available on request'\n }];\n }\n filename = `tickets-summary-${new Date().toISOString().split('T')[0]}.csv`;\n break;\n }\n\n // Convert to CSV and download\n const csvContent = convertToCSV(data);\n downloadCSV(csvContent, filename);\n\n } catch (error) {\n\n alert('Error exporting data');\n } finally {\n setLoading(false);\n }\n };\n\n const convertToCSV = (data: any[]) => {\n if (!data.length) return '';\n \n const headers = Object.keys(data[0]).join(',');\n const rows = data.map(row => \n Object.values(row).map(value => \n typeof value === 'string' ? `\"${value}\"` : value\n ).join(',')\n );\n \n return [headers, ...rows].join('\\n');\n };\n\n const downloadCSV = (csvContent: string, filename: string) => {\n const blob = new Blob([csvContent], { type: 'text/csv' });\n const url = URL.createObjectURL(blob);\n const a = document.createElement('a');\n a.href = url;\n a.download = filename;\n document.body.appendChild(a);\n a.click();\n document.body.removeChild(a);\n URL.revokeObjectURL(url);\n };\n\n return (\n <div className=\"p-4 md:p-6 space-y-6\">\n <div className=\"flex flex-col sm:flex-row sm:justify-between sm:items-center gap-4\">\n <h3 className=\"text-xl md:text-2xl font-light text-white\">Platform Management</h3>\n \n {/* Section Toggle */}\n <div className=\"flex items-center bg-white/10 rounded-lg p-1\">\n <button\n onClick={() => setActiveSection('features')}\n className={`px-3 py-1 rounded-lg text-sm transition-all ${\n activeSection === 'features' \n ? 'bg-white/20 text-white' \n : 'text-white/60 hover:text-white'\n }`}\n >\n Features\n </button>\n <button\n onClick={() => setActiveSection('export')}\n className={`px-3 py-1 rounded-lg text-sm transition-all ${\n activeSection === 'export' \n ? 'bg-white/20 text-white' \n : 'text-white/60 hover:text-white'\n }`}\n >\n Data Export\n </button>\n </div>\n </div>\n\n {activeSection === 'features' ? (\n <div className=\"space-y-6\">\n {/* Territory Manager Feature */}\n <div className=\"bg-white/10 backdrop-blur-xl border border-white/20 rounded-xl p-4 md:p-6 shadow-lg\">\n <div className=\"flex flex-col lg:flex-row lg:items-center lg:justify-between gap-4 mb-6\">\n <div>\n <h4 className=\"text-lg font-medium text-white mb-2\">Territory Manager System</h4>\n <p className=\"text-white/70 text-sm\">\n Enable and manage the territory-based commission system for event organizers.\n </p>\n </div>\n <div className=\"flex items-center\">\n <input\n type=\"checkbox\"\n id=\"territory-manager-toggle\"\n checked={featureToggles.territoryManagerEnabled}\n onChange={(e) => handleTerritoryManagerToggle(e.target.checked)}\n className=\"sr-only\"\n />\n <label\n htmlFor=\"territory-manager-toggle\"\n className={`relative inline-block w-12 h-6 rounded-full cursor-pointer transition-colors ${\n featureToggles.territoryManagerEnabled ? 'status-pill status-success' : 'status-pill status-neutral'\n }`}\n >\n <span\n className={`absolute left-1 top-1 w-4 h-4 bg-white rounded-full transition-transform ${\n featureToggles.territoryManagerEnabled ? 'translate-x-6' : 'translate-x-0'\n }`}\n />\n </label>\n </div>\n </div>\n\n {/* Territory Manager Stats */}\n <div className=\"grid grid-cols-1 md:grid-cols-3 gap-4 mb-6\">\n <div className=\"bg-white/5 border border-white/20 rounded-lg p-4\">\n <div className=\"text-sm text-white/60\">Active Managers</div>\n <div className=\"text-2xl font-bold text-white\">{territoryManagerStats.activeManagers || 0}</div>\n </div>\n <div className=\"bg-white/5 border border-white/20 rounded-lg p-4\">\n <div className=\"text-sm text-white/60\">Pending Applications</div>\n <div className=\"text-2xl font-bold text-yellow-400\">{territoryManagerStats.pendingApplications || 0}</div>\n </div>\n <div className=\"bg-white/5 border border-white/20 rounded-lg p-4\">\n <div className=\"text-sm text-white/60\">Total Commissions</div>\n <div className=\"text-2xl font-bold style={{ color: 'var(--success-color)' }}\">\n ${(territoryManagerStats.totalCommissions || 0).toFixed(2)}\n </div>\n </div>\n </div>\n\n {/* Quick Actions */}\n <div className=\"flex flex-col sm:flex-row gap-3\">\n <button\n onClick={() => window.open('/admin/territory-manager/applications', '_blank')}\n className=\"flex items-center gap-2 px-4 py-2 bg-gradient-to-r from-blue-600 to-cyan-600 hover:from-blue-700 hover:to-cyan-700 text-white rounded-lg text-sm font-medium transition-all\"\n >\n <svg className=\"w-4 h-4\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z\" />\n </svg>\n View Applications\n </button>\n <button\n onClick={() => window.open('/territory-manager/dashboard', '_blank')}\n className=\"flex items-center gap-2 px-4 py-2 bg-white/10 hover:bg-white/20 border border-white/20 rounded-lg text-white text-sm transition-colors\"\n >\n <svg className=\"w-4 h-4\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={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 002 2h2a2 2 0 002-2V5a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2z\" />\n </svg>\n Territory Dashboard\n </button>\n </div>\n </div>\n </div>\n ) : (\n <div className=\"space-y-6\">\n {/* Export Options */}\n <div className=\"grid grid-cols-1 md:grid-cols-2 gap-4 md:gap-6\">\n {/* Revenue Export */}\n <div className=\"bg-white/10 backdrop-blur-xl border border-white/20 rounded-xl p-4 md:p-6 shadow-lg\">\n <div className=\"flex items-center gap-3 mb-4\">\n <div className=\"w-10 h-10 bg-gradient-to-br from-green-500/20 to-emerald-500/20 rounded-lg flex items-center justify-center\">\n <svg className=\"w-5 h-5 style={{ color: 'var(--success-color)' }}\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={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-1\" />\n </svg>\n </div>\n <div>\n <h4 className=\"text-lg font-medium text-white\">Revenue Data</h4>\n <p className=\"text-white/60 text-sm\">Export platform revenue breakdown</p>\n </div>\n </div>\n <button\n onClick={() => exportData('revenue')}\n disabled={loading}\n className=\"w-full flex items-center justify-center gap-2 px-4 py-2 bg-gradient-to-r from-green-600 to-emerald-600 hover:from-green-700 hover:to-emerald-700 text-white rounded-lg text-sm font-medium transition-all disabled:opacity-50\"\n >\n <svg className=\"w-4 h-4\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M12 10v6m0 0l-3-3m3 3l3-3m2 8H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z\" />\n </svg>\n {loading ? 'Exporting...' : 'Export Revenue'}\n </button>\n </div>\n\n {/* Organizers Export */}\n <div className=\"bg-white/10 backdrop-blur-xl border border-white/20 rounded-xl p-4 md:p-6 shadow-lg\">\n <div className=\"flex items-center gap-3 mb-4\">\n <div className=\"w-10 h-10 bg-gradient-to-br from-blue-500/20 to-cyan-500/20 rounded-lg flex items-center justify-center\">\n <svg className=\"w-5 h-5 style={{ color: 'var(--glass-text-accent)' }}\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z\" />\n </svg>\n </div>\n <div>\n <h4 className=\"text-lg font-medium text-white\">Organizer Data</h4>\n <p className=\"text-white/60 text-sm\">Export organizer performance metrics</p>\n </div>\n </div>\n <button\n onClick={() => exportData('organizers')}\n disabled={loading}\n className=\"w-full flex items-center justify-center gap-2 px-4 py-2 bg-gradient-to-r from-blue-600 to-cyan-600 hover:from-blue-700 hover:to-cyan-700 text-white rounded-lg text-sm font-medium transition-all disabled:opacity-50\"\n >\n <svg className=\"w-4 h-4\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M12 10v6m0 0l-3-3m3 3l3-3m2 8H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z\" />\n </svg>\n {loading ? 'Exporting...' : 'Export Organizers'}\n </button>\n </div>\n\n {/* Events Export */}\n <div className=\"bg-white/10 backdrop-blur-xl border border-white/20 rounded-xl p-4 md:p-6 shadow-lg\">\n <div className=\"flex items-center gap-3 mb-4\">\n <div className=\"w-10 h-10 bg-gradient-to-br from-purple-500/20 to-pink-500/20 rounded-lg flex items-center justify-center\">\n <svg className=\"w-5 h-5 text-purple-400\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M8 7V3a2 2 0 012-2h4a2 2 0 012 2v4M8 7V5a2 2 0 012-2h4a2 2 0 012 2v2m-8 0v12a2 2 0 002 2h8a2 2 0 002-2V7m-8 0h8\" />\n </svg>\n </div>\n <div>\n <h4 className=\"text-lg font-medium text-white\">Event Data</h4>\n <p className=\"text-white/60 text-sm\">Export all platform events</p>\n </div>\n </div>\n <button\n onClick={() => exportData('events')}\n disabled={loading}\n className=\"w-full flex items-center justify-center gap-2 px-4 py-2 bg-gradient-to-r from-purple-600 to-pink-600 hover:from-purple-700 hover:to-pink-700 text-white rounded-lg text-sm font-medium transition-all disabled:opacity-50\"\n >\n <svg className=\"w-4 h-4\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M12 10v6m0 0l-3-3m3 3l3-3m2 8H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z\" />\n </svg>\n {loading ? 'Exporting...' : 'Export Events'}\n </button>\n </div>\n\n {/* Tickets Export */}\n <div className=\"bg-white/10 backdrop-blur-xl border border-white/20 rounded-xl p-4 md:p-6 shadow-lg\">\n <div className=\"flex items-center gap-3 mb-4\">\n <div className=\"w-10 h-10 bg-gradient-to-br from-yellow-500/20 to-orange-500/20 rounded-lg flex items-center justify-center\">\n <svg className=\"w-5 h-5 text-yellow-400\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M15 5v2m0 4v2m0 4v2M5 5a2 2 0 00-2 2v3a2 2 0 110 4v3a2 2 0 002 2h14a2 2 0 002-2v-3a2 2 0 110-4V7a2 2 0 00-2-2H5z\" />\n </svg>\n </div>\n <div>\n <h4 className=\"text-lg font-medium text-white\">Ticket Data</h4>\n <p className=\"text-white/60 text-sm\">Export ticket summary data</p>\n </div>\n </div>\n <button\n onClick={() => exportData('tickets')}\n disabled={loading}\n className=\"w-full flex items-center justify-center gap-2 px-4 py-2 bg-gradient-to-r from-yellow-600 to-orange-600 hover:from-yellow-700 hover:to-orange-700 text-white rounded-lg text-sm font-medium transition-all disabled:opacity-50\"\n >\n <svg className=\"w-4 h-4\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M12 10v6m0 0l-3-3m3 3l3-3m2 8H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z\" />\n </svg>\n {loading ? 'Exporting...' : 'Export Tickets'}\n </button>\n </div>\n </div>\n </div>\n )}\n </div>\n );\n}","usedDeprecatedRules":[]},{"filePath":"/home/tyler/apps/bct-whitelabel/src/components/admin/OrganizersTab.tsx","messages":[{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":5,"column":20,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":5,"endColumn":23,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[161,164],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[161,164],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":6,"column":9,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":6,"endColumn":12,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[174,177],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[174,177],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'platformMetrics' is defined but never used. Allowed unused args must match /^_/u.","line":9,"column":41,"nodeType":null,"messageId":"unusedVar","endLine":9,"endColumn":56},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'user' is defined but never used. Allowed unused args must match /^_/u.","line":9,"column":58,"nodeType":null,"messageId":"unusedVar","endLine":9,"endColumn":62},{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":11,"column":48,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":11,"endColumn":51,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[365,368],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[365,368],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":26,"column":14,"nodeType":null,"messageId":"unusedVar","endLine":26,"endColumn":19},{"ruleId":"no-empty","severity":1,"message":"Empty block statement.","line":26,"column":21,"nodeType":"BlockStatement","messageId":"unexpected","endLine":28,"endColumn":6,"suggestions":[{"messageId":"suggestComment","data":{"type":"block"},"fix":{"range":[832,838],"text":" /* empty */ "},"desc":"Add comment inside empty block statement."}]}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":7,"fixableErrorCount":0,"fixableWarningCount":0,"source":"import { useState, useEffect } from 'react';\nimport { makeAuthenticatedRequest } from '../../lib/api-client';\n\ninterface OrganizersTabProps {\n platformMetrics: any;\n user: any;\n}\n\nexport default function OrganizersTab({ platformMetrics, user }: OrganizersTabProps) {\n const [loading, setLoading] = useState(false);\n const [organizers, setOrganizers] = useState<any[]>([]);\n const [searchTerm, setSearchTerm] = useState('');\n const [sortBy, setSortBy] = useState('totalRevenue');\n\n useEffect(() => {\n loadOrganizers();\n }, []);\n\n const loadOrganizers = async () => {\n setLoading(true);\n try {\n const result = await makeAuthenticatedRequest('/api/admin/super-analytics?metric=organizer_performance');\n if (result.success) {\n setOrganizers(result.data.organizers || []);\n }\n } catch (error) {\n\n } finally {\n setLoading(false);\n }\n };\n\n const filteredOrganizers = organizers\n .filter(org => \n org.name.toLowerCase().includes(searchTerm.toLowerCase()) ||\n org.email?.toLowerCase().includes(searchTerm.toLowerCase())\n )\n .sort((a, b) => {\n switch (sortBy) {\n case 'name':\n return a.name.localeCompare(b.name);\n case 'events':\n return (b.eventCount || 0) - (a.eventCount || 0);\n case 'revenue':\n return (b.totalRevenue || 0) - (a.totalRevenue || 0);\n case 'joinDate':\n return new Date(b.joinDate || 0).getTime() - new Date(a.joinDate || 0).getTime();\n default:\n return (b.totalRevenue || 0) - (a.totalRevenue || 0);\n }\n });\n\n const getPerformanceColor = (revenue: number) => {\n if (revenue > 10000) return 'text-green-400';\n if (revenue > 5000) return 'text-yellow-400';\n return 'text-white';\n };\n\n if (loading) {\n return (\n <div className=\"p-4 md:p-6\">\n <div className=\"flex items-center justify-center py-12\">\n <div className=\"animate-spin rounded-full h-8 w-8 border-2 border-white border-t-transparent\"></div>\n </div>\n </div>\n );\n }\n\n return (\n <div className=\"p-4 md:p-6 space-y-6\">\n <div className=\"flex flex-col lg:flex-row lg:justify-between lg:items-center gap-4\">\n <h3 className=\"text-xl md:text-2xl font-light text-white\">Organizer Performance Dashboard</h3>\n <button\n onClick={loadOrganizers}\n className=\"flex items-center gap-2 px-4 py-2 bg-white/10 hover:bg-white/20 border border-white/20 rounded-lg text-white text-sm transition-colors self-start lg:self-auto\"\n >\n <svg className=\"w-4 h-4\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15\" />\n </svg>\n Refresh Data\n </button>\n </div>\n\n {/* Organizer Summary Stats */}\n <div className=\"grid grid-cols-1 md:grid-cols-3 gap-4\">\n <div className=\"bg-white/10 backdrop-blur-xl border border-white/20 rounded-xl p-4 md:p-6\">\n <div className=\"flex items-center gap-3 mb-3\">\n <div className=\"w-10 h-10 bg-gradient-to-br from-blue-500/20 to-cyan-500/20 rounded-lg flex items-center justify-center\">\n <svg className=\"w-5 h-5 text-blue-400\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z\" />\n </svg>\n </div>\n <div>\n <p className=\"text-sm font-medium text-white/80\">Total Organizers</p>\n <p className=\"text-2xl font-light text-white\">{organizers.length}</p>\n </div>\n </div>\n </div>\n\n <div className=\"bg-white/10 backdrop-blur-xl border border-white/20 rounded-xl p-4 md:p-6\">\n <div className=\"flex items-center gap-3 mb-3\">\n <div className=\"w-10 h-10 bg-gradient-to-br from-green-500/20 to-emerald-500/20 rounded-lg flex items-center justify-center\">\n <svg className=\"w-5 h-5 text-green-400\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={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-1\" />\n </svg>\n </div>\n <div>\n <p className=\"text-sm font-medium text-white/80\">Avg Revenue</p>\n <p className=\"text-2xl font-light text-white\">\n ${organizers.length > 0 ? \n (organizers.reduce((sum, org) => sum + (org.totalRevenue || 0), 0) / organizers.length).toFixed(0) : \n '0'\n }\n </p>\n </div>\n </div>\n </div>\n\n <div className=\"bg-white/10 backdrop-blur-xl border border-white/20 rounded-xl p-4 md:p-6\">\n <div className=\"flex items-center gap-3 mb-3\">\n <div className=\"w-10 h-10 bg-gradient-to-br from-purple-500/20 to-pink-500/20 rounded-lg flex items-center justify-center\">\n <svg className=\"w-5 h-5 text-purple-400\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={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 002 2h2a2 2 0 002-2V5a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2z\" />\n </svg>\n </div>\n <div>\n <p className=\"text-sm font-medium text-white/80\">Avg Events</p>\n <p className=\"text-2xl font-light text-white\">\n {organizers.length > 0 ? \n (organizers.reduce((sum, org) => sum + (org.eventCount || 0), 0) / organizers.length).toFixed(1) : \n '0'\n }\n </p>\n </div>\n </div>\n </div>\n </div>\n\n {/* Controls */}\n <div className=\"flex flex-col md:flex-row gap-4\">\n <div className=\"flex-1\">\n <input\n type=\"text\"\n placeholder=\"Search organizers...\"\n value={searchTerm}\n onChange={(e) => setSearchTerm(e.target.value)}\n className=\"w-full px-4 py-2 bg-white/10 border border-white/20 rounded-lg text-white placeholder-white/50 focus:ring-2 focus:ring-blue-500 focus:border-transparent\"\n />\n </div>\n <div>\n <select\n value={sortBy}\n onChange={(e) => setSortBy(e.target.value)}\n className=\"px-4 py-2 bg-white/10 border border-white/20 rounded-lg text-white focus:ring-2 focus:ring-blue-500 focus:border-transparent\"\n >\n <option value=\"revenue\">Sort by Revenue</option>\n <option value=\"name\">Sort by Name</option>\n <option value=\"events\">Sort by Events</option>\n <option value=\"joinDate\">Sort by Join Date</option>\n </select>\n </div>\n </div>\n\n {/* Organizers Table */}\n <div className=\"bg-white/10 backdrop-blur-xl border border-white/20 rounded-xl overflow-hidden\">\n <div className=\"overflow-x-auto\">\n <table className=\"w-full min-w-[700px]\">\n <thead className=\"bg-white/10 backdrop-blur-xl\">\n <tr>\n <th className=\"text-left p-3 md:p-4 text-white font-medium text-sm\">Organization</th>\n <th className=\"text-left p-3 md:p-4 text-white font-medium text-sm\">Events</th>\n <th className=\"text-left p-3 md:p-4 text-white font-medium text-sm\">Revenue</th>\n <th className=\"text-left p-3 md:p-4 text-white font-medium text-sm\">Platform Fees</th>\n <th className=\"text-left p-3 md:p-4 text-white font-medium text-sm hidden lg:table-cell\">Avg. Ticket Price</th>\n <th className=\"text-left p-3 md:p-4 text-white font-medium text-sm\">Status</th>\n <th className=\"text-left p-3 md:p-4 text-white font-medium text-sm\">Actions</th>\n </tr>\n </thead>\n <tbody className=\"divide-y divide-white/10\">\n {filteredOrganizers.map(org => (\n <tr key={org.id} className=\"hover:bg-white/10\">\n <td className=\"p-3 md:p-4\">\n <div className=\"text-white font-medium\">{org.name}</div>\n <div className=\"text-white/60 text-sm\">{org.email || 'No email'}</div>\n <div className=\"lg:hidden text-white/60 text-xs mt-1\">\n Avg: ${(org.avgTicketPrice || 0).toFixed(2)}\n </div>\n </td>\n <td className=\"p-3 md:p-4 text-white/80\">\n <div className=\"font-medium\">{org.eventCount || 0}</div>\n <div className=\"text-white/60 text-xs\">{org.publishedEvents || 0} published</div>\n </td>\n <td className=\"p-3 md:p-4\">\n <div className={`font-bold ${getPerformanceColor(org.totalRevenue || 0)}`}>\n ${(org.totalRevenue || 0).toLocaleString()}\n </div>\n <div className=\"text-white/60 text-xs\">{org.ticketsSold || 0} tickets sold</div>\n </td>\n <td className=\"p-3 md:p-4 text-white/80 font-medium\">\n ${(org.platformFees || 0).toLocaleString()}\n </td>\n <td className=\"p-3 md:p-4 text-white/80 hidden lg:table-cell\">\n ${(org.avgTicketPrice || 0).toFixed(2)}\n </td>\n <td className=\"p-3 md:p-4\">\n <span className=\"px-2 py-1 bg-green-500/20 text-green-400 rounded-full text-xs\">\n Active\n </span>\n </td>\n <td className=\"p-3 md:p-4\">\n <div className=\"flex flex-col sm:flex-row gap-1\">\n <button\n onClick={() => window.open(`/admin/organizer-view/${org.id}`, '_blank')}\n className=\"bg-gradient-to-r from-blue-600 to-cyan-600 hover:from-blue-700 hover:to-cyan-700 text-white px-2 py-1 rounded text-xs\"\n >\n View\n </button>\n <button\n onClick={() => window.open(`/admin/organizer/${org.id}/events`, '_blank')}\n className=\"bg-gradient-to-r from-purple-600 to-pink-600 hover:from-purple-700 hover:to-pink-700 text-white px-2 py-1 rounded text-xs\"\n >\n Events\n </button>\n </div>\n </td>\n </tr>\n ))}\n {filteredOrganizers.length === 0 && (\n <tr>\n <td colSpan={7} className=\"p-4 text-white/60 text-center\">\n {searchTerm ? 'No organizers match your search' : 'No organizers found'}\n </td>\n </tr>\n )}\n </tbody>\n </table>\n </div>\n </div>\n </div>\n );\n}","usedDeprecatedRules":[]},{"filePath":"/home/tyler/apps/bct-whitelabel/src/components/admin/RevenueTab.tsx","messages":[{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":5,"column":20,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":5,"endColumn":23,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[158,161],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[158,161],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":6,"column":9,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":6,"endColumn":12,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[171,174],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[171,174],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'platformMetrics' is defined but never used. Allowed unused args must match /^_/u.","line":9,"column":38,"nodeType":null,"messageId":"unusedVar","endLine":9,"endColumn":53},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'user' is defined but never used. Allowed unused args must match /^_/u.","line":9,"column":55,"nodeType":null,"messageId":"unusedVar","endLine":9,"endColumn":59},{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":11,"column":60,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":11,"endColumn":63,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[368,371],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[368,371],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":12,"column":62,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":12,"endColumn":65,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[441,444],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[441,444],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":13,"column":60,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":13,"endColumn":63,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[512,515],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[512,515],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":14,"column":54,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":14,"endColumn":57,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[577,580],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[577,580],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":29,"column":14,"nodeType":null,"messageId":"unusedVar","endLine":29,"endColumn":19},{"ruleId":"no-empty","severity":1,"message":"Empty block statement.","line":29,"column":21,"nodeType":"BlockStatement","messageId":"unexpected","endLine":31,"endColumn":6,"suggestions":[{"messageId":"suggestComment","data":{"type":"block"},"fix":{"range":[898,904],"text":" /* empty */ "},"desc":"Add comment inside empty block statement."}]},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":42,"column":14,"nodeType":null,"messageId":"unusedVar","endLine":42,"endColumn":19},{"ruleId":"no-empty","severity":1,"message":"Empty block statement.","line":42,"column":21,"nodeType":"BlockStatement","messageId":"unexpected","endLine":44,"endColumn":6,"suggestions":[{"messageId":"suggestComment","data":{"type":"block"},"fix":{"range":[1215,1221],"text":" /* empty */ "},"desc":"Add comment inside empty block statement."}]},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":53,"column":14,"nodeType":null,"messageId":"unusedVar","endLine":53,"endColumn":19},{"ruleId":"no-empty","severity":1,"message":"Empty block statement.","line":53,"column":21,"nodeType":"BlockStatement","messageId":"unexpected","endLine":55,"endColumn":6,"suggestions":[{"messageId":"suggestComment","data":{"type":"block"},"fix":{"range":[1488,1494],"text":" /* empty */ "},"desc":"Add comment inside empty block statement."}]},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":64,"column":14,"nodeType":null,"messageId":"unusedVar","endLine":64,"endColumn":19},{"ruleId":"no-empty","severity":1,"message":"Empty block statement.","line":64,"column":21,"nodeType":"BlockStatement","messageId":"unexpected","endLine":66,"endColumn":6,"suggestions":[{"messageId":"suggestComment","data":{"type":"block"},"fix":{"range":[1762,1768],"text":" /* empty */ "},"desc":"Add comment inside empty block statement."}]},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":75,"column":14,"nodeType":null,"messageId":"unusedVar","endLine":75,"endColumn":19},{"ruleId":"no-empty","severity":1,"message":"Empty block statement.","line":75,"column":21,"nodeType":"BlockStatement","messageId":"unexpected","endLine":77,"endColumn":6,"suggestions":[{"messageId":"suggestComment","data":{"type":"block"},"fix":{"range":[2027,2033],"text":" /* empty */ "},"desc":"Add comment inside empty block statement."}]},{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":226,"column":80,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":226,"endColumn":83,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[10045,10048],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[10045,10048],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":227,"column":75,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":227,"endColumn":78,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[10160,10163],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[10160,10163],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":20,"fixableErrorCount":0,"fixableWarningCount":0,"source":"import { useState, useEffect } from 'react';\nimport { makeAuthenticatedRequest } from '../../lib/api-client';\n\ninterface RevenueTabProps {\n platformMetrics: any;\n user: any;\n}\n\nexport default function RevenueTab({ platformMetrics, user }: RevenueTabProps) {\n const [loading, setLoading] = useState(false);\n const [revenueBreakdown, setRevenueBreakdown] = useState<any>(null);\n const [monthlyComparison, setMonthlyComparison] = useState<any>(null);\n const [eventPerformance, setEventPerformance] = useState<any>(null);\n const [salesVelocity, setSalesVelocity] = useState<any>(null);\n\n useEffect(() => {\n loadRevenueData();\n }, []);\n\n const loadRevenueData = async () => {\n setLoading(true);\n try {\n await Promise.all([\n loadRevenueBreakdown(),\n loadMonthlyComparison(),\n loadEventPerformance(),\n loadSalesVelocity()\n ]);\n } catch (error) {\n\n } finally {\n setLoading(false);\n }\n };\n\n const loadRevenueBreakdown = async () => {\n try {\n const result = await makeAuthenticatedRequest('/api/admin/super-analytics?metric=revenue_breakdown');\n if (result.success) {\n setRevenueBreakdown(result.data);\n }\n } catch (error) {\n\n }\n };\n\n const loadMonthlyComparison = async () => {\n try {\n const result = await makeAuthenticatedRequest('/api/admin/super-analytics?metric=sales_trends');\n if (result.success) {\n setMonthlyComparison(result.data);\n }\n } catch (error) {\n\n }\n };\n\n const loadEventPerformance = async () => {\n try {\n const result = await makeAuthenticatedRequest('/api/admin/super-analytics?metric=event_analytics');\n if (result.success) {\n setEventPerformance(result.data);\n }\n } catch (error) {\n\n }\n };\n\n const loadSalesVelocity = async () => {\n try {\n const result = await makeAuthenticatedRequest('/api/admin/super-analytics?metric=sales_trends');\n if (result.success) {\n setSalesVelocity(result.data);\n }\n } catch (error) {\n\n }\n };\n\n if (loading) {\n return (\n <div className=\"p-6\">\n <div className=\"flex items-center justify-center py-12\">\n <div className=\"animate-spin rounded-full h-8 w-8 border-2 border-white border-t-transparent\"></div>\n </div>\n </div>\n );\n }\n\n return (\n <div className=\"p-6 space-y-6\">\n <div className=\"flex justify-between items-center\">\n <h3 className=\"text-2xl font-light text-white\">Revenue & Performance Analysis</h3>\n <button\n onClick={loadRevenueData}\n className=\"flex items-center gap-2 px-4 py-2 bg-white/10 hover:bg-white/20 border border-white/20 rounded-lg text-white text-sm transition-colors\"\n >\n <svg className=\"w-4 h-4\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15\" />\n </svg>\n Refresh Data\n </button>\n </div>\n\n <div className=\"grid grid-cols-1 lg:grid-cols-2 gap-4 md:gap-6\">\n {/* Revenue Breakdown */}\n <div className=\"bg-white/10 backdrop-blur-xl border border-white/20 rounded-xl p-4 md:p-6 shadow-lg\">\n <h4 className=\"text-base md:text-lg font-medium text-white mb-3 md:mb-4\">Revenue Breakdown</h4>\n {revenueBreakdown ? (\n <div className=\"space-y-4\">\n {(() => {\n const totals = revenueBreakdown.totals || {};\n const processingFees = (totals.grossRevenue || 0) * 0.029;\n const netToOrganizers = (totals.grossRevenue || 0) - (totals.platformFees || 0) - processingFees;\n \n return (\n <>\n <div className=\"flex justify-between items-center p-3 bg-white/5 rounded-lg\">\n <span className=\"text-white\">Gross Revenue</span>\n <span className=\"text-white font-bold\">${(totals.grossRevenue || 0).toLocaleString()}</span>\n </div>\n <div className=\"flex justify-between items-center p-3 bg-white/5 rounded-lg\">\n <span className=\"text-white\">Platform Fees</span>\n <span className=\"text-white font-bold\">${(totals.platformFees || 0).toLocaleString()}</span>\n </div>\n <div className=\"flex justify-between items-center p-3 bg-white/5 rounded-lg\">\n <span className=\"text-white\">Processing Fees</span>\n <span className=\"text-white font-bold\">${processingFees.toLocaleString()}</span>\n </div>\n <div className=\"flex justify-between items-center p-3 bg-white/5 rounded-lg\">\n <span className=\"text-white\">Net to Organizers</span>\n <span className=\"text-white font-bold\">${netToOrganizers.toLocaleString()}</span>\n </div>\n </>\n );\n })()}\n </div>\n ) : (\n <div className=\"text-white/60 text-center py-8\">No revenue breakdown available</div>\n )}\n </div>\n\n {/* Monthly Comparison */}\n <div className=\"bg-white/10 backdrop-blur-xl border border-white/20 rounded-xl p-4 md:p-6 shadow-lg\">\n <h4 className=\"text-base md:text-lg font-medium text-white mb-3 md:mb-4\">Monthly Comparison</h4>\n {monthlyComparison ? (\n <div className=\"space-y-4\">\n {(() => {\n const summary = monthlyComparison.summary || {};\n return (\n <>\n <div className=\"flex justify-between items-center p-3 bg-white/5 rounded-lg\">\n <span className=\"text-white\">Average Monthly Revenue</span>\n <span className=\"text-white font-bold\">${(summary.averageMonthlyRevenue || 0).toFixed(2)}</span>\n </div>\n <div className=\"flex justify-between items-center p-3 bg-white/5 rounded-lg\">\n <span className=\"text-white\">Revenue Growth</span>\n <span className={`font-bold ${\n (summary.growth || 0) > 0 ? 'text-green-400' : 'text-red-400'\n }`}>\n {(summary.growth || 0) > 0 ? '+' : ''}{(summary.growth || 0).toFixed(1)}%\n </span>\n </div>\n <div className=\"flex justify-between items-center p-3 bg-white/5 rounded-lg\">\n <span className=\"text-white\">Average Platform Fees</span>\n <span className=\"text-white font-bold\">${(summary.averageMonthlyFees || 0).toFixed(2)}</span>\n </div>\n <div className=\"flex justify-between items-center p-3 bg-white/5 rounded-lg\">\n <span className=\"text-white\">Total Periods</span>\n <span className=\"text-white font-bold\">{summary.totalPeriods || 0}</span>\n </div>\n </>\n );\n })()}\n </div>\n ) : (\n <div className=\"text-white/60 text-center py-8\">No monthly comparison available</div>\n )}\n </div>\n\n {/* Event Performance */}\n <div className=\"bg-white/10 backdrop-blur-xl border border-white/20 rounded-xl p-4 md:p-6 shadow-lg\">\n <h4 className=\"text-base md:text-lg font-medium text-white mb-3 md:mb-4\">Event Performance Metrics</h4>\n {eventPerformance ? (\n <div className=\"space-y-4\">\n {(() => {\n const summary = eventPerformance.summary || {};\n const eventsThisMonth = Math.floor((summary.totalEvents || 0) / 12);\n return (\n <>\n <div className=\"flex justify-between items-center p-3 bg-white/5 rounded-lg\">\n <span className=\"text-white\">Average Ticket Price</span>\n <span className=\"text-white font-bold\">${(summary.avgTicketPrice || 0).toFixed(2)}</span>\n </div>\n <div className=\"flex justify-between items-center p-3 bg-white/5 rounded-lg\">\n <span className=\"text-white\">Events This Month</span>\n <span className=\"text-white font-bold\">{eventsThisMonth}</span>\n </div>\n <div className=\"flex justify-between items-center p-3 bg-white/5 rounded-lg\">\n <span className=\"text-white\">Avg. Tickets per Event</span>\n <span className=\"text-white font-bold\">\n {(summary.totalEvents || 0) > 0 ? Math.floor((summary.totalTicketsSold || 0) / (summary.totalEvents || 1)) : 0}\n </span>\n </div>\n <div className=\"flex justify-between items-center p-3 bg-white/5 rounded-lg\">\n <span className=\"text-white\">Sell-through Rate</span>\n <span className=\"text-white font-bold\">{(summary.avgSellThroughRate || 0).toFixed(1)}%</span>\n </div>\n </>\n );\n })()}\n </div>\n ) : (\n <div className=\"text-white/60 text-center py-8\">No event performance data available</div>\n )}\n </div>\n\n {/* Sales Velocity */}\n <div className=\"bg-white/10 backdrop-blur-xl border border-white/20 rounded-xl p-4 md:p-6 shadow-lg\">\n <h4 className=\"text-base md:text-lg font-medium text-white mb-3 md:mb-4\">Sales Velocity</h4>\n {salesVelocity ? (\n <div className=\"space-y-4\">\n {(() => {\n const trends = salesVelocity.trends || [];\n const recentTrends = trends.slice(-6);\n const totalTransactions = recentTrends.reduce((sum: number, t: any) => sum + (t.transactions || 0), 0);\n const totalRevenue = recentTrends.reduce((sum: number, t: any) => sum + (t.revenue || 0), 0);\n const avgTransactionValue = totalTransactions > 0 ? totalRevenue / totalTransactions : 0;\n \n const currentMonth = recentTrends[recentTrends.length - 1] || {};\n const previousMonth = recentTrends[recentTrends.length - 2] || {};\n const momGrowth = previousMonth && (previousMonth.revenue || 0) > 0 ? \n (((currentMonth.revenue || 0) - (previousMonth.revenue || 0)) / (previousMonth.revenue || 1)) * 100 : 0;\n \n return (\n <>\n <div className=\"flex justify-between items-center p-3 bg-white/5 rounded-lg\">\n <span className=\"text-white\">Recent Transactions</span>\n <span className=\"text-white font-bold\">{totalTransactions}</span>\n </div>\n <div className=\"flex justify-between items-center p-3 bg-white/5 rounded-lg\">\n <span className=\"text-white\">Avg Transaction Value</span>\n <span className=\"text-white font-bold\">${avgTransactionValue.toFixed(2)}</span>\n </div>\n <div className=\"flex justify-between items-center p-3 bg-white/5 rounded-lg\">\n <span className=\"text-white\">Month-over-Month</span>\n <span className={`font-bold ${momGrowth > 0 ? 'text-green-400' : 'text-red-400'}`}>\n {momGrowth > 0 ? '+' : ''}{momGrowth.toFixed(1)}%\n </span>\n </div>\n <div className=\"flex justify-between items-center p-3 bg-white/5 rounded-lg\">\n <span className=\"text-white\">Recent Revenue</span>\n <span className=\"text-white font-bold\">${totalRevenue.toFixed(2)}</span>\n </div>\n </>\n );\n })()}\n </div>\n ) : (\n <div className=\"text-white/60 text-center py-8\">No sales velocity data available</div>\n )}\n </div>\n </div>\n </div>\n );\n}","usedDeprecatedRules":[]},{"filePath":"/home/tyler/apps/bct-whitelabel/src/components/admin/SuperAdminTabNavigation.tsx","messages":[{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'useState' is defined but never used. Allowed unused vars must match /^_/u.","line":1,"column":10,"nodeType":null,"messageId":"unusedVar","endLine":1,"endColumn":18},{"ruleId":"no-undef","severity":2,"message":"'React' is not defined.","line":6,"column":9,"nodeType":"Identifier","messageId":"undef","endLine":6,"endColumn":14},{"ruleId":"no-undef","severity":2,"message":"'React' is not defined.","line":7,"column":14,"nodeType":"Identifier","messageId":"undef","endLine":7,"endColumn":19},{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":7,"column":34,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":7,"endColumn":37,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[139,142],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[139,142],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":14,"column":20,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":14,"endColumn":23,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[284,287],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[284,287],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":15,"column":9,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":15,"endColumn":12,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[297,300],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[297,300],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]}],"suppressedMessages":[],"errorCount":2,"fatalErrorCount":0,"warningCount":4,"fixableErrorCount":0,"fixableWarningCount":0,"source":"import { useState } from 'react';\n\ninterface Tab {\n id: string;\n name: string;\n icon: React.ReactNode;\n component: React.ComponentType<any>;\n}\n\ninterface SuperAdminTabNavigationProps {\n tabs: Tab[];\n activeTab: string;\n onTabChange: (tabId: string) => void;\n platformMetrics: any;\n user: any;\n}\n\nexport default function SuperAdminTabNavigation({ \n tabs, \n activeTab, \n onTabChange, \n platformMetrics, \n user \n}: SuperAdminTabNavigationProps) {\n \n const ActiveComponent = tabs.find(tab => tab.id === activeTab)?.component;\n\n return (\n <div className=\"space-y-6\">\n {/* Navigation Tabs */}\n <div className=\"mb-6\">\n <nav className=\"flex space-x-8 border-b border-white/20\">\n {tabs.map((tab) => (\n <button\n key={tab.id}\n onClick={() => onTabChange(tab.id)}\n className={`tab-button border-b-2 py-2 px-4 text-sm font-medium transition-all duration-200 hover:bg-white/10 rounded-t-lg flex items-center gap-2 ${\n activeTab === tab.id\n ? 'border-indigo-400 text-white bg-white/10'\n : 'border-transparent text-white/80 hover:text-white'\n }`}\n >\n {tab.icon}\n {tab.name}\n </button>\n ))}\n </nav>\n </div>\n\n {/* Tab Content */}\n <div className=\"bg-white/10 backdrop-blur-xl border border-white/20 shadow-lg rounded-2xl\">\n {ActiveComponent && (\n <ActiveComponent \n platformMetrics={platformMetrics}\n user={user}\n />\n )}\n </div>\n </div>\n );\n}","usedDeprecatedRules":[]},{"filePath":"/home/tyler/apps/bct-whitelabel/src/components/craft/components/ButtonBlock.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/home/tyler/apps/bct-whitelabel/src/components/craft/components/EventDetails.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/home/tyler/apps/bct-whitelabel/src/components/craft/components/HeroSection.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/home/tyler/apps/bct-whitelabel/src/components/craft/components/ImageBlock.tsx","messages":[{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'setProp' is assigned a value but never used. Allowed unused vars must match /^_/u.","line":23,"column":53,"nodeType":null,"messageId":"unusedVar","endLine":23,"endColumn":60}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":1,"fixableErrorCount":0,"fixableWarningCount":0,"source":"import React from 'react';\nimport { useNode } from '@craftjs/core';\n\ninterface ImageBlockProps {\n src?: string;\n alt?: string;\n width?: 'auto' | 'full' | '1/2' | '1/3' | '2/3' | '1/4' | '3/4';\n height?: 'auto' | '32' | '48' | '64' | '80' | '96';\n objectFit?: 'cover' | 'contain' | 'fill' | 'scale-down';\n rounded?: 'none' | 'sm' | 'md' | 'lg' | 'xl' | '2xl' | 'full';\n className?: string;\n}\n\nexport const ImageBlock: React.FC<ImageBlockProps> = ({\n src = 'https://via.placeholder.com/400x300?text=Add+Image',\n alt = 'Image',\n width = 'full',\n height = 'auto',\n objectFit = 'cover',\n rounded = 'lg',\n className = ''\n}) => {\n const { connectors: { connect, drag }, actions: { setProp } } = useNode();\n\n const widthClasses = {\n auto: 'w-auto',\n full: 'w-full',\n '1/2': 'w-1/2',\n '1/3': 'w-1/3',\n '2/3': 'w-2/3',\n '1/4': 'w-1/4',\n '3/4': 'w-3/4'\n };\n\n const heightClasses = {\n auto: 'h-auto',\n '32': 'h-32',\n '48': 'h-48',\n '64': 'h-64',\n '80': 'h-80',\n '96': 'h-96'\n };\n\n const objectFitClasses = {\n cover: 'object-cover',\n contain: 'object-contain',\n fill: 'object-fill',\n 'scale-down': 'object-scale-down'\n };\n\n const roundedClasses = {\n none: 'rounded-none',\n sm: 'rounded-sm',\n md: 'rounded-md',\n lg: 'rounded-lg',\n xl: 'rounded-xl',\n '2xl': 'rounded-2xl',\n full: 'rounded-full'\n };\n\n return (\n <div\n ref={(ref) => connect(drag(ref))}\n className={`p-2 ${className}`}\n >\n <img\n src={src}\n alt={alt}\n className={`\n ${widthClasses[width]}\n ${heightClasses[height]}\n ${objectFitClasses[objectFit]}\n ${roundedClasses[rounded]}\n shadow-lg\n `}\n onError={(e) => {\n (e.target as HTMLImageElement).src = 'https://via.placeholder.com/400x300?text=Image+Not+Found';\n }}\n />\n </div>\n );\n};\n\nImageBlock.craft = {\n displayName: 'Image Block',\n props: {\n src: 'https://via.placeholder.com/400x300?text=Add+Image',\n alt: 'Image',\n width: 'full',\n height: 'auto',\n objectFit: 'cover',\n rounded: 'lg',\n className: ''\n },\n related: {\n toolbar: () => (\n <div className=\"p-4 space-y-4\">\n <div>\n <label className=\"block text-sm font-medium text-gray-700 mb-2\">\n Image URL\n </label>\n <input\n type=\"url\"\n className=\"w-full px-3 py-2 border border-gray-300 rounded-md\"\n placeholder=\"https://...\"\n />\n </div>\n \n <div>\n <label className=\"block text-sm font-medium text-gray-700 mb-2\">\n Alt Text\n </label>\n <input\n type=\"text\"\n className=\"w-full px-3 py-2 border border-gray-300 rounded-md\"\n placeholder=\"Describe the image\"\n />\n </div>\n \n <div>\n <label className=\"block text-sm font-medium text-gray-700 mb-2\">\n Width\n </label>\n <select className=\"w-full px-3 py-2 border border-gray-300 rounded-md\">\n <option value=\"auto\">Auto</option>\n <option value=\"full\">Full Width</option>\n <option value=\"1/2\">Half Width</option>\n <option value=\"1/3\">One Third</option>\n <option value=\"2/3\">Two Thirds</option>\n <option value=\"1/4\">One Quarter</option>\n <option value=\"3/4\">Three Quarters</option>\n </select>\n </div>\n \n <div>\n <label className=\"block text-sm font-medium text-gray-700 mb-2\">\n Height\n </label>\n <select className=\"w-full px-3 py-2 border border-gray-300 rounded-md\">\n <option value=\"auto\">Auto</option>\n <option value=\"32\">8rem</option>\n <option value=\"48\">12rem</option>\n <option value=\"64\">16rem</option>\n <option value=\"80\">20rem</option>\n <option value=\"96\">24rem</option>\n </select>\n </div>\n \n <div>\n <label className=\"block text-sm font-medium text-gray-700 mb-2\">\n Object Fit\n </label>\n <select className=\"w-full px-3 py-2 border border-gray-300 rounded-md\">\n <option value=\"cover\">Cover</option>\n <option value=\"contain\">Contain</option>\n <option value=\"fill\">Fill</option>\n <option value=\"scale-down\">Scale Down</option>\n </select>\n </div>\n \n <div>\n <label className=\"block text-sm font-medium text-gray-700 mb-2\">\n Border Radius\n </label>\n <select className=\"w-full px-3 py-2 border border-gray-300 rounded-md\">\n <option value=\"none\">None</option>\n <option value=\"sm\">Small</option>\n <option value=\"md\">Medium</option>\n <option value=\"lg\">Large</option>\n <option value=\"xl\">Extra Large</option>\n <option value=\"2xl\">2X Large</option>\n <option value=\"full\">Full (Circle)</option>\n </select>\n </div>\n </div>\n )\n }\n};","usedDeprecatedRules":[]},{"filePath":"/home/tyler/apps/bct-whitelabel/src/components/craft/components/SpacerBlock.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/home/tyler/apps/bct-whitelabel/src/components/craft/components/TextBlock.tsx","messages":[{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":60,"column":42,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":60,"endColumn":45,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[1449,1452],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[1449,1452],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":1,"fixableErrorCount":0,"fixableWarningCount":0,"source":"import React from 'react';\nimport { useNode } from '@craftjs/core';\nimport ContentEditable from 'react-contenteditable';\n\ninterface TextBlockProps {\n text?: string;\n fontSize?: 'sm' | 'base' | 'lg' | 'xl' | '2xl' | '3xl';\n fontWeight?: 'normal' | 'medium' | 'semibold' | 'bold';\n textAlign?: 'left' | 'center' | 'right';\n color?: 'slate-900' | 'slate-600' | 'slate-500' | 'white';\n className?: string;\n}\n\nexport const TextBlock: React.FC<TextBlockProps> = ({\n text = 'Edit this text...',\n fontSize = 'base',\n fontWeight = 'normal',\n textAlign = 'left',\n color = 'slate-900',\n className = ''\n}) => {\n const { connectors: { connect, drag }, actions: { setProp } } = useNode();\n\n const fontSizeClasses = {\n sm: 'text-sm',\n base: 'text-base',\n lg: 'text-lg',\n xl: 'text-xl',\n '2xl': 'text-2xl',\n '3xl': 'text-3xl'\n };\n\n const fontWeightClasses = {\n normal: 'font-normal',\n medium: 'font-medium',\n semibold: 'font-semibold',\n bold: 'font-bold'\n };\n\n const textAlignClasses = {\n left: 'text-left',\n center: 'text-center',\n right: 'text-right'\n };\n\n const colorClasses = {\n 'slate-900': 'text-slate-900',\n 'slate-600': 'text-slate-600',\n 'slate-500': 'text-slate-500',\n 'white': 'text-white'\n };\n\n return (\n <div\n ref={(ref) => connect(drag(ref))}\n className={`p-2 ${className}`}\n >\n <ContentEditable\n html={text}\n onChange={(e) => setProp((props: any) => props.text = e.target.value)}\n className={`\n outline-none\n ${fontSizeClasses[fontSize]}\n ${fontWeightClasses[fontWeight]}\n ${textAlignClasses[textAlign]}\n ${colorClasses[color]}\n `}\n />\n </div>\n );\n};\n\nTextBlock.craft = {\n displayName: 'Text Block',\n props: {\n text: 'Edit this text...',\n fontSize: 'base',\n fontWeight: 'normal',\n textAlign: 'left',\n color: 'slate-900',\n className: ''\n },\n related: {\n toolbar: () => (\n <div className=\"p-4 space-y-4\">\n <div>\n <label className=\"block text-sm font-medium text-gray-700 mb-2\">\n Font Size\n </label>\n <select className=\"w-full px-3 py-2 border border-gray-300 rounded-md\">\n <option value=\"sm\">Small</option>\n <option value=\"base\">Base</option>\n <option value=\"lg\">Large</option>\n <option value=\"xl\">Extra Large</option>\n <option value=\"2xl\">2X Large</option>\n <option value=\"3xl\">3X Large</option>\n </select>\n </div>\n \n <div>\n <label className=\"block text-sm font-medium text-gray-700 mb-2\">\n Font Weight\n </label>\n <select className=\"w-full px-3 py-2 border border-gray-300 rounded-md\">\n <option value=\"normal\">Normal</option>\n <option value=\"medium\">Medium</option>\n <option value=\"semibold\">Semibold</option>\n <option value=\"bold\">Bold</option>\n </select>\n </div>\n \n <div>\n <label className=\"block text-sm font-medium text-gray-700 mb-2\">\n Text Alignment\n </label>\n <select className=\"w-full px-3 py-2 border border-gray-300 rounded-md\">\n <option value=\"left\">Left</option>\n <option value=\"center\">Center</option>\n <option value=\"right\">Right</option>\n </select>\n </div>\n \n <div>\n <label className=\"block text-sm font-medium text-gray-700 mb-2\">\n Text Color\n </label>\n <select className=\"w-full px-3 py-2 border border-gray-300 rounded-md\">\n <option value=\"slate-900\">Dark</option>\n <option value=\"slate-600\">Medium</option>\n <option value=\"slate-500\">Light</option>\n <option value=\"white\">White</option>\n </select>\n </div>\n </div>\n )\n }\n};","usedDeprecatedRules":[]},{"filePath":"/home/tyler/apps/bct-whitelabel/src/components/craft/components/TicketSection.tsx","messages":[{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":42,"column":64,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":42,"endColumn":67,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[1503,1506],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[1503,1506],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":48,"column":64,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":48,"endColumn":67,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[1840,1843],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[1840,1843],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":2,"fixableErrorCount":0,"fixableWarningCount":0,"source":"import React from 'react';\nimport { useNode } from '@craftjs/core';\nimport TicketCheckout from '../../TicketCheckout';\n\ninterface TicketSectionProps {\n title?: string;\n subtitle?: string;\n showStats?: boolean;\n className?: string;\n}\n\nexport const TicketSection: React.FC<TicketSectionProps> = ({\n title = 'Get Your Tickets',\n subtitle = '',\n showStats = false,\n className = ''\n}) => {\n const { connectors: { connect, drag }, custom } = useNode();\n const { event } = custom || {};\n\n return (\n <div\n ref={(ref) => connect(drag(ref))}\n className={`bg-gradient-to-br from-slate-50 to-white rounded-xl p-4 sm:p-5 border border-slate-200 shadow-lg ${className}`}\n >\n <div className=\"mb-3 sm:mb-4\">\n <h2 className=\"text-base sm:text-lg font-semibold text-slate-900 flex items-center\">\n <div className=\"w-2 h-2 bg-gradient-to-r from-emerald-500 to-green-500 rounded-full mr-2\"></div>\n {title}\n </h2>\n {subtitle && (\n <p className=\"text-slate-600 text-sm mt-1\">{subtitle}</p>\n )}\n </div>\n \n {showStats && event?.ticket_types && (\n <div className=\"mb-4 p-3 bg-white rounded-lg border border-slate-200\">\n <div className=\"grid grid-cols-2 gap-4 text-center\">\n <div>\n <p className=\"text-xs text-slate-500 uppercase tracking-wide\">Available</p>\n <p className=\"text-lg font-semibold text-slate-900\">\n {event.ticket_types.reduce((sum: number, type: any) => sum + (type.quantity_available - type.quantity_sold), 0)}\n </p>\n </div>\n <div>\n <p className=\"text-xs text-slate-500 uppercase tracking-wide\">Sold</p>\n <p className=\"text-lg font-semibold text-slate-900\">\n {event.ticket_types.reduce((sum: number, type: any) => sum + type.quantity_sold, 0)}\n </p>\n </div>\n </div>\n </div>\n )}\n \n {event && <TicketCheckout event={event} />}\n </div>\n );\n};\n\nTicketSection.craft = {\n displayName: 'Ticket Section',\n props: {\n title: 'Get Your Tickets',\n subtitle: '',\n showStats: false,\n className: ''\n },\n related: {\n toolbar: () => (\n <div className=\"p-4 space-y-4\">\n <div>\n <label className=\"block text-sm font-medium text-gray-700 mb-2\">\n Section Title\n </label>\n <input\n type=\"text\"\n className=\"w-full px-3 py-2 border border-gray-300 rounded-md\"\n placeholder=\"Get Your Tickets\"\n />\n </div>\n <div>\n <label className=\"block text-sm font-medium text-gray-700 mb-2\">\n Subtitle\n </label>\n <input\n type=\"text\"\n className=\"w-full px-3 py-2 border border-gray-300 rounded-md\"\n placeholder=\"Optional subtitle\"\n />\n </div>\n <div>\n <label className=\"flex items-center\">\n <input type=\"checkbox\" className=\"mr-2\" />\n Show Ticket Statistics\n </label>\n </div>\n </div>\n )\n }\n};","usedDeprecatedRules":[]},{"filePath":"/home/tyler/apps/bct-whitelabel/src/components/craft/components/TwoColumnLayout.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/home/tyler/apps/bct-whitelabel/src/components/craft/components/index.ts","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/home/tyler/apps/bct-whitelabel/src/components/manage/AddonsTab.tsx","messages":[{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":79,"column":14,"nodeType":null,"messageId":"unusedVar","endLine":79,"endColumn":19},{"ruleId":"no-empty","severity":1,"message":"Empty block statement.","line":79,"column":21,"nodeType":"BlockStatement","messageId":"unexpected","endLine":81,"endColumn":6,"suggestions":[{"messageId":"suggestComment","data":{"type":"block"},"fix":{"range":[2016,2022],"text":" /* empty */ "},"desc":"Add comment inside empty block statement."}]},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":115,"column":14,"nodeType":null,"messageId":"unusedVar","endLine":115,"endColumn":19},{"ruleId":"no-empty","severity":1,"message":"Empty block statement.","line":115,"column":21,"nodeType":"BlockStatement","messageId":"unexpected","endLine":117,"endColumn":6,"suggestions":[{"messageId":"suggestComment","data":{"type":"block"},"fix":{"range":[2732,2738],"text":" /* empty */ "},"desc":"Add comment inside empty block statement."}]},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":130,"column":14,"nodeType":null,"messageId":"unusedVar","endLine":130,"endColumn":19},{"ruleId":"no-empty","severity":1,"message":"Empty block statement.","line":130,"column":21,"nodeType":"BlockStatement","messageId":"unexpected","endLine":132,"endColumn":6,"suggestions":[{"messageId":"suggestComment","data":{"type":"block"},"fix":{"range":[3068,3074],"text":" /* empty */ "},"desc":"Add comment inside empty block statement."}]},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":147,"column":14,"nodeType":null,"messageId":"unusedVar","endLine":147,"endColumn":19},{"ruleId":"no-empty","severity":1,"message":"Empty block statement.","line":147,"column":21,"nodeType":"BlockStatement","messageId":"unexpected","endLine":149,"endColumn":6,"suggestions":[{"messageId":"suggestComment","data":{"type":"block"},"fix":{"range":[3496,3502],"text":" /* empty */ "},"desc":"Add comment inside empty block statement."}]}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":8,"fixableErrorCount":0,"fixableWarningCount":0,"source":"import { useState, useEffect } from 'react';\nimport { createClient } from '@supabase/supabase-js';\nimport { formatCurrency } from '../../lib/event-management';\n\nconst supabase = createClient(\n import.meta.env.PUBLIC_SUPABASE_URL,\n import.meta.env.PUBLIC_SUPABASE_ANON_KEY\n);\n\ninterface Addon {\n id: string;\n name: string;\n description: string;\n price_cents: number;\n category: string;\n is_active: boolean;\n organization_id: string;\n}\n\ninterface EventAddon {\n id: string;\n event_id: string;\n addon_id: string;\n is_active: boolean;\n addons: Addon;\n}\n\ninterface AddonsTabProps {\n eventId: string;\n organizationId: string;\n}\n\nexport default function AddonsTab({ eventId, organizationId }: AddonsTabProps) {\n const [availableAddons, setAvailableAddons] = useState<Addon[]>([]);\n const [eventAddons, setEventAddons] = useState<EventAddon[]>([]);\n const [loading, setLoading] = useState(true);\n\n useEffect(() => {\n loadData();\n }, [eventId, organizationId]);\n\n const loadData = async () => {\n setLoading(true);\n try {\n // Load available addons for the organization\n const { data: addonsData, error: addonsError } = await supabase\n .from('addons')\n .select('*')\n .eq('organization_id', organizationId)\n .eq('is_active', true)\n .order('category', { ascending: true });\n\n if (addonsError) throw addonsError;\n\n // Load event-specific addons\n const { data: eventAddonsData, error: eventAddonsError } = await supabase\n .from('event_addons')\n .select(`\n id,\n event_id,\n addon_id,\n is_active,\n addons (\n id,\n name,\n description,\n price_cents,\n category,\n is_active,\n organization_id\n )\n `)\n .eq('event_id', eventId);\n\n if (eventAddonsError) throw eventAddonsError;\n\n setAvailableAddons(addonsData || []);\n setEventAddons(eventAddonsData || []);\n } catch (error) {\n\n } finally {\n setLoading(false);\n }\n };\n\n const handleAddAddon = async (addon: Addon) => {\n try {\n const { data, error } = await supabase\n .from('event_addons')\n .insert({\n event_id: eventId,\n addon_id: addon.id,\n is_active: true\n })\n .select(`\n id,\n event_id,\n addon_id,\n is_active,\n addons (\n id,\n name,\n description,\n price_cents,\n category,\n is_active,\n organization_id\n )\n `)\n .single();\n\n if (error) throw error;\n\n setEventAddons(prev => [...prev, data]);\n } catch (error) {\n\n }\n };\n\n const handleRemoveAddon = async (eventAddon: EventAddon) => {\n try {\n const { error } = await supabase\n .from('event_addons')\n .delete()\n .eq('id', eventAddon.id);\n\n if (error) throw error;\n\n setEventAddons(prev => prev.filter(ea => ea.id !== eventAddon.id));\n } catch (error) {\n\n }\n };\n\n const handleToggleAddon = async (eventAddon: EventAddon) => {\n try {\n const { error } = await supabase\n .from('event_addons')\n .update({ is_active: !eventAddon.is_active })\n .eq('id', eventAddon.id);\n\n if (error) throw error;\n\n setEventAddons(prev => prev.map(ea => \n ea.id === eventAddon.id ? { ...ea, is_active: !ea.is_active } : ea\n ));\n } catch (error) {\n\n }\n };\n\n const getCategoryIcon = (category: string) => {\n switch (category.toLowerCase()) {\n case 'merchandise':\n return (\n <svg className=\"w-5 h-5\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M7 7h.01M7 3h5c.512 0 1.024.195 1.414.586l7 7a2 2 0 010 2.828l-7 7a2 2 0 01-2.828 0l-7-7A1.994 1.994 0 013 12V7a4 4 0 014-4z\" />\n </svg>\n );\n case 'food':\n return (\n <svg className=\"w-5 h-5\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M12 6V4m0 2a2 2 0 100 4m0-4a2 2 0 110 4m-6 8a2 2 0 100-4m0 4a2 2 0 100 4m0-4v2m0-6V4m6 6v10m6-2a2 2 0 100-4m0 4a2 2 0 100 4m0-4v2m0-6V4\" />\n </svg>\n );\n case 'drink':\n return (\n <svg className=\"w-5 h-5\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M20.354 15.354A9 9 0 018.646 3.646 9.003 9.003 0 0012 21a9.003 9.003 0 008.354-5.646z\" />\n </svg>\n );\n case 'service':\n return (\n <svg className=\"w-5 h-5\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M13 10V3L4 14h7v7l9-11h-7z\" />\n </svg>\n );\n default:\n return (\n <svg className=\"w-5 h-5\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M12 6v6m0 0v6m0-6h6m-6 0H6\" />\n </svg>\n );\n }\n };\n\n const groupByCategory = (addons: Addon[]) => {\n return addons.reduce((acc, addon) => {\n const category = addon.category || 'Other';\n if (!acc[category]) acc[category] = [];\n acc[category].push(addon);\n return acc;\n }, {} as Record<string, Addon[]>);\n };\n\n const isAddonAdded = (addon: Addon) => {\n return eventAddons.some(ea => ea.addon_id === addon.id);\n };\n\n const getEventAddon = (addon: Addon) => {\n return eventAddons.find(ea => ea.addon_id === addon.id);\n };\n\n const groupedAvailableAddons = groupByCategory(availableAddons);\n const groupedEventAddons = groupByCategory(eventAddons.map(ea => ea.addons));\n\n if (loading) {\n return (\n <div className=\"flex items-center justify-center py-12\">\n <div className=\"animate-spin rounded-full h-8 w-8 border-b-2 border-white\"></div>\n </div>\n );\n }\n\n return (\n <div className=\"space-y-6\">\n <div className=\"flex justify-between items-center\">\n <h2 className=\"text-2xl font-light text-white\">Add-ons & Extras</h2>\n </div>\n\n <div className=\"grid grid-cols-1 lg:grid-cols-2 gap-8\">\n {/* Current Add-ons */}\n <div>\n <h3 className=\"text-xl font-semibold text-white mb-4\">Current Add-ons</h3>\n \n {eventAddons.length === 0 ? (\n <div className=\"bg-white/5 border border-white/20 rounded-xl p-8 text-center\">\n <svg className=\"w-12 h-12 text-white/40 mx-auto mb-4\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M20 7l-8-4-8 4m16 0l-8 4m8-4v10l-8 4m0-10L4 7m8 4v10M4 7v10l8 4\" />\n </svg>\n <p className=\"text-white/60\">No add-ons added to this event yet</p>\n <p className=\"text-white/40 text-sm mt-2\">Select from available add-ons to get started</p>\n </div>\n ) : (\n <div className=\"space-y-4\">\n {Object.entries(groupedEventAddons).map(([category, addons]) => (\n <div key={category} className=\"bg-white/5 border border-white/20 rounded-xl p-6\">\n <div className=\"flex items-center gap-2 mb-4\">\n <div className=\"text-blue-400\">\n {getCategoryIcon(category)}\n </div>\n <h4 className=\"text-lg font-semibold text-white\">{category}</h4>\n </div>\n \n <div className=\"space-y-3\">\n {addons.map((addon) => {\n const eventAddon = getEventAddon(addon);\n return (\n <div key={addon.id} className=\"flex items-center justify-between p-3 bg-white/5 border border-white/10 rounded-lg\">\n <div className=\"flex-1\">\n <div className=\"flex items-center gap-3\">\n <div className=\"text-white font-medium\">{addon.name}</div>\n <span className={`px-2 py-1 text-xs rounded-full ${\n eventAddon?.is_active\n ? 'bg-green-500/20 text-green-300 border border-green-500/30'\n : 'bg-red-500/20 text-red-300 border border-red-500/30'\n }`}>\n {eventAddon?.is_active ? 'Active' : 'Inactive'}\n </span>\n </div>\n {addon.description && (\n <div className=\"text-white/60 text-sm mt-1\">{addon.description}</div>\n )}\n </div>\n <div className=\"flex items-center gap-3\">\n <div className=\"text-white font-bold\">{formatCurrency(addon.price_cents)}</div>\n <div className=\"flex items-center gap-1\">\n <button\n onClick={() => eventAddon && handleToggleAddon(eventAddon)}\n className=\"p-2 text-white/60 hover:text-white transition-colors\"\n title={eventAddon?.is_active ? 'Deactivate' : 'Activate'}\n >\n {eventAddon?.is_active ? (\n <svg className=\"w-4 h-4\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M13.875 18.825A10.05 10.05 0 0112 19c-4.478 0-8.268-2.943-9.543-7a9.97 9.97 0 011.563-3.029m5.858.908a3 3 0 114.142 4.142M9.878 9.878l4.242 4.242M9.878 9.878L3 3m6.878 6.878L21 21\" />\n </svg>\n ) : (\n <svg className=\"w-4 h-4\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M15 12a3 3 0 11-6 0 3 3 0 016 0z\" />\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z\" />\n </svg>\n )}\n </button>\n <button\n onClick={() => eventAddon && handleRemoveAddon(eventAddon)}\n className=\"p-2 text-white/60 hover:text-red-400 transition-colors\"\n title=\"Remove\"\n >\n <svg className=\"w-4 h-4\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16\" />\n </svg>\n </button>\n </div>\n </div>\n </div>\n );\n })}\n </div>\n </div>\n ))}\n </div>\n )}\n </div>\n\n {/* Available Add-ons */}\n <div>\n <h3 className=\"text-xl font-semibold text-white mb-4\">Available Add-ons</h3>\n \n {availableAddons.length === 0 ? (\n <div className=\"bg-white/5 border border-white/20 rounded-xl p-8 text-center\">\n <svg className=\"w-12 h-12 text-white/40 mx-auto mb-4\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M20 7l-8-4-8 4m16 0l-8 4m8-4v10l-8 4m0-10L4 7m8 4v10M4 7v10l8 4\" />\n </svg>\n <p className=\"text-white/60\">No add-ons available</p>\n <p className=\"text-white/40 text-sm mt-2\">Create add-ons in your organization settings</p>\n </div>\n ) : (\n <div className=\"space-y-4\">\n {Object.entries(groupedAvailableAddons).map(([category, addons]) => (\n <div key={category} className=\"bg-white/5 border border-white/20 rounded-xl p-6\">\n <div className=\"flex items-center gap-2 mb-4\">\n <div className=\"text-purple-400\">\n {getCategoryIcon(category)}\n </div>\n <h4 className=\"text-lg font-semibold text-white\">{category}</h4>\n </div>\n \n <div className=\"space-y-3\">\n {addons.map((addon) => (\n <div key={addon.id} className=\"flex items-center justify-between p-3 bg-white/5 border border-white/10 rounded-lg\">\n <div className=\"flex-1\">\n <div className=\"text-white font-medium\">{addon.name}</div>\n {addon.description && (\n <div className=\"text-white/60 text-sm mt-1\">{addon.description}</div>\n )}\n </div>\n <div className=\"flex items-center gap-3\">\n <div className=\"text-white font-bold\">{formatCurrency(addon.price_cents)}</div>\n {isAddonAdded(addon) ? (\n <span className=\"px-3 py-1 bg-green-500/20 text-green-300 border border-green-500/30 rounded-lg text-sm\">\n Added\n </span>\n ) : (\n <button\n onClick={() => handleAddAddon(addon)}\n className=\"px-3 py-1 text-white rounded-lg text-sm transition-colors\"\n style={{\n background: 'var(--glass-text-accent)'\n }}\n >\n Add\n </button>\n )}\n </div>\n </div>\n ))}\n </div>\n </div>\n ))}\n </div>\n )}\n </div>\n </div>\n </div>\n );\n}","usedDeprecatedRules":[]},{"filePath":"/home/tyler/apps/bct-whitelabel/src/components/manage/AttendeesTab.tsx","messages":[{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":42,"column":14,"nodeType":null,"messageId":"unusedVar","endLine":42,"endColumn":19},{"ruleId":"no-empty","severity":1,"message":"Empty block statement.","line":42,"column":21,"nodeType":"BlockStatement","messageId":"unexpected","endLine":44,"endColumn":6,"suggestions":[{"messageId":"suggestComment","data":{"type":"block"},"fix":{"range":[1389,1395],"text":" /* empty */ "},"desc":"Add comment inside empty block statement."}]}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":2,"fixableErrorCount":0,"fixableWarningCount":0,"source":"import { useState, useEffect } from 'react';\nimport { loadSalesData, type SalesData } from '../../lib/sales-analytics';\nimport { checkInTicket, refundTicket } from '../../lib/ticket-management';\nimport { formatCurrency } from '../../lib/event-management';\nimport AttendeesTable from '../tables/AttendeesTable';\n\ninterface AttendeeData {\n email: string;\n name: string;\n ticketCount: number;\n totalSpent: number;\n checkedInCount: number;\n tickets: SalesData[];\n}\n\ninterface AttendeesTabProps {\n eventId: string;\n}\n\nexport default function AttendeesTab({ eventId }: AttendeesTabProps) {\n const [orders, setOrders] = useState<SalesData[]>([]);\n const [attendees, setAttendees] = useState<AttendeeData[]>([]);\n const [searchTerm, setSearchTerm] = useState('');\n const [selectedAttendee, setSelectedAttendee] = useState<AttendeeData | null>(null);\n const [showAttendeeDetails, setShowAttendeeDetails] = useState(false);\n const [checkInFilter, setCheckInFilter] = useState<'all' | 'checked_in' | 'not_checked_in'>('all');\n const [loading, setLoading] = useState(true);\n\n useEffect(() => {\n loadData();\n }, [eventId]);\n\n useEffect(() => {\n processAttendees();\n }, [orders, searchTerm, checkInFilter]);\n\n const loadData = async () => {\n setLoading(true);\n try {\n const ordersData = await loadSalesData(eventId);\n setOrders(ordersData);\n } catch (error) {\n\n } finally {\n setLoading(false);\n }\n };\n\n const processAttendees = () => {\n const attendeeMap = new Map<string, AttendeeData>();\n\n orders.forEach(order => {\n const existing = attendeeMap.get(order.purchaser_email) || {\n email: order.purchaser_email,\n name: order.purchaser_name,\n ticketCount: 0,\n totalSpent: 0,\n checkedInCount: 0,\n tickets: []\n };\n\n existing.tickets.push(order);\n if (!order.refund_status || order.refund_status === null) {\n existing.ticketCount += 1;\n existing.totalSpent += order.price;\n if (order.checked_in) {\n existing.checkedInCount += 1;\n }\n }\n\n attendeeMap.set(order.purchaser_email, existing);\n });\n\n let processedAttendees = Array.from(attendeeMap.values());\n\n // Only show attendees with active tickets (ticketCount > 0)\n processedAttendees = processedAttendees.filter(attendee => attendee.ticketCount > 0);\n\n // Apply search filter\n if (searchTerm) {\n const term = searchTerm.toLowerCase();\n processedAttendees = processedAttendees.filter(attendee => \n attendee.name.toLowerCase().includes(term) ||\n attendee.email.toLowerCase().includes(term)\n );\n }\n\n // Apply check-in filter\n if (checkInFilter === 'checked_in') {\n processedAttendees = processedAttendees.filter(attendee => \n attendee.checkedInCount === attendee.ticketCount && attendee.ticketCount > 0\n );\n } else if (checkInFilter === 'not_checked_in') {\n processedAttendees = processedAttendees.filter(attendee => \n attendee.checkedInCount === 0 && attendee.ticketCount > 0\n );\n }\n\n setAttendees(processedAttendees);\n };\n\n const handleViewAttendee = (attendee: AttendeeData) => {\n setSelectedAttendee(attendee);\n setShowAttendeeDetails(true);\n };\n\n const handleCheckInAttendee = async (attendee: AttendeeData) => {\n const unCheckedTickets = attendee.tickets.filter(ticket => \n !ticket.checked_in && (!ticket.refund_status || ticket.refund_status === null)\n );\n\n if (unCheckedTickets.length === 0) return;\n\n const ticket = unCheckedTickets[0];\n const success = await checkInTicket(ticket.id);\n \n if (success) {\n setOrders(prev => prev.map(order => \n order.id === ticket.id ? { ...order, checked_in: true } : order\n ));\n }\n };\n\n const handleRefundAttendee = async (attendee: AttendeeData) => {\n const confirmedTickets = attendee.tickets.filter(ticket => \n (!ticket.refund_status || ticket.refund_status === null)\n );\n\n if (confirmedTickets.length === 0) return;\n\n const confirmMessage = `Are you sure you want to refund all ${confirmedTickets.length} ticket(s) for ${attendee.name}?`;\n \n if (confirm(confirmMessage)) {\n for (const ticket of confirmedTickets) {\n await refundTicket(ticket.id);\n }\n \n setOrders(prev => prev.map(order => \n confirmedTickets.some(t => t.id === order.id) \n ? { ...order, refund_status: 'completed' } \n : order\n ));\n }\n };\n\n const handleBulkCheckIn = async () => {\n const unCheckedTickets = orders.filter(order => \n !order.checked_in && (!order.refund_status || order.refund_status === null)\n );\n\n if (unCheckedTickets.length === 0) {\n alert('No tickets available for check-in');\n return;\n }\n\n const confirmMessage = `Are you sure you want to check in all ${unCheckedTickets.length} remaining tickets?`;\n \n if (confirm(confirmMessage)) {\n for (const ticket of unCheckedTickets) {\n await checkInTicket(ticket.id);\n }\n \n setOrders(prev => prev.map(order => \n unCheckedTickets.some(t => t.id === order.id) \n ? { ...order, checked_in: true } \n : order\n ));\n }\n };\n\n const getAttendeeStats = () => {\n const totalAttendees = attendees.length;\n const totalTickets = attendees.reduce((sum, a) => sum + a.ticketCount, 0);\n const checkedInAttendees = attendees.filter(a => a.checkedInCount > 0).length;\n const fullyCheckedInAttendees = attendees.filter(a => \n a.checkedInCount === a.ticketCount && a.ticketCount > 0\n ).length;\n\n return {\n totalAttendees,\n totalTickets,\n checkedInAttendees,\n fullyCheckedInAttendees\n };\n };\n\n const stats = getAttendeeStats();\n\n if (loading) {\n return (\n <div className=\"flex items-center justify-center py-12\">\n <div className=\"animate-spin rounded-full h-8 w-8 border-b-2 border-white\"></div>\n </div>\n );\n }\n\n return (\n <div className=\"space-y-6\">\n <div className=\"flex justify-between items-center\">\n <h2 className=\"text-2xl font-light text-white\">Attendees & Check-in</h2>\n <div className=\"flex items-center gap-3\">\n <button\n onClick={handleBulkCheckIn}\n className=\"flex items-center gap-2 px-4 py-2 rounded-lg transition-all duration-200 hover:shadow-lg hover:scale-105\"\n style={{\n background: 'var(--success-color)',\n color: 'white'\n }}\n >\n <svg className=\"w-4 h-4\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M5 13l4 4L19 7\" />\n </svg>\n Bulk Check-in\n </button>\n </div>\n </div>\n\n {/* Stats Cards */}\n <div className=\"grid grid-cols-1 md:grid-cols-4 gap-4\">\n <div className=\"bg-white/10 backdrop-blur-xl border border-white/20 rounded-xl p-4\">\n <div className=\"text-sm text-white/60\">Total Attendees</div>\n <div className=\"text-2xl font-bold text-white\">{stats.totalAttendees}</div>\n </div>\n <div className=\"bg-white/10 backdrop-blur-xl border border-white/20 rounded-xl p-4\">\n <div className=\"text-sm text-white/60\">Total Tickets</div>\n <div className=\"text-2xl font-bold text-blue-400\">{stats.totalTickets}</div>\n </div>\n <div className=\"bg-white/10 backdrop-blur-xl border border-white/20 rounded-xl p-4\">\n <div className=\"text-sm text-white/60\">Partially Checked In</div>\n <div className=\"text-2xl font-bold text-yellow-400\">{stats.checkedInAttendees}</div>\n </div>\n <div className=\"bg-white/10 backdrop-blur-xl border border-white/20 rounded-xl p-4\">\n <div className=\"text-sm text-white/60\">Fully Checked In</div>\n <div className=\"text-2xl font-bold text-green-400\">{stats.fullyCheckedInAttendees}</div>\n </div>\n </div>\n\n {/* Filters */}\n <div className=\"bg-white/5 border border-white/20 rounded-xl p-6\">\n <h3 className=\"text-lg font-semibold text-white mb-4\">Filters</h3>\n <div className=\"grid grid-cols-1 md:grid-cols-2 gap-4\">\n <div>\n <label className=\"block text-sm text-white/80 mb-2\">Search Attendees</label>\n <input\n type=\"text\"\n value={searchTerm}\n onChange={(e) => setSearchTerm(e.target.value)}\n placeholder=\"Search by name or email...\"\n className=\"w-full px-3 py-2 bg-white/10 border border-white/20 rounded-lg text-white placeholder-white/50 focus:ring-2 focus:ring-blue-500 focus:border-transparent\"\n />\n </div>\n <div>\n <label className=\"block text-sm text-white/80 mb-2\">Check-in Status</label>\n <select\n value={checkInFilter}\n onChange={(e) => setCheckInFilter(e.target.value as typeof checkInFilter)}\n className=\"w-full px-3 py-2 bg-white/10 border border-white/20 rounded-lg text-white focus:ring-2 focus:ring-blue-500 focus:border-transparent\"\n >\n <option value=\"all\">All Attendees</option>\n <option value=\"checked_in\">Fully Checked In</option>\n <option value=\"not_checked_in\">Not Checked In</option>\n </select>\n </div>\n </div>\n </div>\n\n {/* Attendees Table */}\n <div className=\"bg-white/5 border border-white/20 rounded-xl p-6\">\n <AttendeesTable\n orders={orders}\n onViewAttendee={handleViewAttendee}\n onCheckInAttendee={handleCheckInAttendee}\n onRefundAttendee={handleRefundAttendee}\n showActions={true}\n />\n </div>\n\n {/* Attendee Details Modal */}\n {showAttendeeDetails && selectedAttendee && (\n <div className=\"fixed inset-0 bg-black/50 backdrop-blur-sm flex items-center justify-center p-4 z-50\">\n <div className=\"bg-white/10 backdrop-blur-xl border border-white/20 rounded-2xl shadow-2xl max-w-4xl w-full max-h-[90vh] overflow-y-auto\">\n <div className=\"p-6\">\n <div className=\"flex justify-between items-center mb-6\">\n <h3 className=\"text-2xl font-light text-white\">Attendee Details</h3>\n <button\n onClick={() => setShowAttendeeDetails(false)}\n className=\"text-white/60 hover:text-white transition-colors\"\n >\n <svg className=\"w-6 h-6\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M6 18L18 6M6 6l12 12\" />\n </svg>\n </button>\n </div>\n\n <div className=\"space-y-6\">\n <div className=\"grid grid-cols-1 md:grid-cols-2 gap-6\">\n <div>\n <h4 className=\"text-lg font-semibold text-white mb-3\">Contact Information</h4>\n <div className=\"space-y-2\">\n <div>\n <span className=\"text-white/60 text-sm\">Name:</span>\n <div className=\"text-white font-medium\">{selectedAttendee.name}</div>\n </div>\n <div>\n <span className=\"text-white/60 text-sm\">Email:</span>\n <div className=\"text-white\">{selectedAttendee.email}</div>\n </div>\n </div>\n </div>\n\n <div>\n <h4 className=\"text-lg font-semibold text-white mb-3\">Summary</h4>\n <div className=\"space-y-2\">\n <div>\n <span className=\"text-white/60 text-sm\">Total Tickets:</span>\n <div className=\"text-white font-medium\">{selectedAttendee.ticketCount}</div>\n </div>\n <div>\n <span className=\"text-white/60 text-sm\">Total Spent:</span>\n <div className=\"text-white font-bold\">{formatCurrency(selectedAttendee.totalSpent)}</div>\n </div>\n <div>\n <span className=\"text-white/60 text-sm\">Checked In:</span>\n <div className=\"text-white font-medium\">\n {selectedAttendee.checkedInCount} / {selectedAttendee.ticketCount}\n </div>\n </div>\n </div>\n </div>\n </div>\n\n <div>\n <h4 className=\"text-lg font-semibold text-white mb-3\">Tickets</h4>\n <div className=\"space-y-3\">\n {selectedAttendee.tickets.map((ticket) => (\n <div key={ticket.id} className=\"bg-white/5 border border-white/20 rounded-lg p-4\">\n <div className=\"flex justify-between items-start\">\n <div>\n <div className=\"font-medium text-white\">{ticket.ticket_types.name}</div>\n <div className=\"text-white/60 text-sm\">\n Purchased: {new Date(ticket.created_at).toLocaleDateString()}\n </div>\n <div className=\"text-white/60 text-sm font-mono\">\n ID: {ticket.uuid}\n </div>\n </div>\n <div className=\"text-right\">\n <div className=\"text-white font-bold\">{formatCurrency(ticket.price)}</div>\n <div className=\"flex items-center gap-2 mt-1\">\n <span className={`px-2 py-1 text-xs rounded-full ${\n (!ticket.refund_status || ticket.refund_status === null) ? 'status-pill status-success' :\n ticket.refund_status === 'completed' ? 'status-pill status-error' :\n 'status-pill status-warning'\n }`}>\n {(!ticket.refund_status || ticket.refund_status === null) ? 'confirmed' :\n ticket.refund_status === 'completed' ? 'refunded' :\n ticket.refund_status === 'pending' ? 'pending' : 'failed'}\n </span>\n {ticket.checked_in ? (\n <span className=\"status-pill status-success\">\n Checked In\n </span>\n ) : (\n <span className=\"px-2 py-1 text-xs bg-white/20 text-white/60 border border-white/30 rounded-full\">\n Not Checked In\n </span>\n )}\n </div>\n </div>\n </div>\n </div>\n ))}\n </div>\n </div>\n\n <div className=\"flex justify-between items-center\">\n <button\n onClick={() => setShowAttendeeDetails(false)}\n className=\"px-6 py-3 text-white/80 hover:text-white transition-colors\"\n >\n Close\n </button>\n <div className=\"flex items-center gap-3\">\n {selectedAttendee.checkedInCount < selectedAttendee.ticketCount && (\n <button\n onClick={() => {\n handleCheckInAttendee(selectedAttendee);\n setShowAttendeeDetails(false);\n }}\n className=\"px-6 py-3 text-white rounded-lg transition-colors\"\n style={{\n background: 'var(--success-color)'\n }}\n >\n Check In\n </button>\n )}\n {selectedAttendee.ticketCount > 0 && (\n <button\n onClick={() => {\n handleRefundAttendee(selectedAttendee);\n setShowAttendeeDetails(false);\n }}\n className=\"px-6 py-3 text-white rounded-lg transition-colors\"\n style={{\n background: 'var(--error-color)'\n }}\n >\n Refund All\n </button>\n )}\n </div>\n </div>\n </div>\n </div>\n </div>\n </div>\n )}\n </div>\n );\n}","usedDeprecatedRules":[]},{"filePath":"/home/tyler/apps/bct-whitelabel/src/components/manage/CustomPageTab.tsx","messages":[{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'selectedTemplateId' is assigned a value but never used. Allowed unused vars must match /^_/u.","line":45,"column":10,"nodeType":null,"messageId":"unusedVar","endLine":45,"endColumn":28},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'setSelectedTemplateId' is assigned a value but never used. Allowed unused vars must match /^_/u.","line":45,"column":30,"nodeType":null,"messageId":"unusedVar","endLine":45,"endColumn":51},{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":229,"column":43,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":229,"endColumn":46,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[7056,7059],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[7056,7059],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":3,"fixableErrorCount":0,"fixableWarningCount":0,"source":"import React, { useState, useEffect } from 'react';\nimport PageBuilder from '../PageBuilder';\nimport TemplateManager from '../TemplateManager';\nimport { supabase } from '../../lib/supabase';\n\ninterface CustomPageTabProps {\n eventId: string;\n organizationId: string;\n eventSlug?: string | null;\n}\n\ninterface Template {\n id: string;\n name: string;\n description: string;\n preview_image_url?: string;\n created_at: string;\n updated_at: string;\n is_active: boolean;\n}\n\ninterface CustomPage {\n id: string;\n custom_slug: string;\n is_active: boolean;\n is_default: boolean;\n template_id?: string;\n meta_title?: string;\n meta_description?: string;\n view_count: number;\n template?: Template;\n}\n\nconst CustomPageTab: React.FC<CustomPageTabProps> = ({\n eventId,\n organizationId,\n eventSlug\n}) => {\n const [customPages, setCustomPages] = useState<CustomPage[]>([]);\n const [templates, setTemplates] = useState<Template[]>([]);\n const [loading, setLoading] = useState(true);\n const [showPageBuilder, setShowPageBuilder] = useState(false);\n const [showTemplateManager, setShowTemplateManager] = useState(false);\n const [selectedPageId, setSelectedPageId] = useState<string | null>(null);\n const [selectedTemplateId, setSelectedTemplateId] = useState<string | null>(null);\n const [showCreateModal, setShowCreateModal] = useState(false);\n const [newPageData, setNewPageData] = useState({\n custom_slug: '',\n meta_title: '',\n meta_description: '',\n template_id: ''\n });\n\n useEffect(() => {\n loadData();\n }, [eventId, organizationId]);\n\n const loadData = async () => {\n try {\n console.log('Loading data for:', { organizationId, eventId });\n \n // Get auth token\n const { data: { session } } = await supabase.auth.getSession();\n console.log('Session state:', { hasSession: !!session, hasToken: !!session?.access_token });\n \n const headers: Record<string, string> = {};\n if (session?.access_token) {\n headers['Authorization'] = `Bearer ${session.access_token}`;\n }\n\n console.log('Making API calls...');\n const [pagesRes, templatesRes] = await Promise.all([\n fetch(`/api/custom-pages?organization_id=${organizationId}&event_id=${eventId}`, { headers }),\n fetch(`/api/templates?organization_id=${organizationId}`, { headers })\n ]);\n\n const pagesData = await pagesRes.json();\n const templatesData = await templatesRes.json();\n\n console.log('Pages response:', { status: pagesRes.status, data: pagesData });\n console.log('Templates response:', { status: templatesRes.status, data: templatesData });\n\n if (pagesData.success) {\n setCustomPages(pagesData.pages || []);\n } else {\n console.error('Failed to load pages:', pagesData);\n setCustomPages([]);\n }\n \n if (templatesData.success) {\n setTemplates(templatesData.templates || []);\n } else {\n console.error('Failed to load templates:', templatesData);\n setTemplates([]);\n }\n } catch (error) {\n console.error('Error in loadData:', error);\n setCustomPages([]);\n setTemplates([]);\n } finally {\n setLoading(false);\n }\n };\n\n const handleCreateCustomPage = async () => {\n if (!newPageData.custom_slug.trim()) {\n alert('Please enter a custom URL slug');\n return;\n }\n\n try {\n // Get auth token\n const { data: { session } } = await supabase.auth.getSession();\n if (!session?.access_token) {\n alert('Please log in to create custom pages');\n return;\n }\n\n const requestBody = {\n organization_id: organizationId,\n event_id: eventId,\n template_id: newPageData.template_id || null,\n custom_slug: newPageData.custom_slug.toLowerCase().replace(/[^a-z0-9-]/g, '-'),\n meta_title: newPageData.meta_title,\n meta_description: newPageData.meta_description,\n created_by: session.user.id\n };\n \n console.log('Sending request body:', requestBody);\n console.log('JSON string:', JSON.stringify(requestBody));\n \n const response = await fetch('/api/custom-pages', {\n method: 'POST',\n headers: { \n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${session.access_token}`\n },\n body: JSON.stringify(requestBody)\n });\n\n const data = await response.json();\n console.log('API Response:', { status: response.status, data });\n \n if (data.success) {\n setShowCreateModal(false);\n setNewPageData({\n custom_slug: '',\n meta_title: '',\n meta_description: '',\n template_id: ''\n });\n // Reload data to show the new page\n await loadData();\n } else {\n const errorMessage = data.details ? `${data.error}: ${data.details}` : data.error || 'Failed to create custom page';\n alert(errorMessage);\n }\n } catch (error) {\n console.error('Error creating custom page:', error);\n alert('Network error: Unable to create custom page. Please try again.');\n }\n };\n\n const handleEditPage = (pageId: string) => {\n setSelectedPageId(pageId);\n setShowPageBuilder(true);\n };\n\n const handleDeletePage = async (pageId: string) => {\n if (!confirm('Are you sure you want to delete this custom page?')) return;\n\n try {\n // Get auth token\n const { data: { session } } = await supabase.auth.getSession();\n if (!session?.access_token) {\n alert('Please log in to delete custom pages');\n return;\n }\n\n const response = await fetch(`/api/custom-pages/${pageId}`, {\n method: 'DELETE',\n headers: {\n 'Authorization': `Bearer ${session.access_token}`\n }\n });\n\n if (response.ok) {\n await loadData(); // Reload data\n alert('Page deleted successfully');\n } else {\n alert('Failed to delete page');\n }\n } catch (error) {\n console.error('Error deleting page:', error);\n alert('Error deleting page');\n }\n };\n\n const handleTogglePageStatus = async (pageId: string, isActive: boolean) => {\n try {\n // Get auth token\n const { data: { session } } = await supabase.auth.getSession();\n if (!session?.access_token) {\n alert('Please log in to update page status');\n return;\n }\n\n const response = await fetch(`/api/custom-pages/${pageId}`, {\n method: 'PUT',\n headers: { \n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${session.access_token}`\n },\n body: JSON.stringify({ is_active: isActive })\n });\n\n if (response.ok) {\n await loadData(); // Reload data\n alert(`Page ${isActive ? 'activated' : 'deactivated'} successfully`);\n } else {\n alert('Failed to update page status');\n }\n } catch (error) {\n console.error('Error updating page status:', error);\n alert('Error updating page status');\n }\n };\n\n const handleSavePage = async (pageData: any) => {\n if (!selectedPageId) return;\n\n try {\n // Get auth token\n const { data: { session } } = await supabase.auth.getSession();\n if (!session?.access_token) {\n alert('Please log in to save page changes');\n return;\n }\n\n const response = await fetch(`/api/custom-pages/${selectedPageId}`, {\n method: 'PUT',\n headers: { \n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${session.access_token}`\n },\n body: JSON.stringify({\n page_data: pageData,\n updated_at: new Date().toISOString()\n })\n });\n\n if (response.ok) {\n alert('Page saved successfully!');\n await loadData();\n } else {\n const errorData = await response.json();\n alert(`Failed to save page: ${errorData.error || 'Unknown error'}`);\n }\n } catch (error) {\n console.error('Error saving page:', error);\n alert('Error saving page changes');\n }\n };\n\n const handleTemplateSelect = (template: Template) => {\n setNewPageData({\n ...newPageData,\n template_id: template.id,\n meta_title: `${eventSlug} - ${template.name}`,\n meta_description: template.description\n });\n setShowTemplateManager(false);\n };\n\n if (loading) {\n return (\n <div className=\"flex items-center justify-center py-12\">\n <div className=\"animate-spin rounded-full h-8 w-8 border-b-2 border-white\"></div>\n </div>\n );\n }\n\n if (showPageBuilder) {\n return (\n <div className=\"fixed inset-0 z-50 bg-white\">\n <div className=\"h-full flex flex-col\">\n <div className=\"bg-white border-b border-gray-200 p-4 flex items-center justify-between\">\n <div className=\"flex items-center space-x-4\">\n <button\n onClick={() => setShowPageBuilder(false)}\n className=\"flex items-center px-3 py-2 text-sm text-gray-600 hover:text-gray-900 border border-gray-300 rounded-md hover:bg-gray-50\"\n >\n ← Back to Custom Pages\n </button>\n <h1 className=\"text-lg font-semibold\">\n Editing Custom Page\n </h1>\n </div>\n </div>\n \n <div className=\"flex-1 overflow-hidden\">\n <PageBuilder\n eventId={eventId}\n pageId={selectedPageId!}\n onSave={handleSavePage}\n onPreview={(pageData) => {\n console.log('Preview data:', pageData);\n }}\n />\n </div>\n </div>\n </div>\n );\n }\n\n\n if (showTemplateManager) {\n return (\n <div className=\"h-screen -m-6\">\n <div className=\"bg-white border-b border-gray-200 p-4 flex items-center justify-between\">\n <div className=\"flex items-center space-x-4\">\n <button\n onClick={() => setShowTemplateManager(false)}\n className=\"flex items-center px-3 py-2 text-sm text-gray-600 hover:text-gray-900 border border-gray-300 rounded-md hover:bg-gray-50\"\n >\n ← Back to Custom Pages\n </button>\n <h1 className=\"text-lg font-semibold\">Select Template</h1>\n </div>\n </div>\n \n <TemplateManager\n organizationId={organizationId}\n onTemplateSelect={handleTemplateSelect}\n />\n </div>\n );\n }\n\n return (\n <div className=\"space-y-6\">\n <div className=\"flex items-center justify-between\">\n <div>\n <h2 className=\"text-xl font-semibold text-white mb-2\">Custom Sales Pages</h2>\n <p className=\"text-white/60\">Create custom branded pages for your event with unique URLs</p>\n </div>\n <button\n onClick={() => setShowCreateModal(true)}\n className=\"px-4 py-2 text-white rounded-md transition-colors\"\n style={{\n background: 'var(--glass-text-accent)'\n }}\n >\n Create Custom Page\n </button>\n </div>\n\n {/* Default Event Page Info */}\n <div className=\"bg-white/5 border border-white/20 rounded-xl p-4\">\n <div className=\"flex items-center justify-between\">\n <div>\n <h3 className=\"font-semibold text-white mb-1\">Default Event Page</h3>\n <p className=\"text-white/60 text-sm\">Standard event page accessible to everyone</p>\n <p className=\"text-blue-400 text-sm mt-1\">\n blackcanyontickets.com/e/{eventSlug}\n </p>\n </div>\n <div className=\"flex items-center space-x-2\">\n <span className=\"inline-flex px-2 py-1 text-xs font-semibold rounded-full bg-green-500/20 text-green-400\">\n Active\n </span>\n </div>\n </div>\n </div>\n\n {/* Custom Pages List */}\n <div className=\"space-y-4\">\n {customPages.length === 0 ? (\n <div className=\"text-center py-8 border-2 border-dashed border-white/20 rounded-xl\">\n <div className=\"text-4xl mb-4\">🎨</div>\n <h3 className=\"text-lg font-medium text-white mb-2\">No Custom Pages Yet</h3>\n <p className=\"text-white/60 mb-4\">Create custom branded pages with unique URLs for better marketing</p>\n <button\n onClick={() => setShowCreateModal(true)}\n className=\"px-4 py-2 text-white rounded-md transition-colors\"\n style={{\n background: 'var(--glass-text-accent)'\n }}\n >\n Create Your First Custom Page\n </button>\n </div>\n ) : (\n customPages.map((page) => (\n <div\n key={page.id}\n className=\"bg-white/5 border border-white/20 rounded-xl p-4\"\n >\n <div className=\"flex items-center justify-between\">\n <div className=\"flex-1\">\n <div className=\"flex items-center space-x-3 mb-2\">\n <h3 className=\"font-semibold text-white\">\n {page.meta_title || `Custom Page - ${page.custom_slug}`}\n </h3>\n <span className={`inline-flex px-2 py-1 text-xs font-semibold rounded-full ${\n page.is_active \n ? 'bg-green-500/20 text-green-400' \n : 'bg-gray-500/20 text-gray-400'\n }`}>\n {page.is_active ? 'Active' : 'Inactive'}\n </span>\n {page.template && (\n <span className=\"inline-flex px-2 py-1 text-xs font-semibold rounded-full bg-blue-500/20 text-blue-400\">\n Template: {page.template.name}\n </span>\n )}\n </div>\n <p className=\"text-blue-400 text-sm mb-1\">\n blackcanyontickets.com/{page.custom_slug}\n </p>\n {page.meta_description && (\n <p className=\"text-white/60 text-sm\">{page.meta_description}</p>\n )}\n <div className=\"flex items-center space-x-4 mt-2 text-xs text-white/40\">\n <span>{page.view_count} views</span>\n <span>Created {new Date(page.created_at).toLocaleDateString()}</span>\n </div>\n </div>\n \n <div className=\"flex items-center space-x-2\">\n <button\n onClick={() => window.open(`/${page.custom_slug}`, '_blank')}\n className=\"px-3 py-1 text-xs text-blue-400 hover:text-blue-300 border border-blue-400/50 rounded hover:bg-blue-400/10\"\n >\n View\n </button>\n <button\n onClick={() => handleEditPage(page.id)}\n className=\"px-3 py-1 text-xs text-white hover:text-gray-200 border border-white/50 rounded hover:bg-white/10\"\n >\n Edit\n </button>\n <button\n onClick={() => handleTogglePageStatus(page.id, !page.is_active)}\n className={`px-3 py-1 text-xs border rounded ${\n page.is_active\n ? 'text-yellow-400 border-yellow-400/50 hover:bg-yellow-400/10'\n : 'text-green-400 border-green-400/50 hover:bg-green-400/10'\n }`}\n >\n {page.is_active ? 'Deactivate' : 'Activate'}\n </button>\n <button\n onClick={() => handleDeletePage(page.id)}\n className=\"px-3 py-1 text-xs text-red-400 hover:text-red-300 border border-red-400/50 rounded hover:bg-red-400/10\"\n >\n Delete\n </button>\n </div>\n </div>\n </div>\n ))\n )}\n </div>\n\n\n {/* Create Custom Page Modal */}\n {showCreateModal && (\n <div className=\"fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center p-4 z-50\">\n <div className=\"bg-white rounded-lg max-w-2xl w-full p-6\">\n <h2 className=\"text-xl font-bold mb-4 text-gray-900\">Create Custom Page</h2>\n \n <div className=\"space-y-4\">\n <div>\n <label className=\"block text-sm font-medium text-gray-700 mb-1\">\n Custom URL Slug\n </label>\n <div className=\"flex\">\n <span className=\"inline-flex items-center px-3 py-2 border border-gray-300 border-r-0 rounded-l-md bg-gray-50 text-gray-500 text-sm\">\n blackcanyontickets.com/\n </span>\n <input\n type=\"text\"\n value={newPageData.custom_slug}\n onChange={(e) => setNewPageData({ \n ...newPageData, \n custom_slug: e.target.value.toLowerCase().replace(/[^a-z0-9-]/g, '-')\n })}\n className=\"flex-1 px-3 py-2 border border-gray-300 rounded-r-md focus:outline-none focus:ring-2 text-gray-900\"\n style={{\n '--tw-ring-color': 'var(--glass-text-accent)'\n } as React.CSSProperties}\n placeholder=\"festival2024\"\n />\n </div>\n <p className=\"text-xs text-gray-500 mt-1\">Use lowercase letters, numbers, and hyphens only</p>\n </div>\n \n <div>\n <label className=\"block text-sm font-medium text-gray-700 mb-1\">\n Page Title (for SEO)\n </label>\n <input\n type=\"text\"\n value={newPageData.meta_title}\n onChange={(e) => setNewPageData({ ...newPageData, meta_title: e.target.value })}\n className=\"w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 text-gray-900\"\n style={{\n '--tw-ring-color': 'var(--glass-text-accent)'\n } as React.CSSProperties}\n placeholder=\"Amazing Festival 2024 - Get Your Tickets\"\n />\n </div>\n \n <div>\n <label className=\"block text-sm font-medium text-gray-700 mb-1\">\n Meta Description\n </label>\n <textarea\n value={newPageData.meta_description}\n onChange={(e) => setNewPageData({ ...newPageData, meta_description: e.target.value })}\n className=\"w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 text-gray-900\"\n style={{\n '--tw-ring-color': 'var(--glass-text-accent)'\n } as React.CSSProperties}\n rows={3}\n placeholder=\"Join us for an unforgettable festival experience...\"\n />\n </div>\n \n <div>\n <label className=\"block text-sm font-medium text-gray-700 mb-1\">\n Template (Optional)\n </label>\n <div className=\"flex space-x-2\">\n <select\n value={newPageData.template_id}\n onChange={(e) => setNewPageData({ ...newPageData, template_id: e.target.value })}\n className=\"flex-1 px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 text-gray-900\"\n style={{\n '--tw-ring-color': 'var(--glass-text-accent)'\n } as React.CSSProperties}\n >\n <option value=\"\">No template (start from scratch)</option>\n {templates.map(template => (\n <option key={template.id} value={template.id}>\n {template.name}\n </option>\n ))}\n </select>\n <button\n onClick={() => setShowTemplateManager(true)}\n className=\"px-4 py-2 rounded-md border\"\n style={{\n color: 'var(--glass-text-accent)',\n borderColor: 'var(--glass-text-accent)'\n }}\n >\n Browse Templates\n </button>\n </div>\n </div>\n </div>\n \n <div className=\"flex justify-end space-x-2 mt-6\">\n <button\n onClick={() => setShowCreateModal(false)}\n className=\"px-4 py-2 text-gray-600 hover:text-gray-800 border border-gray-300 rounded-md hover:bg-gray-50\"\n >\n Cancel\n </button>\n <button\n onClick={handleCreateCustomPage}\n className=\"px-4 py-2 text-white rounded-md transition-colors\"\n style={{\n background: 'var(--glass-text-accent)'\n }}\n >\n Create Page\n </button>\n </div>\n </div>\n </div>\n )}\n </div>\n );\n};\n\nexport default CustomPageTab;","usedDeprecatedRules":[]},{"filePath":"/home/tyler/apps/bct-whitelabel/src/components/manage/DiscountTab.tsx","messages":[{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":30,"column":50,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":30,"endColumn":53,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[819,822],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[819,822],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":70,"column":14,"nodeType":null,"messageId":"unusedVar","endLine":70,"endColumn":19},{"ruleId":"no-empty","severity":1,"message":"Empty block statement.","line":70,"column":21,"nodeType":"BlockStatement","messageId":"unexpected","endLine":72,"endColumn":6,"suggestions":[{"messageId":"suggestComment","data":{"type":"block"},"fix":{"range":[2058,2064],"text":" /* empty */ "},"desc":"Add comment inside empty block statement."}]},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":145,"column":14,"nodeType":null,"messageId":"unusedVar","endLine":145,"endColumn":19},{"ruleId":"no-empty","severity":1,"message":"Empty block statement.","line":145,"column":21,"nodeType":"BlockStatement","messageId":"unexpected","endLine":147,"endColumn":6,"suggestions":[{"messageId":"suggestComment","data":{"type":"block"},"fix":{"range":[3982,3988],"text":" /* empty */ "},"desc":"Add comment inside empty block statement."}]},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":162,"column":16,"nodeType":null,"messageId":"unusedVar","endLine":162,"endColumn":21},{"ruleId":"no-empty","severity":1,"message":"Empty block statement.","line":162,"column":23,"nodeType":"BlockStatement","messageId":"unexpected","endLine":164,"endColumn":8,"suggestions":[{"messageId":"suggestComment","data":{"type":"block"},"fix":{"range":[4396,4404],"text":" /* empty */ "},"desc":"Add comment inside empty block statement."}]},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":177,"column":14,"nodeType":null,"messageId":"unusedVar","endLine":177,"endColumn":19},{"ruleId":"no-empty","severity":1,"message":"Empty block statement.","line":177,"column":21,"nodeType":"BlockStatement","messageId":"unexpected","endLine":179,"endColumn":6,"suggestions":[{"messageId":"suggestComment","data":{"type":"block"},"fix":{"range":[4704,4710],"text":" /* empty */ "},"desc":"Add comment inside empty block statement."}]}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":9,"fixableErrorCount":0,"fixableWarningCount":0,"source":"import { useState, useEffect } from 'react';\nimport { createClient } from '@supabase/supabase-js';\nimport { formatCurrency } from '../../lib/event-management';\n\nconst supabase = createClient(\n import.meta.env.PUBLIC_SUPABASE_URL,\n import.meta.env.PUBLIC_SUPABASE_ANON_KEY\n);\n\ninterface DiscountCode {\n id: string;\n code: string;\n discount_type: 'percentage' | 'fixed';\n discount_value: number;\n minimum_purchase: number;\n max_uses: number;\n uses_count: number;\n expires_at: string;\n is_active: boolean;\n created_at: string;\n applicable_ticket_types: string[];\n}\n\ninterface DiscountTabProps {\n eventId: string;\n}\n\nexport default function DiscountTab({ eventId }: DiscountTabProps) {\n const [discountCodes, setDiscountCodes] = useState<DiscountCode[]>([]);\n const [ticketTypes, setTicketTypes] = useState<any[]>([]);\n const [showModal, setShowModal] = useState(false);\n const [editingCode, setEditingCode] = useState<DiscountCode | null>(null);\n const [formData, setFormData] = useState({\n code: '',\n discount_type: 'percentage' as 'percentage' | 'fixed',\n discount_value: 10,\n minimum_purchase: 0,\n max_uses: 100,\n expires_at: '',\n applicable_ticket_types: [] as string[]\n });\n const [loading, setLoading] = useState(true);\n const [saving, setSaving] = useState(false);\n\n useEffect(() => {\n loadData();\n }, [eventId]);\n\n const loadData = async () => {\n setLoading(true);\n try {\n const [discountData, ticketTypesData] = await Promise.all([\n supabase\n .from('discount_codes')\n .select('*')\n .eq('event_id', eventId)\n .order('created_at', { ascending: false }),\n supabase\n .from('ticket_types')\n .select('id, name')\n .eq('event_id', eventId)\n .eq('is_active', true)\n ]);\n\n if (discountData.error) throw discountData.error;\n if (ticketTypesData.error) throw ticketTypesData.error;\n\n setDiscountCodes(discountData.data || []);\n setTicketTypes(ticketTypesData.data || []);\n } catch (error) {\n\n } finally {\n setLoading(false);\n }\n };\n\n const generateCode = () => {\n const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';\n let result = '';\n for (let i = 0; i < 8; i++) {\n result += chars.charAt(Math.floor(Math.random() * chars.length));\n }\n return result;\n };\n\n const handleCreateCode = () => {\n setEditingCode(null);\n setFormData({\n code: generateCode(),\n discount_type: 'percentage',\n discount_value: 10,\n minimum_purchase: 0,\n max_uses: 100,\n expires_at: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000).toISOString().split('T')[0],\n applicable_ticket_types: []\n });\n setShowModal(true);\n };\n\n const handleEditCode = (code: DiscountCode) => {\n setEditingCode(code);\n setFormData({\n code: code.code,\n discount_type: code.discount_type,\n discount_value: code.discount_value,\n minimum_purchase: code.minimum_purchase,\n max_uses: code.max_uses,\n expires_at: code.expires_at.split('T')[0],\n applicable_ticket_types: code.applicable_ticket_types || []\n });\n setShowModal(true);\n };\n\n const handleSaveCode = async () => {\n setSaving(true);\n try {\n const codeData = {\n ...formData,\n event_id: eventId,\n expires_at: new Date(formData.expires_at).toISOString(),\n minimum_purchase: formData.minimum_purchase * 100 // Convert to cents\n };\n\n if (editingCode) {\n const { error } = await supabase\n .from('discount_codes')\n .update(codeData)\n .eq('id', editingCode.id);\n\n if (error) throw error;\n } else {\n const { error } = await supabase\n .from('discount_codes')\n .insert({\n ...codeData,\n is_active: true,\n uses_count: 0\n });\n\n if (error) throw error;\n }\n\n setShowModal(false);\n loadData();\n } catch (error) {\n\n } finally {\n setSaving(false);\n }\n };\n\n const handleDeleteCode = async (code: DiscountCode) => {\n if (confirm(`Are you sure you want to delete the discount code \"${code.code}\"?`)) {\n try {\n const { error } = await supabase\n .from('discount_codes')\n .delete()\n .eq('id', code.id);\n\n if (error) throw error;\n loadData();\n } catch (error) {\n\n }\n }\n };\n\n const handleToggleCode = async (code: DiscountCode) => {\n try {\n const { error } = await supabase\n .from('discount_codes')\n .update({ is_active: !code.is_active })\n .eq('id', code.id);\n\n if (error) throw error;\n loadData();\n } catch (error) {\n\n }\n };\n\n const formatDiscount = (type: string, value: number) => {\n return type === 'percentage' ? `${value}%` : formatCurrency(value * 100);\n };\n\n const isExpired = (expiresAt: string) => {\n return new Date(expiresAt) < new Date();\n };\n\n const getApplicableTicketNames = (ticketTypeIds: string[]) => {\n if (!ticketTypeIds || ticketTypeIds.length === 0) return 'All ticket types';\n return ticketTypes\n .filter(type => ticketTypeIds.includes(type.id))\n .map(type => type.name)\n .join(', ');\n };\n\n const handleTicketTypeChange = (ticketTypeId: string, checked: boolean) => {\n if (checked) {\n setFormData(prev => ({\n ...prev,\n applicable_ticket_types: [...prev.applicable_ticket_types, ticketTypeId]\n }));\n } else {\n setFormData(prev => ({\n ...prev,\n applicable_ticket_types: prev.applicable_ticket_types.filter(id => id !== ticketTypeId)\n }));\n }\n };\n\n if (loading) {\n return (\n <div className=\"flex items-center justify-center py-12\">\n <div className=\"animate-spin rounded-full h-8 w-8 border-b-2 border-white\"></div>\n </div>\n );\n }\n\n return (\n <div className=\"space-y-6\">\n <div className=\"flex justify-between items-center\">\n <h2 className=\"text-2xl font-light text-white\">Discount Codes</h2>\n <button\n onClick={handleCreateCode}\n className=\"flex items-center gap-2 px-4 py-2 rounded-lg font-medium transition-all duration-200\"\n style={{\n background: 'var(--glass-text-accent)',\n color: 'white'\n }}\n >\n <svg className=\"w-4 h-4\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M12 6v6m0 0v6m0-6h6m-6 0H6\" />\n </svg>\n Create Discount Code\n </button>\n </div>\n\n {discountCodes.length === 0 ? (\n <div className=\"text-center py-12\">\n <svg className=\"w-12 h-12 text-white/40 mx-auto mb-4\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M9 14l6-6m-5.5.5h.01m4.99 5h.01M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16l4-2 4 2 4-2 4 2z\" />\n </svg>\n <p className=\"text-white/60 mb-4\">No discount codes created yet</p>\n <button\n onClick={handleCreateCode}\n className=\"px-6 py-3 rounded-lg font-medium transition-all duration-200\"\n style={{\n background: 'var(--glass-text-accent)',\n color: 'white'\n }}\n >\n Create Your First Discount Code\n </button>\n </div>\n ) : (\n <div className=\"grid grid-cols-1 lg:grid-cols-2 gap-6\">\n {discountCodes.map((code) => (\n <div key={code.id} className=\"bg-white/5 border border-white/20 rounded-xl p-6 hover:bg-white/10 transition-all duration-200\">\n <div className=\"flex justify-between items-start mb-4\">\n <div className=\"flex-1\">\n <div className=\"flex items-center gap-3 mb-2\">\n <div className=\"text-2xl font-bold text-white font-mono\">{code.code}</div>\n <div className=\"flex items-center gap-2\">\n <span className={`px-2 py-1 text-xs rounded-full ${\n code.is_active \n ? 'bg-green-500/20 text-green-300 border border-green-500/30' \n : 'bg-red-500/20 text-red-300 border border-red-500/30'\n }`}>\n {code.is_active ? 'Active' : 'Inactive'}\n </span>\n {isExpired(code.expires_at) && (\n <span className=\"px-2 py-1 text-xs bg-red-500/20 text-red-300 border border-red-500/30 rounded-full\">\n Expired\n </span>\n )}\n </div>\n </div>\n <div className=\"text-3xl font-bold text-orange-400 mb-2\">\n {formatDiscount(code.discount_type, code.discount_value)} OFF\n </div>\n {code.minimum_purchase > 0 && (\n <div className=\"text-sm text-white/60 mb-2\">\n Minimum purchase: {formatCurrency(code.minimum_purchase)}\n </div>\n )}\n <div className=\"text-sm text-white/60\">\n Applies to: {getApplicableTicketNames(code.applicable_ticket_types)}\n </div>\n </div>\n <div className=\"flex items-center gap-2\">\n <button\n onClick={() => handleEditCode(code)}\n className=\"p-2 text-white/60 hover:text-white transition-colors\"\n title=\"Edit\"\n >\n <svg className=\"w-4 h-4\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z\" />\n </svg>\n </button>\n <button\n onClick={() => handleToggleCode(code)}\n className=\"p-2 text-white/60 hover:text-white transition-colors\"\n title={code.is_active ? 'Deactivate' : 'Activate'}\n >\n {code.is_active ? (\n <svg className=\"w-4 h-4\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M13.875 18.825A10.05 10.05 0 0112 19c-4.478 0-8.268-2.943-9.543-7a9.97 9.97 0 011.563-3.029m5.858.908a3 3 0 114.142 4.142M9.878 9.878l4.242 4.242M9.878 9.878L3 3m6.878 6.878L21 21\" />\n </svg>\n ) : (\n <svg className=\"w-4 h-4\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M15 12a3 3 0 11-6 0 3 3 0 016 0z\" />\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z\" />\n </svg>\n )}\n </button>\n <button\n onClick={() => handleDeleteCode(code)}\n className=\"p-2 text-white/60 hover:text-red-400 transition-colors\"\n title=\"Delete\"\n >\n <svg className=\"w-4 h-4\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16\" />\n </svg>\n </button>\n </div>\n </div>\n\n <div className=\"space-y-3\">\n <div className=\"grid grid-cols-2 gap-4 text-sm\">\n <div>\n <div className=\"text-white/60\">Uses</div>\n <div className=\"text-white font-semibold\">\n {code.uses_count} / {code.max_uses}\n </div>\n </div>\n <div>\n <div className=\"text-white/60\">Expires</div>\n <div className=\"text-white font-semibold\">\n {new Date(code.expires_at).toLocaleDateString()}\n </div>\n </div>\n </div>\n\n <div className=\"w-full bg-white/10 rounded-full h-2\">\n <div \n className=\"bg-gradient-to-r from-orange-500 to-red-500 h-2 rounded-full transition-all duration-300\"\n style={{ width: `${(code.uses_count / code.max_uses) * 100}%` }}\n />\n </div>\n <div className=\"text-xs text-white/60\">\n {((code.uses_count / code.max_uses) * 100).toFixed(1)}% used\n </div>\n </div>\n </div>\n ))}\n </div>\n )}\n\n {/* Modal */}\n {showModal && (\n <div className=\"fixed inset-0 bg-black/50 backdrop-blur-sm flex items-center justify-center p-4 z-50\">\n <div className=\"bg-white/10 backdrop-blur-xl border border-white/20 rounded-2xl shadow-2xl max-w-lg w-full max-h-[90vh] overflow-y-auto\">\n <div className=\"p-6\">\n <div className=\"flex justify-between items-center mb-6\">\n <h3 className=\"text-2xl font-light text-white\">\n {editingCode ? 'Edit Discount Code' : 'Create Discount Code'}\n </h3>\n <button\n onClick={() => setShowModal(false)}\n className=\"text-white/60 hover:text-white transition-colors\"\n >\n <svg className=\"w-6 h-6\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M6 18L18 6M6 6l12 12\" />\n </svg>\n </button>\n </div>\n\n <div className=\"space-y-4\">\n <div>\n <label className=\"block text-sm font-medium text-white/80 mb-2\">Code</label>\n <div className=\"flex\">\n <input\n type=\"text\"\n value={formData.code}\n onChange={(e) => setFormData(prev => ({ ...prev, code: e.target.value.toUpperCase() }))}\n className=\"flex-1 px-4 py-3 bg-white/10 border border-white/20 rounded-l-lg text-white placeholder-white/50 focus:ring-2 focus:ring-blue-500 focus:border-transparent font-mono\"\n placeholder=\"DISCOUNT10\"\n />\n <button\n onClick={() => setFormData(prev => ({ ...prev, code: generateCode() }))}\n className=\"px-4 py-3 bg-white/20 hover:bg-white/30 text-white rounded-r-lg border border-l-0 border-white/20 transition-colors\"\n title=\"Generate Random Code\"\n >\n <svg className=\"w-4 h-4\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15\" />\n </svg>\n </button>\n </div>\n </div>\n\n <div>\n <label className=\"block text-sm font-medium text-white/80 mb-2\">Discount Type</label>\n <select\n value={formData.discount_type}\n onChange={(e) => setFormData(prev => ({ ...prev, discount_type: e.target.value as 'percentage' | 'fixed' }))}\n className=\"w-full px-4 py-3 bg-white/10 border border-white/20 rounded-lg text-white focus:ring-2 focus:ring-blue-500 focus:border-transparent\"\n >\n <option value=\"percentage\">Percentage</option>\n <option value=\"fixed\">Fixed Amount</option>\n </select>\n </div>\n\n <div className=\"grid grid-cols-2 gap-4\">\n <div>\n <label className=\"block text-sm font-medium text-white/80 mb-2\">\n Discount Value {formData.discount_type === 'percentage' ? '(%)' : '($)'}\n </label>\n <input\n type=\"number\"\n value={formData.discount_value}\n onChange={(e) => setFormData(prev => ({ ...prev, discount_value: parseFloat(e.target.value) || 0 }))}\n className=\"w-full px-4 py-3 bg-white/10 border border-white/20 rounded-lg text-white placeholder-white/50 focus:ring-2 focus:ring-blue-500 focus:border-transparent\"\n min=\"0\"\n step={formData.discount_type === 'percentage' ? \"1\" : \"0.01\"}\n max={formData.discount_type === 'percentage' ? \"100\" : undefined}\n />\n </div>\n\n <div>\n <label className=\"block text-sm font-medium text-white/80 mb-2\">Minimum Purchase ($)</label>\n <input\n type=\"number\"\n value={formData.minimum_purchase}\n onChange={(e) => setFormData(prev => ({ ...prev, minimum_purchase: parseFloat(e.target.value) || 0 }))}\n className=\"w-full px-4 py-3 bg-white/10 border border-white/20 rounded-lg text-white placeholder-white/50 focus:ring-2 focus:ring-blue-500 focus:border-transparent\"\n min=\"0\"\n step=\"0.01\"\n />\n </div>\n </div>\n\n <div className=\"grid grid-cols-2 gap-4\">\n <div>\n <label className=\"block text-sm font-medium text-white/80 mb-2\">Max Uses</label>\n <input\n type=\"number\"\n value={formData.max_uses}\n onChange={(e) => setFormData(prev => ({ ...prev, max_uses: parseInt(e.target.value) || 0 }))}\n className=\"w-full px-4 py-3 bg-white/10 border border-white/20 rounded-lg text-white placeholder-white/50 focus:ring-2 focus:ring-blue-500 focus:border-transparent\"\n min=\"1\"\n />\n </div>\n\n <div>\n <label className=\"block text-sm font-medium text-white/80 mb-2\">Expires On</label>\n <input\n type=\"date\"\n value={formData.expires_at}\n onChange={(e) => setFormData(prev => ({ ...prev, expires_at: e.target.value }))}\n className=\"w-full px-4 py-3 bg-white/10 border border-white/20 rounded-lg text-white focus:ring-2 focus:ring-blue-500 focus:border-transparent\"\n />\n </div>\n </div>\n\n <div>\n <label className=\"block text-sm font-medium text-white/80 mb-2\">Applicable Ticket Types</label>\n <div className=\"bg-white/5 border border-white/20 rounded-lg p-3 max-h-32 overflow-y-auto\">\n {ticketTypes.length === 0 ? (\n <div className=\"text-white/60 text-sm\">No ticket types available</div>\n ) : (\n <div className=\"space-y-2\">\n <label className=\"flex items-center\">\n <input\n type=\"checkbox\"\n checked={formData.applicable_ticket_types.length === 0}\n onChange={(e) => {\n if (e.target.checked) {\n setFormData(prev => ({ ...prev, applicable_ticket_types: [] }));\n }\n }}\n className=\"w-4 h-4 text-blue-600 bg-white/10 border-white/20 rounded focus:ring-blue-500\"\n />\n <span className=\"ml-2 text-white text-sm\">All ticket types</span>\n </label>\n {ticketTypes.map((type) => (\n <label key={type.id} className=\"flex items-center\">\n <input\n type=\"checkbox\"\n checked={formData.applicable_ticket_types.includes(type.id)}\n onChange={(e) => handleTicketTypeChange(type.id, e.target.checked)}\n className=\"w-4 h-4 text-blue-600 bg-white/10 border-white/20 rounded focus:ring-blue-500\"\n />\n <span className=\"ml-2 text-white text-sm\">{type.name}</span>\n </label>\n ))}\n </div>\n )}\n </div>\n </div>\n </div>\n\n <div className=\"flex justify-end space-x-3 mt-6\">\n <button\n onClick={() => setShowModal(false)}\n className=\"px-6 py-3 text-white/80 hover:text-white transition-colors\"\n >\n Cancel\n </button>\n <button\n onClick={handleSaveCode}\n disabled={saving}\n className=\"px-6 py-3 rounded-lg font-medium transition-all duration-200 disabled:opacity-50\"\n style={{\n background: 'var(--glass-text-accent)',\n color: 'white'\n }}\n >\n {saving ? 'Saving...' : editingCode ? 'Update' : 'Create'}\n </button>\n </div>\n </div>\n </div>\n </div>\n )}\n </div>\n );\n}","usedDeprecatedRules":[]},{"filePath":"/home/tyler/apps/bct-whitelabel/src/components/manage/EventSettingsTab.tsx","messages":[{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":35,"column":43,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":35,"endColumn":46,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[1156,1159],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[1156,1159],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":1,"fixableErrorCount":0,"fixableWarningCount":0,"source":"import { useState } from 'react';\nimport VenueTab from './VenueTab';\nimport MarketingTab from './MarketingTab';\nimport SettingsTab from './SettingsTab';\nimport EmbedCodeModal from '../modals/EmbedCodeModal';\n\ninterface EventSettingsTabProps {\n eventId: string;\n organizationId: string;\n eventSlug?: string;\n}\n\nexport default function EventSettingsTab({ eventId, organizationId, eventSlug }: EventSettingsTabProps) {\n const [activeSubTab, setActiveSubTab] = useState<'details' | 'venue' | 'marketing' | 'embed'>('details');\n const [showEmbedModal, setShowEmbedModal] = useState(false);\n\n const subTabs = [\n { id: 'details', label: 'Event Details' },\n { id: 'venue', label: 'Venue' },\n { id: 'marketing', label: 'Marketing' },\n { id: 'embed', label: 'Embed Code' }\n ];\n\n return (\n <div>\n {/* Sub-navigation */}\n <div className=\"flex flex-wrap border-b border-white/20 mb-6\">\n {subTabs.map((tab) => (\n <button\n key={tab.id}\n onClick={() => {\n if (tab.id === 'embed') {\n setShowEmbedModal(true);\n } else {\n setActiveSubTab(tab.id as any);\n }\n }}\n className={`px-4 py-2 text-sm font-medium transition-colors duration-200 border-b-2 ${\n activeSubTab === tab.id\n ? 'border-blue-500 text-white'\n : 'border-transparent text-white/60 hover:text-white'\n }`}\n >\n {tab.label}\n </button>\n ))}\n </div>\n\n {/* Sub-tab content */}\n <div>\n {activeSubTab === 'details' && (\n <SettingsTab eventId={eventId} organizationId={organizationId} />\n )}\n {activeSubTab === 'venue' && (\n <VenueTab eventId={eventId} organizationId={organizationId} />\n )}\n {activeSubTab === 'marketing' && (\n <MarketingTab eventId={eventId} organizationId={organizationId} />\n )}\n </div>\n\n {/* Embed Code Modal */}\n <EmbedCodeModal\n isOpen={showEmbedModal}\n onClose={() => setShowEmbedModal(false)}\n eventId={eventId}\n eventSlug={eventSlug || ''}\n />\n </div>\n );\n}","usedDeprecatedRules":[]},{"filePath":"/home/tyler/apps/bct-whitelabel/src/components/manage/MarketingTab.tsx","messages":[{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'generateSocialMediaContent' is defined but never used. Allowed unused vars must match /^_/u.","line":5,"column":3,"nodeType":null,"messageId":"unusedVar","endLine":5,"endColumn":29},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'generateFlyerData' is defined but never used. Allowed unused vars must match /^_/u.","line":7,"column":3,"nodeType":null,"messageId":"unusedVar","endLine":7,"endColumn":20},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'loadEventData' is defined but never used. Allowed unused vars must match /^_/u.","line":21,"column":10,"nodeType":null,"messageId":"unusedVar","endLine":21,"endColumn":23},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'organizationId' is defined but never used. Allowed unused args must match /^_/u.","line":29,"column":49,"nodeType":null,"messageId":"unusedVar","endLine":29,"endColumn":63},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'emailTemplate' is assigned a value but never used. Allowed unused vars must match /^_/u.","line":32,"column":10,"nodeType":null,"messageId":"unusedVar","endLine":32,"endColumn":23},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":76,"column":14,"nodeType":null,"messageId":"unusedVar","endLine":76,"endColumn":19},{"ruleId":"no-empty","severity":1,"message":"Empty block statement.","line":76,"column":21,"nodeType":"BlockStatement","messageId":"unexpected","endLine":78,"endColumn":6,"suggestions":[{"messageId":"suggestComment","data":{"type":"block"},"fix":{"range":[2892,2898],"text":" /* empty */ "},"desc":"Add comment inside empty block statement."}]},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":95,"column":14,"nodeType":null,"messageId":"unusedVar","endLine":95,"endColumn":19},{"ruleId":"no-empty","severity":1,"message":"Empty block statement.","line":95,"column":21,"nodeType":"BlockStatement","messageId":"unexpected","endLine":97,"endColumn":6,"suggestions":[{"messageId":"suggestComment","data":{"type":"block"},"fix":{"range":[3353,3359],"text":" /* empty */ "},"desc":"Add comment inside empty block statement."}]},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":106,"column":14,"nodeType":null,"messageId":"unusedVar","endLine":106,"endColumn":19},{"ruleId":"no-empty","severity":1,"message":"Empty block statement.","line":106,"column":21,"nodeType":"BlockStatement","messageId":"unexpected","endLine":108,"endColumn":6,"suggestions":[{"messageId":"suggestComment","data":{"type":"block"},"fix":{"range":[3582,3588],"text":" /* empty */ "},"desc":"Add comment inside empty block statement."}]},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'handleDownloadAsset' is assigned a value but never used. Allowed unused vars must match /^_/u.","line":111,"column":9,"nodeType":null,"messageId":"unusedVar","endLine":111,"endColumn":28},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":114,"column":14,"nodeType":null,"messageId":"unusedVar","endLine":114,"endColumn":19},{"ruleId":"no-empty","severity":1,"message":"Empty block statement.","line":114,"column":21,"nodeType":"BlockStatement","messageId":"unexpected","endLine":116,"endColumn":6,"suggestions":[{"messageId":"suggestComment","data":{"type":"block"},"fix":{"range":[3752,3758],"text":" /* empty */ "},"desc":"Add comment inside empty block statement."}]},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":129,"column":14,"nodeType":null,"messageId":"unusedVar","endLine":129,"endColumn":19},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":147,"column":14,"nodeType":null,"messageId":"unusedVar","endLine":147,"endColumn":19},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":165,"column":14,"nodeType":null,"messageId":"unusedVar","endLine":165,"endColumn":19},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":203,"column":14,"nodeType":null,"messageId":"unusedVar","endLine":203,"endColumn":19},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":230,"column":14,"nodeType":null,"messageId":"unusedVar","endLine":230,"endColumn":19},{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":366,"column":57,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":366,"endColumn":60,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[14847,14850],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[14847,14850],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'index' is defined but never used. Allowed unused args must match /^_/u.","line":617,"column":54,"nodeType":null,"messageId":"unusedVar","endLine":617,"endColumn":59}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":21,"fixableErrorCount":0,"fixableWarningCount":0,"source":"import { useState, useEffect } from 'react';\nimport { \n loadMarketingKit, \n generateMarketingKit, \n generateSocialMediaContent, \n generateEmailTemplate,\n generateFlyerData,\n copyToClipboard,\n downloadAsset\n} from '../../lib/marketing-kit';\nimport { \n generateSocialMediaContentWithAI, \n generateEmailTemplatesWithAI, \n generateFlyerDataWithAI,\n regenerateSocialMediaPost,\n regenerateEmailTemplate,\n saveContentVote,\n type EmailTemplate as AIEmailTemplate,\n type FlyerData\n} from '../../lib/marketing-kit-enhanced';\nimport { loadEventData } from '../../lib/event-management';\nimport type { MarketingKitData, SocialMediaContent, EmailTemplate } from '../../lib/marketing-kit';\n\ninterface MarketingTabProps {\n eventId: string;\n organizationId: string;\n}\n\nexport default function MarketingTab({ eventId, organizationId }: MarketingTabProps) {\n const [marketingKit, setMarketingKit] = useState<MarketingKitData | null>(null);\n const [socialContent, setSocialContent] = useState<SocialMediaContent[]>([]);\n const [emailTemplate, setEmailTemplate] = useState<EmailTemplate | null>(null);\n const [aiEmailTemplates, setAiEmailTemplates] = useState<AIEmailTemplate[]>([]);\n const [flyerData, setFlyerData] = useState<FlyerData[]>([]);\n const [activeTab, setActiveTab] = useState<'overview' | 'social' | 'email' | 'assets'>('overview');\n const [generating, setGenerating] = useState(false);\n const [loading, setLoading] = useState(true);\n const [generatingAI, setGeneratingAI] = useState(false);\n const [generatingEmails, setGeneratingEmails] = useState(false);\n const [generatingFlyers, setGeneratingFlyers] = useState(false);\n const [regeneratingPosts, setRegeneratingPosts] = useState<Record<string, boolean>>({});\n const [regeneratingEmails, setRegeneratingEmails] = useState<Record<string, boolean>>({});\n\n useEffect(() => {\n loadData();\n }, [eventId]);\n\n const loadData = async () => {\n setLoading(true);\n try {\n const kitData = await loadMarketingKit(eventId);\n \n if (kitData) {\n setMarketingKit(kitData);\n \n // Load saved AI content from localStorage\n const savedSocialContent = localStorage.getItem(`social_content_${eventId}`);\n if (savedSocialContent) {\n setSocialContent(JSON.parse(savedSocialContent));\n }\n \n const savedEmailTemplates = localStorage.getItem(`email_templates_${eventId}`);\n if (savedEmailTemplates) {\n setAiEmailTemplates(JSON.parse(savedEmailTemplates));\n }\n \n const savedFlyerData = localStorage.getItem(`flyer_data_${eventId}`);\n if (savedFlyerData) {\n setFlyerData(JSON.parse(savedFlyerData));\n }\n \n // Generate basic email template (fallback)\n const emailData = generateEmailTemplate(kitData.event);\n setEmailTemplate(emailData);\n }\n } catch (error) {\n\n } finally {\n setLoading(false);\n }\n };\n\n const handleGenerateKit = async () => {\n setGenerating(true);\n try {\n const newKit = await generateMarketingKit(eventId);\n if (newKit) {\n setMarketingKit(newKit);\n \n // Don't auto-generate, wait for user to click generate button\n \n const emailData = generateEmailTemplate(newKit.event);\n setEmailTemplate(emailData);\n }\n } catch (error) {\n\n } finally {\n setGenerating(false);\n }\n };\n\n const handleCopyContent = async (content: string) => {\n try {\n await copyToClipboard(content);\n alert('Content copied to clipboard!');\n } catch (error) {\n\n }\n };\n\n const handleDownloadAsset = async (assetUrl: string, filename: string) => {\n try {\n await downloadAsset(assetUrl, filename);\n } catch (error) {\n\n }\n };\n\n const handleGenerateAISocialContent = async () => {\n if (!marketingKit) return;\n \n setGeneratingAI(true);\n try {\n const aiContent = await generateSocialMediaContentWithAI(marketingKit.event);\n setSocialContent(aiContent);\n \n // Save to localStorage for persistence\n localStorage.setItem(`social_content_${eventId}`, JSON.stringify(aiContent));\n } catch (error) {\n\n alert('Failed to generate AI content. Please try again.');\n } finally {\n setGeneratingAI(false);\n }\n };\n\n const handleGenerateAIEmailTemplates = async () => {\n if (!marketingKit) return;\n \n setGeneratingEmails(true);\n try {\n const aiTemplates = await generateEmailTemplatesWithAI(marketingKit.event);\n setAiEmailTemplates(aiTemplates);\n \n // Save to localStorage for persistence\n localStorage.setItem(`email_templates_${eventId}`, JSON.stringify(aiTemplates));\n } catch (error) {\n\n alert('Failed to generate AI email templates. Please try again.');\n } finally {\n setGeneratingEmails(false);\n }\n };\n\n const handleGenerateAIFlyers = async () => {\n if (!marketingKit) return;\n \n setGeneratingFlyers(true);\n try {\n const aiFlyers = await generateFlyerDataWithAI(marketingKit.event);\n setFlyerData(aiFlyers);\n \n // Save to localStorage for persistence\n localStorage.setItem(`flyer_data_${eventId}`, JSON.stringify(aiFlyers));\n } catch (error) {\n\n alert('Failed to generate AI flyers. Please try again.');\n } finally {\n setGeneratingFlyers(false);\n }\n };\n\n const handleVoteContent = async (contentId: string, vote: 'up' | 'down') => {\n await saveContentVote(contentId, vote);\n \n // Update local state to reflect the vote\n setSocialContent(prev => prev.map(content => \n content.id === contentId \n ? { ...content, votes: (content.votes || 0) + (vote === 'up' ? 1 : -1) }\n : content\n ));\n };\n\n const handleRegenerateSocialPost = async (contentId: string, platform: string, tone?: string) => {\n if (!marketingKit) return;\n \n setRegeneratingPosts(prev => ({ ...prev, [contentId]: true }));\n \n try {\n const newPost = await regenerateSocialMediaPost(marketingKit.event, platform, tone);\n if (newPost) {\n // Replace the old post with the new one\n setSocialContent(prev => prev.map(content => \n content.id === contentId ? newPost : content\n ));\n \n // Update localStorage\n const updatedContent = socialContent.map(content => \n content.id === contentId ? newPost : content\n );\n localStorage.setItem(`social_content_${eventId}`, JSON.stringify(updatedContent));\n }\n } catch (error) {\n\n alert('Failed to regenerate post. Please try again.');\n } finally {\n setRegeneratingPosts(prev => ({ ...prev, [contentId]: false }));\n }\n };\n\n const handleRegenerateEmailTemplate = async (templateId: string, tone: string) => {\n if (!marketingKit) return;\n \n setRegeneratingEmails(prev => ({ ...prev, [templateId]: true }));\n \n try {\n const newTemplate = await regenerateEmailTemplate(marketingKit.event, tone);\n if (newTemplate) {\n // Replace the old template with the new one\n setAiEmailTemplates(prev => prev.map(template => \n template.id === templateId ? newTemplate : template\n ));\n \n // Update localStorage\n const updatedTemplates = aiEmailTemplates.map(template => \n template.id === templateId ? newTemplate : template\n );\n localStorage.setItem(`email_templates_${eventId}`, JSON.stringify(updatedTemplates));\n }\n } catch (error) {\n\n alert('Failed to regenerate email template. Please try again.');\n } finally {\n setRegeneratingEmails(prev => ({ ...prev, [templateId]: false }));\n }\n };\n\n const handleVoteEmailTemplate = async (templateId: string, vote: 'up' | 'down') => {\n await saveContentVote(templateId, vote);\n \n // If thumbs down, automatically regenerate\n if (vote === 'down') {\n const template = aiEmailTemplates.find(t => t.id === templateId);\n if (template) {\n await handleRegenerateEmailTemplate(templateId, template.tone || 'professional');\n }\n } else {\n // Update local state to reflect the vote\n setAiEmailTemplates(prev => prev.map(template => \n template.id === templateId \n ? { ...template, votes: (template.votes || 0) + (vote === 'up' ? 1 : -1) }\n : template\n ));\n }\n };\n\n const handleVoteSocialContent = async (contentId: string, vote: 'up' | 'down') => {\n await saveContentVote(contentId, vote);\n \n // If thumbs down, automatically regenerate\n if (vote === 'down') {\n const content = socialContent.find(c => c.id === contentId);\n if (content) {\n await handleRegenerateSocialPost(contentId, content.platform, content.tone);\n }\n } else {\n // Update local state to reflect the vote\n setSocialContent(prev => prev.map(content => \n content.id === contentId \n ? { ...content, votes: (content.votes || 0) + (vote === 'up' ? 1 : -1) }\n : content\n ));\n }\n };\n\n const getPlatformIcon = (platform: string) => {\n switch (platform) {\n case 'facebook':\n return (\n <svg className=\"w-5 h-5\" fill=\"currentColor\" viewBox=\"0 0 24 24\">\n <path d=\"M24 12.073c0-6.627-5.373-12-12-12s-12 5.373-12 12c0 5.99 4.388 10.954 10.125 11.854v-8.385H7.078v-3.47h3.047V9.43c0-3.007 1.792-4.669 4.533-4.669 1.312 0 2.686.235 2.686.235v2.953H15.83c-1.491 0-1.956.925-1.956 1.874v2.25h3.328l-.532 3.47h-2.796v8.385C19.612 23.027 24 18.062 24 12.073z\"/>\n </svg>\n );\n case 'twitter':\n return (\n <svg className=\"w-5 h-5\" fill=\"currentColor\" viewBox=\"0 0 24 24\">\n <path d=\"M23.953 4.57a10 10 0 01-2.825.775 4.958 4.958 0 002.163-2.723c-.951.555-2.005.959-3.127 1.184a4.92 4.92 0 00-8.384 4.482C7.69 8.095 4.067 6.13 1.64 3.162a4.822 4.822 0 00-.666 2.475c0 1.71.87 3.213 2.188 4.096a4.904 4.904 0 01-2.228-.616v.06a4.923 4.923 0 003.946 4.827 4.996 4.996 0 01-2.212.085 4.936 4.936 0 004.604 3.417 9.867 9.867 0 01-6.102 2.105c-.39 0-.779-.023-1.17-.067a13.995 13.995 0 007.557 2.209c9.053 0 13.998-7.496 13.998-13.985 0-.21 0-.42-.015-.63A9.935 9.935 0 0024 4.59z\"/>\n </svg>\n );\n case 'instagram':\n return (\n <svg className=\"w-5 h-5\" fill=\"currentColor\" viewBox=\"0 0 24 24\">\n <path d=\"M12 2.163c3.204 0 3.584.012 4.85.07 3.252.148 4.771 1.691 4.919 4.919.058 1.265.069 1.645.069 4.849 0 3.205-.012 3.584-.069 4.849-.149 3.225-1.664 4.771-4.919 4.919-1.266.058-1.644.07-4.85.07-3.204 0-3.584-.012-4.849-.07-3.26-.149-4.771-1.699-4.919-4.92-.058-1.265-.07-1.644-.07-4.849 0-3.204.013-3.583.07-4.849.149-3.227 1.664-4.771 4.919-4.919 1.266-.057 1.645-.069 4.849-.069zm0-2.163c-3.259 0-3.667.014-4.947.072-4.358.2-6.78 2.618-6.98 6.98-.059 1.281-.073 1.689-.073 4.948 0 3.259.014 3.668.072 4.948.2 4.358 2.618 6.78 6.98 6.98 1.281.058 1.689.072 4.948.072 3.259 0 3.668-.014 4.948-.072 4.354-.2 6.782-2.618 6.979-6.98.059-1.28.073-1.689.073-4.948 0-3.259-.014-3.667-.072-4.947-.196-4.354-2.617-6.78-6.979-6.98-1.281-.059-1.69-.073-4.949-.073zm0 5.838c-3.403 0-6.162 2.759-6.162 6.162s2.759 6.163 6.162 6.163 6.162-2.759 6.162-6.163c0-3.403-2.759-6.162-6.162-6.162zm0 10.162c-2.209 0-4-1.79-4-4 0-2.209 1.791-4 4-4s4 1.791 4 4c0 2.21-1.791 4-4 4zm6.406-11.845c-.796 0-1.441.645-1.441 1.44s.645 1.44 1.441 1.44c.795 0 1.439-.645 1.439-1.44s-.644-1.44-1.439-1.44z\"/>\n </svg>\n );\n case 'linkedin':\n return (\n <svg className=\"w-5 h-5\" fill=\"currentColor\" viewBox=\"0 0 24 24\">\n <path d=\"M20.447 20.452h-3.554v-5.569c0-1.328-.027-3.037-1.852-3.037-1.853 0-2.136 1.445-2.136 2.939v5.667H9.351V9h3.414v1.561h.046c.477-.9 1.637-1.85 3.37-1.85 3.601 0 4.267 2.37 4.267 5.455v6.286zM5.337 7.433c-1.144 0-2.063-.926-2.063-2.065 0-1.138.92-2.063 2.063-2.063 1.14 0 2.064.925 2.064 2.063 0 1.139-.925 2.065-2.064 2.065zm1.782 13.019H3.555V9h3.564v11.452zM22.225 0H1.771C.792 0 0 .774 0 1.729v20.542C0 23.227.792 24 1.771 24h20.451C23.2 24 24 23.227 24 22.271V1.729C24 .774 23.2 0 22.222 0h.003z\"/>\n </svg>\n );\n default:\n return null;\n }\n };\n\n if (loading) {\n return (\n <div className=\"flex items-center justify-center py-12\">\n <div className=\"animate-spin rounded-full h-8 w-8 border-b-2 border-white\"></div>\n </div>\n );\n }\n\n return (\n <div className=\"space-y-6\">\n <div className=\"flex justify-between items-center\">\n <h2 className=\"text-2xl font-light text-white\">Marketing Kit</h2>\n <button\n onClick={handleGenerateKit}\n disabled={generating}\n className=\"flex items-center gap-2 px-4 py-2 rounded-lg font-medium transition-all duration-200 disabled:opacity-50\"\n style={{\n background: 'var(--glass-text-accent)',\n color: 'white'\n }}\n >\n <svg className=\"w-4 h-4\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15\" />\n </svg>\n {generating ? 'Generating...' : 'Generate Marketing Kit'}\n </button>\n </div>\n\n {!marketingKit ? (\n <div className=\"text-center py-12\">\n <svg className=\"w-12 h-12 text-white/40 mx-auto mb-4\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M9.663 17h4.673M12 3v1m6.364 1.636l-.707.707M21 12h-1M4 12H3m3.343-5.657l-.707-.707m2.828 9.9a5 5 0 117.072 0l-.548.547A3.374 3.374 0 0014 18.469V19a2 2 0 11-4 0v-.531c0-.895-.356-1.754-.988-2.386l-.548-.547z\" />\n </svg>\n <p className=\"text-white/60 mb-4\">No marketing kit generated yet</p>\n <button\n onClick={handleGenerateKit}\n disabled={generating}\n className=\"px-6 py-3 rounded-lg font-medium transition-all duration-200 disabled:opacity-50\"\n style={{\n background: 'var(--glass-text-accent)',\n color: 'white'\n }}\n >\n {generating ? 'Generating...' : 'Generate Marketing Kit'}\n </button>\n </div>\n ) : (\n <>\n {/* Tab Navigation */}\n <div className=\"border-b border-white/20\">\n <nav className=\"flex space-x-8\">\n {[\n { id: 'overview', label: 'Overview', icon: '📊' },\n { id: 'social', label: 'Social Media', icon: '📱' },\n { id: 'email', label: 'Email', icon: '✉️' },\n { id: 'assets', label: 'Assets', icon: '🎨' }\n ].map((tab) => (\n <button\n key={tab.id}\n onClick={() => setActiveTab(tab.id as any)}\n className={`flex items-center gap-2 py-3 px-1 border-b-2 font-medium text-sm transition-colors ${\n activeTab === tab.id\n ? 'border-blue-500 text-blue-400'\n : 'border-transparent text-white/60 hover:text-white/80 hover:border-white/20'\n }`}\n >\n <span>{tab.icon}</span>\n {tab.label}\n </button>\n ))}\n </nav>\n </div>\n\n {/* Tab Content */}\n <div className=\"min-h-[500px]\">\n {activeTab === 'overview' && (\n <div className=\"space-y-6\">\n <div className=\"bg-white/5 border border-white/20 rounded-xl p-6\">\n <h3 className=\"text-lg font-semibold text-white mb-4\">Marketing Kit Overview</h3>\n <div className=\"grid grid-cols-1 md:grid-cols-4 gap-6\">\n <div className=\"text-center\">\n <div className=\"text-3xl font-bold text-blue-400 mb-2\">{socialContent.length}</div>\n <div className=\"text-white/60\">Social Posts</div>\n </div>\n <div className=\"text-center\">\n <div className=\"text-3xl font-bold text-green-400 mb-2\">{aiEmailTemplates.length}</div>\n <div className=\"text-white/60\">Email Templates</div>\n </div>\n <div className=\"text-center\">\n <div className=\"text-3xl font-bold text-purple-400 mb-2\">{flyerData.length}</div>\n <div className=\"text-white/60\">Flyer Concepts</div>\n </div>\n <div className=\"text-center\">\n <div className=\"text-3xl font-bold text-orange-400 mb-2\">{marketingKit.assets.length}</div>\n <div className=\"text-white/60\">Legacy Assets</div>\n </div>\n </div>\n </div>\n\n <div className=\"bg-white/5 border border-white/20 rounded-xl p-6\">\n <h3 className=\"text-lg font-semibold text-white mb-4\">Event Information</h3>\n <div className=\"grid grid-cols-1 md:grid-cols-2 gap-6\">\n <div>\n <h4 className=\"font-medium text-white mb-2\">Event Details</h4>\n <div className=\"space-y-2 text-sm\">\n <div><span className=\"text-white/60\">Title:</span> <span className=\"text-white\">{marketingKit.event.title}</span></div>\n <div><span className=\"text-white/60\">Date:</span> <span className=\"text-white\">{new Date(marketingKit.event.date).toLocaleDateString()}</span></div>\n <div><span className=\"text-white/60\">Venue:</span> <span className=\"text-white\">{marketingKit.event.venue}</span></div>\n </div>\n </div>\n <div>\n <h4 className=\"font-medium text-white mb-2\">Social Links</h4>\n <div className=\"space-y-2 text-sm\">\n {Object.entries(marketingKit.social_links).map(([platform, url]) => (\n <div key={platform}>\n <span className=\"text-white/60 capitalize\">{platform}:</span> \n <span className=\"text-white ml-2\">{url || 'Not configured'}</span>\n </div>\n ))}\n </div>\n </div>\n </div>\n </div>\n </div>\n )}\n\n {activeTab === 'social' && (\n <div className=\"space-y-6\">\n <div className=\"flex justify-between items-center mb-6\">\n <h3 className=\"text-xl font-semibold text-white\">Social Media Content</h3>\n <button\n onClick={handleGenerateAISocialContent}\n disabled={generatingAI || !marketingKit}\n className={`flex items-center gap-2 px-4 py-2 rounded-lg font-medium transition-all duration-200 ${\n generatingAI || !marketingKit\n ? 'bg-gray-600 text-gray-400 cursor-not-allowed'\n : ''\n }`}\n style={generatingAI || !marketingKit ? {} : {\n background: 'var(--glass-text-accent)',\n color: 'white'\n }}\n >\n {generatingAI ? (\n <>\n <svg className=\"animate-spin h-4 w-4\" fill=\"none\" viewBox=\"0 0 24 24\">\n <circle className=\"opacity-25\" cx=\"12\" cy=\"12\" r=\"10\" stroke=\"currentColor\" strokeWidth=\"4\"></circle>\n <path className=\"opacity-75\" fill=\"currentColor\" d=\"M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z\"></path>\n </svg>\n Generating with AI...\n </>\n ) : (\n <>\n <svg className=\"w-4 h-4\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M13 10V3L4 14h7v7l9-11h-7z\" />\n </svg>\n Generate AI Content\n </>\n )}\n </button>\n </div>\n \n {socialContent.length === 0 ? (\n <div className=\"text-center py-12\">\n <svg className=\"w-12 h-12 text-white/40 mx-auto mb-4\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M7 8h10M7 12h4m1 8l-4-4H5a2 2 0 01-2-2V6a2 2 0 012-2h14a2 2 0 012 2v8a2 2 0 01-2 2h-3l-4 4z\" />\n </svg>\n <p className=\"text-white/60 mb-4\">No social media content generated yet</p>\n <p className=\"text-white/40 text-sm\">Click \"Generate AI Content\" to create 5 unique posts</p>\n </div>\n ) : (\n <div className=\"grid grid-cols-1 lg:grid-cols-2 gap-6\">\n {socialContent.map((content) => (\n <div key={content.platform} className=\"bg-white/5 border border-white/20 rounded-xl p-6\">\n <div className=\"flex items-center gap-3 mb-4\">\n <div className=\"text-blue-400\">\n {getPlatformIcon(content.platform)}\n </div>\n <h3 className=\"text-lg font-semibold text-white capitalize\">{content.platform}</h3>\n </div>\n \n <div className=\"space-y-4\">\n <div>\n <label className=\"block text-sm font-medium text-white/80 mb-2\">Content</label>\n <div className=\"bg-white/10 rounded-lg p-3 text-white text-sm whitespace-pre-wrap\">\n {content.content}\n </div>\n </div>\n \n <div>\n <label className=\"block text-sm font-medium text-white/80 mb-2\">Hashtags</label>\n <div className=\"flex flex-wrap gap-2\">\n {content.hashtags.map((hashtag, index) => (\n <span key={index} className=\"px-2 py-1 bg-blue-500/20 text-blue-300 rounded-lg text-xs\">\n {hashtag}\n </span>\n ))}\n </div>\n </div>\n \n <div className=\"flex justify-between items-center\">\n <div className=\"flex gap-2\">\n <button\n onClick={() => handleVoteSocialContent(content.id || '', 'up')}\n disabled={regeneratingPosts[content.id || '']}\n className={`p-2 rounded-lg transition-colors ${\n (content.votes || 0) > 0 \n ? 'bg-green-500/20 text-green-400' \n : 'bg-white/10 text-white/60 hover:text-green-400'\n } ${regeneratingPosts[content.id || ''] ? 'opacity-50 cursor-not-allowed' : ''}`}\n title=\"Like this content\"\n >\n <svg className=\"w-4 h-4\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M14 10h4.764a2 2 0 011.789 2.894l-3.5 7A2 2 0 0115.263 21h-4.017c-.163 0-.326-.02-.485-.06L7 20m7-10V5a2 2 0 00-2-2h-.095c-.5 0-.905.405-.905.905 0 .714-.211 1.412-.608 2.006L7 11v9m7-10h-2M7 20H5a2 2 0 01-2-2v-6a2 2 0 012-2h2.5\" />\n </svg>\n </button>\n <button\n onClick={() => handleVoteSocialContent(content.id || '', 'down')}\n disabled={regeneratingPosts[content.id || '']}\n className={`p-2 rounded-lg transition-colors ${\n regeneratingPosts[content.id || ''] \n ? 'bg-blue-500/20 text-blue-400' \n : (content.votes || 0) < 0 \n ? 'bg-red-500/20 text-red-400' \n : 'bg-white/10 text-white/60 hover:text-red-400'\n }`}\n title={regeneratingPosts[content.id || ''] ? \"Regenerating...\" : \"Dislike & regenerate\"}\n >\n {regeneratingPosts[content.id || ''] ? (\n <svg className=\"animate-spin h-4 w-4\" fill=\"none\" viewBox=\"0 0 24 24\">\n <circle className=\"opacity-25\" cx=\"12\" cy=\"12\" r=\"10\" stroke=\"currentColor\" strokeWidth=\"4\"></circle>\n <path className=\"opacity-75\" fill=\"currentColor\" d=\"M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z\"></path>\n </svg>\n ) : (\n <svg className=\"w-4 h-4\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M10 14H5.236a2 2 0 01-1.789-2.894l3.5-7A2 2 0 018.736 3h4.018a2 2 0 01.485.06l3.76.94m-7 10v5a2 2 0 002 2h.096c.5 0 .905-.405.905-.904 0-.715.211-1.413.608-2.008L17 13V4m-7 10h2m5-10h2a2 2 0 012 2v6a2 2 0 01-2 2h-2.5\" />\n </svg>\n )}\n </button>\n {content.tone && (\n <span className=\"px-2 py-1 bg-white/10 text-white/60 rounded-lg text-xs capitalize\">\n {content.tone}\n </span>\n )}\n </div>\n <button\n onClick={() => handleCopyContent(`${content.content}\\n\\n${content.hashtags.join(' ')}`)}\n className=\"px-4 py-2 rounded-lg text-sm transition-colors\"\n style={{\n background: 'var(--glass-text-accent)',\n color: 'white'\n }}\n >\n Copy Content\n </button>\n </div>\n </div>\n </div>\n ))}\n </div>\n )}\n </div>\n )}\n\n {activeTab === 'email' && (\n <div className=\"space-y-6\">\n <div className=\"flex justify-between items-center mb-6\">\n <h3 className=\"text-xl font-semibold text-white\">Email Templates</h3>\n <button\n onClick={handleGenerateAIEmailTemplates}\n disabled={generatingEmails || !marketingKit}\n className={`flex items-center gap-2 px-4 py-2 rounded-lg font-medium transition-all duration-200 ${\n generatingEmails || !marketingKit\n ? 'bg-gray-600 text-gray-400 cursor-not-allowed'\n : ''\n }`}\n style={generatingEmails || !marketingKit ? {} : {\n background: 'var(--glass-text-accent)',\n color: 'white'\n }}\n >\n {generatingEmails ? (\n <>\n <svg className=\"animate-spin h-4 w-4\" fill=\"none\" viewBox=\"0 0 24 24\">\n <circle className=\"opacity-25\" cx=\"12\" cy=\"12\" r=\"10\" stroke=\"currentColor\" strokeWidth=\"4\"></circle>\n <path className=\"opacity-75\" fill=\"currentColor\" d=\"M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z\"></path>\n </svg>\n Generating with AI...\n </>\n ) : (\n <>\n <svg className=\"w-4 h-4\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M13 10V3L4 14h7v7l9-11h-7z\" />\n </svg>\n Generate AI Email Templates\n </>\n )}\n </button>\n </div>\n\n {aiEmailTemplates.length === 0 ? (\n <div className=\"text-center py-12\">\n <svg className=\"w-12 h-12 text-white/40 mx-auto mb-4\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M3 8l7.89 5.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z\" />\n </svg>\n <p className=\"text-white/60 mb-4\">No AI email templates generated yet</p>\n <p className=\"text-white/40 text-sm\">Click \"Generate AI Email Templates\" to create 3 unique templates</p>\n </div>\n ) : (\n <div className=\"grid grid-cols-1 gap-6\">\n {aiEmailTemplates.map((template, index) => (\n <div key={template.id} className=\"bg-white/5 border border-white/20 rounded-xl p-6\">\n <div className=\"flex justify-between items-start mb-4\">\n <h4 className=\"text-lg font-semibold text-white capitalize\">{template.tone} Tone</h4>\n <div className=\"flex gap-2\">\n <button\n onClick={() => handleVoteEmailTemplate(template.id || '', 'up')}\n disabled={regeneratingEmails[template.id || '']}\n className={`p-2 rounded-lg transition-colors ${\n (template.votes || 0) > 0 \n ? 'bg-green-500/20 text-green-400' \n : 'bg-white/10 text-white/60 hover:text-green-400'\n } ${regeneratingEmails[template.id || ''] ? 'opacity-50 cursor-not-allowed' : ''}`}\n title=\"Like this template\"\n >\n <svg className=\"w-4 h-4\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M14 10h4.764a2 2 0 011.789 2.894l-3.5 7A2 2 0 0115.263 21h-4.017c-.163 0-.326-.02-.485-.06L7 20m7-10V5a2 2 0 00-2-2h-.095c-.5 0-.905.405-.905.905 0 .714-.211 1.412-.608 2.006L7 11v9m7-10h-2M7 20H5a2 2 0 01-2-2v-6a2 2 0 012-2h2.5\" />\n </svg>\n </button>\n <button\n onClick={() => handleVoteEmailTemplate(template.id || '', 'down')}\n disabled={regeneratingEmails[template.id || '']}\n className={`p-2 rounded-lg transition-colors ${\n regeneratingEmails[template.id || ''] \n ? 'bg-blue-500/20 text-blue-400' \n : (template.votes || 0) < 0 \n ? 'bg-red-500/20 text-red-400' \n : 'bg-white/10 text-white/60 hover:text-red-400'\n }`}\n title={regeneratingEmails[template.id || ''] ? \"Regenerating...\" : \"Dislike & regenerate\"}\n >\n {regeneratingEmails[template.id || ''] ? (\n <svg className=\"animate-spin h-4 w-4\" fill=\"none\" viewBox=\"0 0 24 24\">\n <circle className=\"opacity-25\" cx=\"12\" cy=\"12\" r=\"10\" stroke=\"currentColor\" strokeWidth=\"4\"></circle>\n <path className=\"opacity-75\" fill=\"currentColor\" d=\"M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z\"></path>\n </svg>\n ) : (\n <svg className=\"w-4 h-4\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M10 14H5.236a2 2 0 01-1.789-2.894l3.5-7A2 2 0 018.736 3h4.018a2 2 0 01.485.06l3.76.94m-7 10v5a2 2 0 002 2h.096c.5 0 .905-.405.905-.904 0-.715.211-1.413.608-2.008L17 13V4m-7 10h2m5-10h2a2 2 0 012 2v6a2 2 0 01-2 2h-2.5\" />\n </svg>\n )}\n </button>\n </div>\n </div>\n \n <div className=\"space-y-4\">\n <div>\n <label className=\"block text-sm font-medium text-white/80 mb-2\">Subject Line</label>\n <div className=\"bg-white/10 rounded-lg p-3 text-white\">\n {template.subject}\n </div>\n <div className=\"flex justify-end mt-2\">\n <button\n onClick={() => handleCopyContent(template.subject)}\n className=\"px-3 py-1 rounded-lg text-sm transition-colors\"\n style={{\n background: 'var(--glass-text-accent)',\n color: 'white'\n }}\n >\n Copy Subject\n </button>\n </div>\n </div>\n \n <div>\n <label className=\"block text-sm font-medium text-white/80 mb-2\">Preview Text</label>\n <div className=\"bg-white/10 rounded-lg p-3 text-white\">\n {template.preview_text}\n </div>\n </div>\n \n <div>\n <label className=\"block text-sm font-medium text-white/80 mb-2\">HTML Content</label>\n <div className=\"bg-white/10 rounded-lg p-3 text-white text-sm max-h-64 overflow-y-auto\">\n <pre className=\"whitespace-pre-wrap\">{template.html_content}</pre>\n </div>\n <div className=\"flex justify-end mt-2\">\n <button\n onClick={() => handleCopyContent(template.html_content)}\n className=\"px-3 py-1 rounded-lg text-sm transition-colors\"\n style={{\n background: 'var(--glass-text-accent)',\n color: 'white'\n }}\n >\n Copy HTML\n </button>\n </div>\n </div>\n \n <div>\n <label className=\"block text-sm font-medium text-white/80 mb-2\">Text Content</label>\n <div className=\"bg-white/10 rounded-lg p-3 text-white text-sm max-h-64 overflow-y-auto\">\n <pre className=\"whitespace-pre-wrap\">{template.text_content}</pre>\n </div>\n <div className=\"flex justify-end mt-2\">\n <button\n onClick={() => handleCopyContent(template.text_content)}\n className=\"px-3 py-1 rounded-lg text-sm transition-colors\"\n style={{\n background: 'var(--glass-text-accent)',\n color: 'white'\n }}\n >\n Copy Text\n </button>\n </div>\n </div>\n </div>\n </div>\n ))}\n </div>\n )}\n </div>\n )}\n\n {activeTab === 'assets' && (\n <div className=\"space-y-6\">\n <div className=\"flex justify-between items-center mb-6\">\n <h3 className=\"text-xl font-semibold text-white\">Marketing Assets</h3>\n <button\n onClick={handleGenerateAIFlyers}\n disabled={generatingFlyers || !marketingKit}\n className={`flex items-center gap-2 px-4 py-2 rounded-lg font-medium transition-all duration-200 ${\n generatingFlyers || !marketingKit\n ? 'bg-gray-600 text-gray-400 cursor-not-allowed'\n : ''\n }`}\n style={generatingFlyers || !marketingKit ? {} : {\n background: 'var(--glass-text-accent)',\n color: 'white'\n }}\n >\n {generatingFlyers ? (\n <>\n <svg className=\"animate-spin h-4 w-4\" fill=\"none\" viewBox=\"0 0 24 24\">\n <circle className=\"opacity-25\" cx=\"12\" cy=\"12\" r=\"10\" stroke=\"currentColor\" strokeWidth=\"4\"></circle>\n <path className=\"opacity-75\" fill=\"currentColor\" d=\"M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z\"></path>\n </svg>\n Generating with AI...\n </>\n ) : (\n <>\n <svg className=\"w-4 h-4\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M13 10V3L4 14h7v7l9-11h-7z\" />\n </svg>\n Generate AI Flyers\n </>\n )}\n </button>\n </div>\n\n {flyerData.length === 0 ? (\n <div className=\"text-center py-12\">\n <svg className=\"w-12 h-12 text-white/40 mx-auto mb-4\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z\" />\n </svg>\n <p className=\"text-white/60 mb-4\">No AI flyers generated yet</p>\n <p className=\"text-white/40 text-sm\">Click \"Generate AI Flyers\" to create 3 unique flyer concepts</p>\n </div>\n ) : (\n <div className=\"grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6\">\n {flyerData.map((flyer) => (\n <div key={flyer.id} className=\"bg-white/5 border border-white/20 rounded-xl p-6\">\n <div className=\"flex items-center justify-between mb-4\">\n <h3 className=\"text-lg font-semibold text-white capitalize\">{flyer.style} Style</h3>\n <div className=\"flex gap-2\">\n <button\n onClick={() => handleVoteContent(flyer.id || '', 'up')}\n className={`p-2 rounded-lg transition-colors ${\n (flyer.votes || 0) > 0 \n ? 'bg-green-500/20 text-green-400' \n : 'bg-white/10 text-white/60 hover:text-green-400'\n }`}\n title=\"Like this flyer\"\n >\n <svg className=\"w-4 h-4\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M14 10h4.764a2 2 0 011.789 2.894l-3.5 7A2 2 0 0115.263 21h-4.017c-.163 0-.326-.02-.485-.06L7 20m7-10V5a2 2 0 00-2-2h-.095c-.5 0-.905.405-.905.905 0 .714-.211 1.412-.608 2.006L7 11v9m7-10h-2M7 20H5a2 2 0 01-2-2v-6a2 2 0 012-2h2.5\" />\n </svg>\n </button>\n <button\n onClick={() => handleVoteContent(flyer.id || '', 'down')}\n className={`p-2 rounded-lg transition-colors ${\n (flyer.votes || 0) < 0 \n ? 'bg-red-500/20 text-red-400' \n : 'bg-white/10 text-white/60 hover:text-red-400'\n }`}\n title=\"Dislike this flyer\"\n >\n <svg className=\"w-4 h-4\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M10 14H5.236a2 2 0 01-1.789-2.894l3.5-7A2 2 0 018.736 3h4.018a2 2 0 01.485.06l3.76.94m-7 10v5a2 2 0 002 2h.096c.5 0 .905-.405.905-.904 0-.715.211-1.413.608-2.008L17 13V4m-7 10h2m5-10h2a2 2 0 012 2v6a2 2 0 01-2 2h-2.5\" />\n </svg>\n </button>\n </div>\n </div>\n \n <div className=\"space-y-4\">\n <div>\n <label className=\"block text-sm font-medium text-white/80 mb-2\">Title</label>\n <div className=\"bg-white/10 rounded-lg p-3 text-white font-semibold\">\n {flyer.title}\n </div>\n </div>\n \n <div>\n <label className=\"block text-sm font-medium text-white/80 mb-2\">Content</label>\n <div className=\"bg-white/10 rounded-lg p-3 text-white text-sm\">\n {flyer.content}\n </div>\n </div>\n \n <div>\n <label className=\"block text-sm font-medium text-white/80 mb-2\">Design Theme</label>\n <div className=\"bg-white/10 rounded-lg p-3 text-white/80 text-sm\">\n {flyer.theme}\n </div>\n </div>\n \n <div className=\"flex justify-end\">\n <button\n onClick={() => handleCopyContent(`${flyer.title}\\n\\n${flyer.content}\\n\\nDesign Theme: ${flyer.theme}`)}\n className=\"px-4 py-2 rounded-lg text-sm transition-colors\"\n style={{\n background: 'var(--glass-text-accent)',\n color: 'white'\n }}\n >\n Copy Flyer Content\n </button>\n </div>\n </div>\n </div>\n ))}\n </div>\n )}\n </div>\n )}\n </div>\n </>\n )}\n </div>\n );\n}","usedDeprecatedRules":[]},{"filePath":"/home/tyler/apps/bct-whitelabel/src/components/manage/OrdersTab.tsx","messages":[{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":15,"column":50,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":15,"endColumn":53,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[697,700],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[697,700],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":41,"column":14,"nodeType":null,"messageId":"unusedVar","endLine":41,"endColumn":19},{"ruleId":"no-empty","severity":1,"message":"Empty block statement.","line":41,"column":21,"nodeType":"BlockStatement","messageId":"unexpected","endLine":43,"endColumn":6,"suggestions":[{"messageId":"suggestComment","data":{"type":"block"},"fix":{"range":[1499,1505],"text":" /* empty */ "},"desc":"Add comment inside empty block statement."}]},{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":87,"column":62,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":87,"endColumn":65,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[2967,2970],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[2967,2970],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":136,"column":14,"nodeType":null,"messageId":"unusedVar","endLine":136,"endColumn":19},{"ruleId":"no-empty","severity":1,"message":"Empty block statement.","line":136,"column":21,"nodeType":"BlockStatement","messageId":"unexpected","endLine":138,"endColumn":6,"suggestions":[{"messageId":"suggestComment","data":{"type":"block"},"fix":{"range":[4478,4484],"text":" /* empty */ "},"desc":"Add comment inside empty block statement."}]}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":6,"fixableErrorCount":0,"fixableWarningCount":0,"source":"import { useState, useEffect } from 'react';\nimport { loadSalesData, exportSalesData, type SalesData, type SalesFilter } from '../../lib/sales-analytics';\nimport { loadTicketTypes } from '../../lib/ticket-management';\nimport { refundTicket, checkInTicket } from '../../lib/ticket-management';\nimport { formatCurrency } from '../../lib/event-management';\nimport OrdersTable from '../tables/OrdersTable';\n\ninterface OrdersTabProps {\n eventId: string;\n}\n\nexport default function OrdersTab({ eventId }: OrdersTabProps) {\n const [orders, setOrders] = useState<SalesData[]>([]);\n const [filteredOrders, setFilteredOrders] = useState<SalesData[]>([]);\n const [ticketTypes, setTicketTypes] = useState<any[]>([]);\n const [filters, setFilters] = useState<SalesFilter>({});\n const [searchTerm, setSearchTerm] = useState('');\n const [selectedOrder, setSelectedOrder] = useState<SalesData | null>(null);\n const [showOrderDetails, setShowOrderDetails] = useState(false);\n const [loading, setLoading] = useState(true);\n const [exporting, setExporting] = useState(false);\n\n useEffect(() => {\n loadData();\n }, [eventId]);\n\n useEffect(() => {\n applyFilters();\n }, [orders, filters, searchTerm]);\n\n const loadData = async () => {\n setLoading(true);\n try {\n const [ordersData, ticketTypesData] = await Promise.all([\n loadSalesData(eventId),\n loadTicketTypes(eventId)\n ]);\n \n setOrders(ordersData);\n setTicketTypes(ticketTypesData);\n } catch (error) {\n\n } finally {\n setLoading(false);\n }\n };\n\n const applyFilters = () => {\n let filtered = [...orders];\n\n // Apply ticket type filter\n if (filters.ticketTypeId) {\n filtered = filtered.filter(order => order.ticket_type_id === filters.ticketTypeId);\n }\n\n // Apply status filter\n if (filters.status) {\n if (filters.status === 'confirmed') {\n filtered = filtered.filter(order => !order.refund_status || order.refund_status === null);\n } else if (filters.status === 'refunded') {\n filtered = filtered.filter(order => order.refund_status === 'completed');\n } else if (filters.status === 'pending') {\n filtered = filtered.filter(order => order.refund_status === 'pending');\n } else if (filters.status === 'cancelled') {\n filtered = filtered.filter(order => order.refund_status === 'failed');\n }\n }\n\n // Apply check-in filter\n if (filters.checkedIn !== undefined) {\n filtered = filtered.filter(order => order.checked_in === filters.checkedIn);\n }\n\n // Apply search filter\n if (searchTerm) {\n const term = searchTerm.toLowerCase();\n filtered = filtered.filter(order => \n order.purchaser_name.toLowerCase().includes(term) ||\n order.purchaser_email.toLowerCase().includes(term) ||\n order.uuid.toLowerCase().includes(term)\n );\n }\n\n setFilteredOrders(filtered);\n };\n\n const handleFilterChange = (key: keyof SalesFilter, value: any) => {\n setFilters(prev => ({ ...prev, [key]: value }));\n };\n\n const clearFilters = () => {\n setFilters({});\n setSearchTerm('');\n };\n\n const handleViewOrder = (order: SalesData) => {\n setSelectedOrder(order);\n setShowOrderDetails(true);\n };\n\n const handleRefundOrder = async (order: SalesData) => {\n if (confirm(`Are you sure you want to refund ${order.purchaser_name}'s ticket?`)) {\n const success = await refundTicket(order.id);\n if (success) {\n setOrders(prev => prev.map(o => \n o.id === order.id ? { ...o, refund_status: 'completed' } : o\n ));\n }\n }\n };\n\n const handleCheckInOrder = async (order: SalesData) => {\n const success = await checkInTicket(order.id);\n if (success) {\n setOrders(prev => prev.map(o => \n o.id === order.id ? { ...o, checked_in: true } : o\n ));\n }\n };\n\n const handleExport = async (format: 'csv' | 'json' = 'csv') => {\n setExporting(true);\n try {\n const exportData = await exportSalesData(eventId, format);\n const blob = new Blob([exportData], { \n type: format === 'csv' ? 'text/csv' : 'application/json' \n });\n const url = window.URL.createObjectURL(blob);\n const a = document.createElement('a');\n a.href = url;\n a.download = `orders-${new Date().toISOString().split('T')[0]}.${format}`;\n document.body.appendChild(a);\n a.click();\n window.URL.revokeObjectURL(url);\n document.body.removeChild(a);\n } catch (error) {\n\n } finally {\n setExporting(false);\n }\n };\n\n const getOrderStats = () => {\n const totalOrders = filteredOrders.length;\n const confirmedOrders = filteredOrders.filter(o => !o.refund_status || o.refund_status === null).length;\n const refundedOrders = filteredOrders.filter(o => o.refund_status === 'completed').length;\n const checkedInOrders = filteredOrders.filter(o => o.checked_in).length;\n const totalRevenue = filteredOrders\n .filter(o => !o.refund_status || o.refund_status === null)\n .reduce((sum, o) => sum + o.price, 0);\n\n return {\n totalOrders,\n confirmedOrders,\n refundedOrders,\n checkedInOrders,\n totalRevenue\n };\n };\n\n const stats = getOrderStats();\n\n if (loading) {\n return (\n <div className=\"flex items-center justify-center py-12\">\n <div className=\"animate-spin rounded-full h-8 w-8 border-b-2 border-white\"></div>\n </div>\n );\n }\n\n return (\n <div className=\"space-y-6\">\n <div className=\"flex justify-between items-center\">\n <h2 className=\"text-2xl font-light text-white\">Orders & Sales</h2>\n <div className=\"flex items-center gap-3\">\n <button\n onClick={() => handleExport('csv')}\n disabled={exporting}\n className=\"flex items-center gap-2 px-4 py-2 bg-white/10 hover:bg-white/20 text-white rounded-lg transition-colors disabled:opacity-50\"\n >\n <svg className=\"w-4 h-4\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M12 10v6m0 0l-3-3m3 3l3-3m2 8H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z\" />\n </svg>\n {exporting ? 'Exporting...' : 'Export CSV'}\n </button>\n </div>\n </div>\n\n {/* Stats Cards */}\n <div className=\"grid grid-cols-1 md:grid-cols-5 gap-4\">\n <div className=\"bg-white/10 backdrop-blur-xl border border-white/20 rounded-xl p-4\">\n <div className=\"text-sm text-white/60\">Total Orders</div>\n <div className=\"text-2xl font-bold text-white\">{stats.totalOrders}</div>\n </div>\n <div className=\"bg-white/10 backdrop-blur-xl border border-white/20 rounded-xl p-4\">\n <div className=\"text-sm text-white/60\">Confirmed</div>\n <div className=\"text-2xl font-bold text-green-400\">{stats.confirmedOrders}</div>\n </div>\n <div className=\"bg-white/10 backdrop-blur-xl border border-white/20 rounded-xl p-4\">\n <div className=\"text-sm text-white/60\">Refunded</div>\n <div className=\"text-2xl font-bold text-red-400\">{stats.refundedOrders}</div>\n </div>\n <div className=\"bg-white/10 backdrop-blur-xl border border-white/20 rounded-xl p-4\">\n <div className=\"text-sm text-white/60\">Checked In</div>\n <div className=\"text-2xl font-bold text-blue-400\">{stats.checkedInOrders}</div>\n </div>\n <div className=\"bg-white/10 backdrop-blur-xl border border-white/20 rounded-xl p-4\">\n <div className=\"text-sm text-white/60\">Revenue</div>\n <div className=\"text-2xl font-bold text-white\">{formatCurrency(stats.totalRevenue)}</div>\n </div>\n </div>\n\n {/* Filters */}\n <div className=\"bg-white/5 border border-white/20 rounded-xl p-6\">\n <h3 className=\"text-lg font-semibold text-white mb-4\">Filters</h3>\n <div className=\"grid grid-cols-1 md:grid-cols-4 gap-4\">\n <div>\n <label className=\"block text-sm text-white/80 mb-2\">Search</label>\n <input\n type=\"text\"\n value={searchTerm}\n onChange={(e) => setSearchTerm(e.target.value)}\n placeholder=\"Name, email, or ticket ID...\"\n className=\"w-full px-3 py-2 bg-white/10 border border-white/20 rounded-lg text-white placeholder-white/50 focus:ring-2 focus:ring-blue-500 focus:border-transparent\"\n />\n </div>\n <div>\n <label className=\"block text-sm text-white/80 mb-2\">Ticket Type</label>\n <select\n value={filters.ticketTypeId || ''}\n onChange={(e) => handleFilterChange('ticketTypeId', e.target.value || undefined)}\n className=\"w-full px-3 py-2 bg-white/10 border border-white/20 rounded-lg text-white focus:ring-2 focus:ring-blue-500 focus:border-transparent\"\n >\n <option value=\"\">All Ticket Types</option>\n {ticketTypes.map(type => (\n <option key={type.id} value={type.id}>{type.name}</option>\n ))}\n </select>\n </div>\n <div>\n <label className=\"block text-sm text-white/80 mb-2\">Status</label>\n <select\n value={filters.status || ''}\n onChange={(e) => handleFilterChange('status', e.target.value || undefined)}\n className=\"w-full px-3 py-2 bg-white/10 border border-white/20 rounded-lg text-white focus:ring-2 focus:ring-blue-500 focus:border-transparent\"\n >\n <option value=\"\">All Statuses</option>\n <option value=\"confirmed\">Confirmed</option>\n <option value=\"pending\">Pending</option>\n <option value=\"refunded\">Refunded</option>\n <option value=\"cancelled\">Cancelled</option>\n </select>\n </div>\n <div>\n <label className=\"block text-sm text-white/80 mb-2\">Check-in Status</label>\n <select\n value={filters.checkedIn === undefined ? '' : filters.checkedIn.toString()}\n onChange={(e) => handleFilterChange('checkedIn', e.target.value === '' ? undefined : e.target.value === 'true')}\n className=\"w-full px-3 py-2 bg-white/10 border border-white/20 rounded-lg text-white focus:ring-2 focus:ring-blue-500 focus:border-transparent\"\n >\n <option value=\"\">All</option>\n <option value=\"true\">Checked In</option>\n <option value=\"false\">Not Checked In</option>\n </select>\n </div>\n </div>\n <div className=\"mt-4 flex justify-end\">\n <button\n onClick={clearFilters}\n className=\"px-4 py-2 text-white/80 hover:text-white transition-colors\"\n >\n Clear Filters\n </button>\n </div>\n </div>\n\n {/* Orders Table */}\n <div className=\"bg-white/5 border border-white/20 rounded-xl p-6\">\n <OrdersTable\n orders={filteredOrders}\n onViewOrder={handleViewOrder}\n onRefundOrder={handleRefundOrder}\n onCheckIn={handleCheckInOrder}\n showActions={true}\n showCheckIn={true}\n />\n </div>\n\n {/* Order Details Modal */}\n {showOrderDetails && selectedOrder && (\n <div className=\"fixed inset-0 bg-black/50 backdrop-blur-sm flex items-center justify-center p-4 z-50\">\n <div className=\"bg-white/10 backdrop-blur-xl border border-white/20 rounded-2xl shadow-2xl max-w-2xl w-full max-h-[90vh] overflow-y-auto\">\n <div className=\"p-6\">\n <div className=\"flex justify-between items-center mb-6\">\n <h3 className=\"text-2xl font-light text-white\">Order Details</h3>\n <button\n onClick={() => setShowOrderDetails(false)}\n className=\"text-white/60 hover:text-white transition-colors\"\n >\n <svg className=\"w-6 h-6\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M6 18L18 6M6 6l12 12\" />\n </svg>\n </button>\n </div>\n\n <div className=\"space-y-6\">\n <div className=\"grid grid-cols-2 gap-6\">\n <div>\n <h4 className=\"text-lg font-semibold text-white mb-3\">Customer Information</h4>\n <div className=\"space-y-2\">\n <div>\n <span className=\"text-white/60 text-sm\">Name:</span>\n <div className=\"text-white font-medium\">{selectedOrder.purchaser_name}</div>\n </div>\n <div>\n <span className=\"text-white/60 text-sm\">Email:</span>\n <div className=\"text-white\">{selectedOrder.purchaser_email}</div>\n </div>\n </div>\n </div>\n\n <div>\n <h4 className=\"text-lg font-semibold text-white mb-3\">Order Information</h4>\n <div className=\"space-y-2\">\n <div>\n <span className=\"text-white/60 text-sm\">Order ID:</span>\n <div className=\"text-white font-mono text-sm\">{selectedOrder.id}</div>\n </div>\n <div>\n <span className=\"text-white/60 text-sm\">Ticket ID:</span>\n <div className=\"text-white font-mono text-sm\">{selectedOrder.uuid}</div>\n </div>\n <div>\n <span className=\"text-white/60 text-sm\">Purchase Date:</span>\n <div className=\"text-white\">{new Date(selectedOrder.created_at).toLocaleString()}</div>\n </div>\n </div>\n </div>\n </div>\n\n <div>\n <h4 className=\"text-lg font-semibold text-white mb-3\">Ticket Details</h4>\n <div className=\"bg-white/5 border border-white/20 rounded-lg p-4\">\n <div className=\"grid grid-cols-3 gap-4\">\n <div>\n <span className=\"text-white/60 text-sm\">Ticket Type:</span>\n <div className=\"text-white font-medium\">{selectedOrder.ticket_types.name}</div>\n </div>\n <div>\n <span className=\"text-white/60 text-sm\">Price Paid:</span>\n <div className=\"text-white font-bold\">{formatCurrency(selectedOrder.price)}</div>\n </div>\n <div>\n <span className=\"text-white/60 text-sm\">Status:</span>\n <div className={`font-medium ${\n (!selectedOrder.refund_status || selectedOrder.refund_status === null) ? 'text-green-400' :\n selectedOrder.refund_status === 'completed' ? 'text-red-400' :\n 'text-yellow-400'\n }`}>\n {(!selectedOrder.refund_status || selectedOrder.refund_status === null) ? 'Confirmed' :\n selectedOrder.refund_status === 'completed' ? 'Refunded' :\n selectedOrder.refund_status === 'pending' ? 'Pending' : 'Failed'}\n </div>\n </div>\n </div>\n </div>\n </div>\n\n <div>\n <h4 className=\"text-lg font-semibold text-white mb-3\">Check-in Status</h4>\n <div className=\"bg-white/5 border border-white/20 rounded-lg p-4\">\n {selectedOrder.checked_in ? (\n <div className=\"flex items-center text-green-400\">\n <svg className=\"w-5 h-5 mr-2\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M5 13l4 4L19 7\" />\n </svg>\n Checked In\n </div>\n ) : (\n <div className=\"flex items-center justify-between\">\n <div className=\"flex items-center text-white/60\">\n <svg className=\"w-5 h-5 mr-2\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z\" />\n </svg>\n Not Checked In\n </div>\n {(!selectedOrder.refund_status || selectedOrder.refund_status === null) && (\n <button\n onClick={() => {\n handleCheckInOrder(selectedOrder);\n setShowOrderDetails(false);\n }}\n className=\"px-4 py-2 rounded-lg transition-all duration-200 hover:shadow-lg hover:scale-105\"\n style={{\n background: 'var(--success-color)',\n color: 'white'\n }}\n >\n Check In Now\n </button>\n )}\n </div>\n )}\n </div>\n </div>\n\n <div className=\"flex justify-end space-x-3\">\n <button\n onClick={() => setShowOrderDetails(false)}\n className=\"px-6 py-3 text-white/80 hover:text-white transition-colors\"\n >\n Close\n </button>\n {selectedOrder.status === 'confirmed' && (\n <button\n onClick={() => {\n handleRefundOrder(selectedOrder);\n setShowOrderDetails(false);\n }}\n className=\"px-6 py-3 rounded-lg transition-all duration-200 hover:shadow-lg hover:scale-105\"\n style={{\n background: 'var(--error-color)',\n color: 'white'\n }}\n >\n Refund Order\n </button>\n )}\n </div>\n </div>\n </div>\n </div>\n </div>\n )}\n </div>\n );\n}","usedDeprecatedRules":[]},{"filePath":"/home/tyler/apps/bct-whitelabel/src/components/manage/PresaleTab.tsx","messages":[{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":55,"column":14,"nodeType":null,"messageId":"unusedVar","endLine":55,"endColumn":19},{"ruleId":"no-empty","severity":1,"message":"Empty block statement.","line":55,"column":21,"nodeType":"BlockStatement","messageId":"unexpected","endLine":57,"endColumn":6,"suggestions":[{"messageId":"suggestComment","data":{"type":"block"},"fix":{"range":[1513,1519],"text":" /* empty */ "},"desc":"Add comment inside empty block statement."}]},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":125,"column":14,"nodeType":null,"messageId":"unusedVar","endLine":125,"endColumn":19},{"ruleId":"no-empty","severity":1,"message":"Empty block statement.","line":125,"column":21,"nodeType":"BlockStatement","messageId":"unexpected","endLine":127,"endColumn":6,"suggestions":[{"messageId":"suggestComment","data":{"type":"block"},"fix":{"range":[3187,3193],"text":" /* empty */ "},"desc":"Add comment inside empty block statement."}]},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":142,"column":16,"nodeType":null,"messageId":"unusedVar","endLine":142,"endColumn":21},{"ruleId":"no-empty","severity":1,"message":"Empty block statement.","line":142,"column":23,"nodeType":"BlockStatement","messageId":"unexpected","endLine":144,"endColumn":8,"suggestions":[{"messageId":"suggestComment","data":{"type":"block"},"fix":{"range":[3598,3606],"text":" /* empty */ "},"desc":"Add comment inside empty block statement."}]},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":157,"column":14,"nodeType":null,"messageId":"unusedVar","endLine":157,"endColumn":19},{"ruleId":"no-empty","severity":1,"message":"Empty block statement.","line":157,"column":21,"nodeType":"BlockStatement","messageId":"unexpected","endLine":159,"endColumn":6,"suggestions":[{"messageId":"suggestComment","data":{"type":"block"},"fix":{"range":[3912,3918],"text":" /* empty */ "},"desc":"Add comment inside empty block statement."}]}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":8,"fixableErrorCount":0,"fixableWarningCount":0,"source":"import { useState, useEffect } from 'react';\nimport { createClient } from '@supabase/supabase-js';\nimport { formatCurrency } from '../../lib/event-management';\n\nconst supabase = createClient(\n import.meta.env.PUBLIC_SUPABASE_URL,\n import.meta.env.PUBLIC_SUPABASE_ANON_KEY\n);\n\ninterface PresaleCode {\n id: string;\n code: string;\n discount_type: 'percentage' | 'fixed';\n discount_value: number;\n max_uses: number;\n uses_count: number;\n expires_at: string;\n is_active: boolean;\n created_at: string;\n}\n\ninterface PresaleTabProps {\n eventId: string;\n}\n\nexport default function PresaleTab({ eventId }: PresaleTabProps) {\n const [presaleCodes, setPresaleCodes] = useState<PresaleCode[]>([]);\n const [showModal, setShowModal] = useState(false);\n const [editingCode, setEditingCode] = useState<PresaleCode | null>(null);\n const [formData, setFormData] = useState({\n code: '',\n discount_type: 'percentage' as 'percentage' | 'fixed',\n discount_value: 10,\n max_uses: 100,\n expires_at: ''\n });\n const [loading, setLoading] = useState(true);\n const [saving, setSaving] = useState(false);\n\n useEffect(() => {\n loadPresaleCodes();\n }, [eventId]);\n\n const loadPresaleCodes = async () => {\n setLoading(true);\n try {\n const { data, error } = await supabase\n .from('presale_codes')\n .select('*')\n .eq('event_id', eventId)\n .order('created_at', { ascending: false });\n\n if (error) throw error;\n setPresaleCodes(data || []);\n } catch (error) {\n\n } finally {\n setLoading(false);\n }\n };\n\n const generateCode = () => {\n const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';\n let result = '';\n for (let i = 0; i < 8; i++) {\n result += chars.charAt(Math.floor(Math.random() * chars.length));\n }\n return result;\n };\n\n const handleCreateCode = () => {\n setEditingCode(null);\n setFormData({\n code: generateCode(),\n discount_type: 'percentage',\n discount_value: 10,\n max_uses: 100,\n expires_at: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000).toISOString().split('T')[0]\n });\n setShowModal(true);\n };\n\n const handleEditCode = (code: PresaleCode) => {\n setEditingCode(code);\n setFormData({\n code: code.code,\n discount_type: code.discount_type,\n discount_value: code.discount_value,\n max_uses: code.max_uses,\n expires_at: code.expires_at.split('T')[0]\n });\n setShowModal(true);\n };\n\n const handleSaveCode = async () => {\n setSaving(true);\n try {\n const codeData = {\n ...formData,\n event_id: eventId,\n expires_at: new Date(formData.expires_at).toISOString()\n };\n\n if (editingCode) {\n const { error } = await supabase\n .from('presale_codes')\n .update(codeData)\n .eq('id', editingCode.id);\n\n if (error) throw error;\n } else {\n const { error } = await supabase\n .from('presale_codes')\n .insert({\n ...codeData,\n is_active: true,\n uses_count: 0\n });\n\n if (error) throw error;\n }\n\n setShowModal(false);\n loadPresaleCodes();\n } catch (error) {\n\n } finally {\n setSaving(false);\n }\n };\n\n const handleDeleteCode = async (code: PresaleCode) => {\n if (confirm(`Are you sure you want to delete the code \"${code.code}\"?`)) {\n try {\n const { error } = await supabase\n .from('presale_codes')\n .delete()\n .eq('id', code.id);\n\n if (error) throw error;\n loadPresaleCodes();\n } catch (error) {\n\n }\n }\n };\n\n const handleToggleCode = async (code: PresaleCode) => {\n try {\n const { error } = await supabase\n .from('presale_codes')\n .update({ is_active: !code.is_active })\n .eq('id', code.id);\n\n if (error) throw error;\n loadPresaleCodes();\n } catch (error) {\n\n }\n };\n\n const formatDiscount = (type: string, value: number) => {\n return type === 'percentage' ? `${value}%` : formatCurrency(value * 100);\n };\n\n const isExpired = (expiresAt: string) => {\n return new Date(expiresAt) < new Date();\n };\n\n if (loading) {\n return (\n <div className=\"flex items-center justify-center py-12\">\n <div className=\"animate-spin rounded-full h-8 w-8 border-b-2 border-white\"></div>\n </div>\n );\n }\n\n return (\n <div className=\"space-y-6\">\n <div className=\"flex justify-between items-center\">\n <h2 className=\"text-2xl font-light text-white\">Presale Codes</h2>\n <button\n onClick={handleCreateCode}\n className=\"flex items-center gap-2 px-4 py-2 rounded-lg font-medium transition-all duration-200\"\n style={{\n background: 'var(--glass-text-accent)',\n color: 'white'\n }}\n >\n <svg className=\"w-4 h-4\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M12 6v6m0 0v6m0-6h6m-6 0H6\" />\n </svg>\n Create Presale Code\n </button>\n </div>\n\n {presaleCodes.length === 0 ? (\n <div className=\"text-center py-12\">\n <svg className=\"w-12 h-12 text-white/40 mx-auto mb-4\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M7 7h.01M7 3h5c.512 0 1.024.195 1.414.586l7 7a2 2 0 010 2.828l-7 7a2 2 0 01-2.828 0l-7-7A1.994 1.994 0 013 12V7a4 4 0 014-4z\" />\n </svg>\n <p className=\"text-white/60 mb-4\">No presale codes created yet</p>\n <button\n onClick={handleCreateCode}\n className=\"px-6 py-3 rounded-lg font-medium transition-all duration-200\"\n style={{\n background: 'var(--glass-text-accent)',\n color: 'white'\n }}\n >\n Create Your First Presale Code\n </button>\n </div>\n ) : (\n <div className=\"grid grid-cols-1 lg:grid-cols-2 gap-6\">\n {presaleCodes.map((code) => (\n <div key={code.id} className=\"bg-white/5 border border-white/20 rounded-xl p-6 hover:bg-white/10 transition-all duration-200\">\n <div className=\"flex justify-between items-start mb-4\">\n <div className=\"flex-1\">\n <div className=\"flex items-center gap-3 mb-2\">\n <div className=\"text-2xl font-bold text-white font-mono\">{code.code}</div>\n <div className=\"flex items-center gap-2\">\n <span className={`px-2 py-1 text-xs rounded-full ${\n code.is_active \n ? 'bg-green-500/20 text-green-300 border border-green-500/30' \n : 'bg-red-500/20 text-red-300 border border-red-500/30'\n }`}>\n {code.is_active ? 'Active' : 'Inactive'}\n </span>\n {isExpired(code.expires_at) && (\n <span className=\"px-2 py-1 text-xs bg-red-500/20 text-red-300 border border-red-500/30 rounded-full\">\n Expired\n </span>\n )}\n </div>\n </div>\n <div className=\"text-3xl font-bold text-purple-400 mb-2\">\n {formatDiscount(code.discount_type, code.discount_value)} OFF\n </div>\n </div>\n <div className=\"flex items-center gap-2\">\n <button\n onClick={() => handleEditCode(code)}\n className=\"p-2 text-white/60 hover:text-white transition-colors\"\n title=\"Edit\"\n >\n <svg className=\"w-4 h-4\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z\" />\n </svg>\n </button>\n <button\n onClick={() => handleToggleCode(code)}\n className=\"p-2 text-white/60 hover:text-white transition-colors\"\n title={code.is_active ? 'Deactivate' : 'Activate'}\n >\n {code.is_active ? (\n <svg className=\"w-4 h-4\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M13.875 18.825A10.05 10.05 0 0112 19c-4.478 0-8.268-2.943-9.543-7a9.97 9.97 0 011.563-3.029m5.858.908a3 3 0 114.142 4.142M9.878 9.878l4.242 4.242M9.878 9.878L3 3m6.878 6.878L21 21\" />\n </svg>\n ) : (\n <svg className=\"w-4 h-4\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M15 12a3 3 0 11-6 0 3 3 0 016 0z\" />\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z\" />\n </svg>\n )}\n </button>\n <button\n onClick={() => handleDeleteCode(code)}\n className=\"p-2 text-white/60 hover:text-red-400 transition-colors\"\n title=\"Delete\"\n >\n <svg className=\"w-4 h-4\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16\" />\n </svg>\n </button>\n </div>\n </div>\n\n <div className=\"space-y-3\">\n <div className=\"grid grid-cols-2 gap-4 text-sm\">\n <div>\n <div className=\"text-white/60\">Uses</div>\n <div className=\"text-white font-semibold\">\n {code.uses_count} / {code.max_uses}\n </div>\n </div>\n <div>\n <div className=\"text-white/60\">Expires</div>\n <div className=\"text-white font-semibold\">\n {new Date(code.expires_at).toLocaleDateString()}\n </div>\n </div>\n </div>\n\n <div className=\"w-full bg-white/10 rounded-full h-2\">\n <div \n className=\"bg-gradient-to-r from-purple-500 to-pink-500 h-2 rounded-full transition-all duration-300\"\n style={{ width: `${(code.uses_count / code.max_uses) * 100}%` }}\n />\n </div>\n <div className=\"text-xs text-white/60\">\n {((code.uses_count / code.max_uses) * 100).toFixed(1)}% used\n </div>\n </div>\n </div>\n ))}\n </div>\n )}\n\n {/* Modal */}\n {showModal && (\n <div className=\"fixed inset-0 bg-black/50 backdrop-blur-sm flex items-center justify-center p-4 z-50\">\n <div className=\"bg-white/10 backdrop-blur-xl border border-white/20 rounded-2xl shadow-2xl max-w-md w-full\">\n <div className=\"p-6\">\n <div className=\"flex justify-between items-center mb-6\">\n <h3 className=\"text-2xl font-light text-white\">\n {editingCode ? 'Edit Presale Code' : 'Create Presale Code'}\n </h3>\n <button\n onClick={() => setShowModal(false)}\n className=\"text-white/60 hover:text-white transition-colors\"\n >\n <svg className=\"w-6 h-6\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M6 18L18 6M6 6l12 12\" />\n </svg>\n </button>\n </div>\n\n <div className=\"space-y-4\">\n <div>\n <label className=\"block text-sm font-medium text-white/80 mb-2\">Code</label>\n <div className=\"flex\">\n <input\n type=\"text\"\n value={formData.code}\n onChange={(e) => setFormData(prev => ({ ...prev, code: e.target.value.toUpperCase() }))}\n className=\"flex-1 px-4 py-3 bg-white/10 border border-white/20 rounded-l-lg text-white placeholder-white/50 focus:ring-2 focus:ring-blue-500 focus:border-transparent font-mono\"\n placeholder=\"CODE123\"\n />\n <button\n onClick={() => setFormData(prev => ({ ...prev, code: generateCode() }))}\n className=\"px-4 py-3 bg-white/20 hover:bg-white/30 text-white rounded-r-lg border border-l-0 border-white/20 transition-colors\"\n title=\"Generate Random Code\"\n >\n <svg className=\"w-4 h-4\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15\" />\n </svg>\n </button>\n </div>\n </div>\n\n <div>\n <label className=\"block text-sm font-medium text-white/80 mb-2\">Discount Type</label>\n <select\n value={formData.discount_type}\n onChange={(e) => setFormData(prev => ({ ...prev, discount_type: e.target.value as 'percentage' | 'fixed' }))}\n className=\"w-full px-4 py-3 bg-white/10 border border-white/20 rounded-lg text-white focus:ring-2 focus:ring-blue-500 focus:border-transparent\"\n >\n <option value=\"percentage\">Percentage</option>\n <option value=\"fixed\">Fixed Amount</option>\n </select>\n </div>\n\n <div>\n <label className=\"block text-sm font-medium text-white/80 mb-2\">\n Discount Value {formData.discount_type === 'percentage' ? '(%)' : '($)'}\n </label>\n <input\n type=\"number\"\n value={formData.discount_value}\n onChange={(e) => setFormData(prev => ({ ...prev, discount_value: parseFloat(e.target.value) || 0 }))}\n className=\"w-full px-4 py-3 bg-white/10 border border-white/20 rounded-lg text-white placeholder-white/50 focus:ring-2 focus:ring-blue-500 focus:border-transparent\"\n min=\"0\"\n step={formData.discount_type === 'percentage' ? \"1\" : \"0.01\"}\n max={formData.discount_type === 'percentage' ? \"100\" : undefined}\n />\n </div>\n\n <div className=\"grid grid-cols-2 gap-4\">\n <div>\n <label className=\"block text-sm font-medium text-white/80 mb-2\">Max Uses</label>\n <input\n type=\"number\"\n value={formData.max_uses}\n onChange={(e) => setFormData(prev => ({ ...prev, max_uses: parseInt(e.target.value) || 0 }))}\n className=\"w-full px-4 py-3 bg-white/10 border border-white/20 rounded-lg text-white placeholder-white/50 focus:ring-2 focus:ring-blue-500 focus:border-transparent\"\n min=\"1\"\n />\n </div>\n\n <div>\n <label className=\"block text-sm font-medium text-white/80 mb-2\">Expires On</label>\n <input\n type=\"date\"\n value={formData.expires_at}\n onChange={(e) => setFormData(prev => ({ ...prev, expires_at: e.target.value }))}\n className=\"w-full px-4 py-3 bg-white/10 border border-white/20 rounded-lg text-white focus:ring-2 focus:ring-blue-500 focus:border-transparent\"\n />\n </div>\n </div>\n </div>\n\n <div className=\"flex justify-end space-x-3 mt-6\">\n <button\n onClick={() => setShowModal(false)}\n className=\"px-6 py-3 text-white/80 hover:text-white transition-colors\"\n >\n Cancel\n </button>\n <button\n onClick={handleSaveCode}\n disabled={saving}\n className=\"px-6 py-3 rounded-lg font-medium transition-all duration-200 disabled:opacity-50\"\n style={{\n background: 'var(--glass-text-accent)',\n color: 'white'\n }}\n >\n {saving ? 'Saving...' : editingCode ? 'Update' : 'Create'}\n </button>\n </div>\n </div>\n </div>\n </div>\n )}\n </div>\n );\n}","usedDeprecatedRules":[]},{"filePath":"/home/tyler/apps/bct-whitelabel/src/components/manage/PrintedTab.tsx","messages":[{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'formatCurrency' is defined but never used. Allowed unused vars must match /^_/u.","line":3,"column":10,"nodeType":null,"messageId":"unusedVar","endLine":3,"endColumn":24},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":58,"column":14,"nodeType":null,"messageId":"unusedVar","endLine":58,"endColumn":19},{"ruleId":"no-empty","severity":1,"message":"Empty block statement.","line":58,"column":21,"nodeType":"BlockStatement","messageId":"unexpected","endLine":60,"endColumn":6,"suggestions":[{"messageId":"suggestComment","data":{"type":"block"},"fix":{"range":[1853,1859],"text":" /* empty */ "},"desc":"Add comment inside empty block statement."}]},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":132,"column":14,"nodeType":null,"messageId":"unusedVar","endLine":132,"endColumn":19},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":164,"column":14,"nodeType":null,"messageId":"unusedVar","endLine":164,"endColumn":19},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":180,"column":16,"nodeType":null,"messageId":"unusedVar","endLine":180,"endColumn":21},{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":553,"column":96,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":553,"endColumn":99,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[23987,23990],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[23987,23990],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":7,"fixableErrorCount":0,"fixableWarningCount":0,"source":"import { useState, useEffect } from 'react';\nimport { createClient } from '@supabase/supabase-js';\nimport { formatCurrency } from '../../lib/event-management';\nimport TicketPreviewModal from '../modals/TicketPreviewModal';\n\nconst supabase = createClient(\n import.meta.env.PUBLIC_SUPABASE_URL,\n import.meta.env.PUBLIC_SUPABASE_ANON_KEY\n);\n\ninterface PrintedTicket {\n id: string;\n event_id: string;\n barcode: string;\n status: 'pending' | 'printed' | 'distributed' | 'used';\n notes: string;\n created_at: string;\n updated_at: string;\n}\n\ninterface PrintedTabProps {\n eventId: string;\n organizationId: string;\n}\n\nexport default function PrintedTab({ eventId, organizationId }: PrintedTabProps) {\n const [printedTickets, setPrintedTickets] = useState<PrintedTicket[]>([]);\n const [showModal, setShowModal] = useState(false);\n const [showPreviewModal, setShowPreviewModal] = useState(false);\n const [barcodeMethod, setBarcodeMethod] = useState<'generate' | 'manual'>('generate');\n const [barcodeData, setBarcodeData] = useState({\n startNumber: 1,\n quantity: 100,\n prefix: 'BCT',\n padding: 6\n });\n const [manualBarcodes, setManualBarcodes] = useState('');\n const [editingTicket, setEditingTicket] = useState<PrintedTicket | null>(null);\n const [editForm, setEditForm] = useState({ status: 'pending', notes: '' });\n const [loading, setLoading] = useState(true);\n const [processing, setProcessing] = useState(false);\n\n useEffect(() => {\n loadPrintedTickets();\n }, [eventId]);\n\n const loadPrintedTickets = async () => {\n setLoading(true);\n try {\n const { data, error } = await supabase\n .from('printed_tickets')\n .select('*')\n .eq('event_id', eventId)\n .order('created_at', { ascending: false });\n\n if (error) throw error;\n setPrintedTickets(data || []);\n } catch (error) {\n\n } finally {\n setLoading(false);\n }\n };\n\n const generateBarcodes = (start: number, quantity: number, prefix: string, padding: number) => {\n const barcodes = [];\n for (let i = 0; i < quantity; i++) {\n const number = start + i;\n const paddedNumber = number.toString().padStart(padding, '0');\n barcodes.push(`${prefix}${paddedNumber}`);\n }\n return barcodes;\n };\n\n const handleCreateTickets = async () => {\n setProcessing(true);\n try {\n let barcodes: string[] = [];\n\n if (barcodeMethod === 'generate') {\n barcodes = generateBarcodes(\n barcodeData.startNumber,\n barcodeData.quantity,\n barcodeData.prefix,\n barcodeData.padding\n );\n } else {\n barcodes = manualBarcodes\n .split('\\n')\n .map(line => line.trim())\n .filter(line => line.length > 0);\n }\n\n if (barcodes.length === 0) {\n alert('Please provide at least one barcode');\n return;\n }\n\n // Check for duplicate barcodes\n const existingBarcodes = printedTickets.map(ticket => ticket.barcode);\n const duplicates = barcodes.filter(barcode => existingBarcodes.includes(barcode));\n \n if (duplicates.length > 0) {\n alert(`Duplicate barcodes found: ${duplicates.join(', ')}`);\n return;\n }\n\n const ticketsToInsert = barcodes.map(barcode => ({\n event_id: eventId,\n barcode,\n status: 'pending' as const,\n notes: ''\n }));\n\n const { error } = await supabase\n .from('printed_tickets')\n .insert(ticketsToInsert);\n\n if (error) throw error;\n\n setShowModal(false);\n loadPrintedTickets();\n \n // Reset form\n setBarcodeData({\n startNumber: 1,\n quantity: 100,\n prefix: 'BCT',\n padding: 6\n });\n setManualBarcodes('');\n } catch (error) {\n\n alert('Failed to create printed tickets');\n } finally {\n setProcessing(false);\n }\n };\n\n const handleEditTicket = (ticket: PrintedTicket) => {\n setEditingTicket(ticket);\n setEditForm({\n status: ticket.status,\n notes: ticket.notes\n });\n };\n\n const handleUpdateTicket = async () => {\n if (!editingTicket) return;\n\n try {\n const { error } = await supabase\n .from('printed_tickets')\n .update({\n status: editForm.status,\n notes: editForm.notes\n })\n .eq('id', editingTicket.id);\n\n if (error) throw error;\n\n setEditingTicket(null);\n loadPrintedTickets();\n } catch (error) {\n\n alert('Failed to update printed ticket');\n }\n };\n\n const handleDeleteTicket = async (ticket: PrintedTicket) => {\n if (confirm(`Are you sure you want to delete the printed ticket \"${ticket.barcode}\"?`)) {\n try {\n const { error } = await supabase\n .from('printed_tickets')\n .delete()\n .eq('id', ticket.id);\n\n if (error) throw error;\n loadPrintedTickets();\n } catch (error) {\n\n alert('Failed to delete printed ticket');\n }\n }\n };\n\n const getStatusColor = (status: string) => {\n switch (status) {\n case 'pending':\n return 'bg-yellow-500/20 text-yellow-300 border-yellow-500/30';\n case 'printed':\n return 'bg-blue-500/20 text-blue-300 border-blue-500/30';\n case 'distributed':\n return 'bg-green-500/20 text-green-300 border-green-500/30';\n case 'used':\n return 'bg-gray-500/20 text-gray-300 border-gray-500/30';\n default:\n return 'bg-white/20 text-white border-white/30';\n }\n };\n\n const getStatusIcon = (status: string) => {\n switch (status) {\n case 'pending':\n return (\n <svg className=\"w-4 h-4\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z\" />\n </svg>\n );\n case 'printed':\n return (\n <svg className=\"w-4 h-4\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M17 17h2a2 2 0 002-2v-4a2 2 0 00-2-2H5a2 2 0 00-2 2v4a2 2 0 002 2h2m2 4h6a2 2 0 002-2v-4a2 2 0 00-2-2H9a2 2 0 00-2 2v4a2 2 0 002 2zm8-12V5a2 2 0 00-2-2H9a2 2 0 00-2 2v4h10z\" />\n </svg>\n );\n case 'distributed':\n return (\n <svg className=\"w-4 h-4\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M20 7l-8-4-8 4m16 0l-8 4m8-4v10l-8 4m0-10L4 7m8 4v10M4 7v10l8 4\" />\n </svg>\n );\n case 'used':\n return (\n <svg className=\"w-4 h-4\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M5 13l4 4L19 7\" />\n </svg>\n );\n default:\n return null;\n }\n };\n\n const getStatusStats = () => {\n const stats = {\n total: printedTickets.length,\n pending: printedTickets.filter(t => t.status === 'pending').length,\n printed: printedTickets.filter(t => t.status === 'printed').length,\n distributed: printedTickets.filter(t => t.status === 'distributed').length,\n used: printedTickets.filter(t => t.status === 'used').length\n };\n return stats;\n };\n\n const stats = getStatusStats();\n\n if (loading) {\n return (\n <div className=\"flex items-center justify-center py-12\">\n <div className=\"animate-spin rounded-full h-8 w-8 border-b-2 border-white\"></div>\n </div>\n );\n }\n\n return (\n <div className=\"space-y-6\">\n <div className=\"flex justify-between items-center\">\n <div>\n <h2 className=\"text-2xl font-light text-white mb-2\">Printed Tickets</h2>\n <p className=\"text-white/80\">Manage barcodes for printed tickets</p>\n </div>\n <div className=\"flex gap-3\">\n <button\n onClick={() => setShowPreviewModal(true)}\n className=\"bg-white/10 hover:bg-white/20 text-white px-6 py-3 rounded-xl font-medium transition-all duration-200 backdrop-blur-sm flex items-center gap-2\"\n >\n <svg className=\"w-4 h-4\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M15 12a3 3 0 11-6 0 3 3 0 016 0z\" />\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z\" />\n </svg>\n Preview Sample Ticket\n </button>\n <button\n onClick={() => setShowModal(true)}\n className=\"px-6 py-3 rounded-xl font-medium transition-all duration-200 flex items-center gap-2\"\n style={{\n background: 'var(--glass-text-accent)',\n color: 'white'\n }}\n >\n <svg className=\"w-4 h-4\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M12 4v16m8-8H4\" />\n </svg>\n Add Printed Tickets\n </button>\n </div>\n </div>\n\n {/* Stats Cards */}\n <div className=\"grid grid-cols-2 md:grid-cols-5 gap-4\">\n <div className=\"bg-white/10 backdrop-blur-xl border border-white/20 rounded-xl p-4\">\n <div className=\"text-sm text-white/60\">Total</div>\n <div className=\"text-2xl font-bold text-white\">{stats.total}</div>\n </div>\n <div className=\"bg-white/10 backdrop-blur-xl border border-white/20 rounded-xl p-4\">\n <div className=\"text-sm text-white/60\">Pending</div>\n <div className=\"text-2xl font-bold text-yellow-400\">{stats.pending}</div>\n </div>\n <div className=\"bg-white/10 backdrop-blur-xl border border-white/20 rounded-xl p-4\">\n <div className=\"text-sm text-white/60\">Printed</div>\n <div className=\"text-2xl font-bold text-blue-400\">{stats.printed}</div>\n </div>\n <div className=\"bg-white/10 backdrop-blur-xl border border-white/20 rounded-xl p-4\">\n <div className=\"text-sm text-white/60\">Distributed</div>\n <div className=\"text-2xl font-bold text-green-400\">{stats.distributed}</div>\n </div>\n <div className=\"bg-white/10 backdrop-blur-xl border border-white/20 rounded-xl p-4\">\n <div className=\"text-sm text-white/60\">Used</div>\n <div className=\"text-2xl font-bold text-gray-400\">{stats.used}</div>\n </div>\n </div>\n\n {/* Tickets Table */}\n <div className=\"bg-white/5 border border-white/20 rounded-xl overflow-hidden\">\n {printedTickets.length === 0 ? (\n <div className=\"text-center py-12\">\n <svg className=\"w-12 h-12 text-white/40 mx-auto mb-4\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M15 5v2m0 4v2m0 4v2M5 5a2 2 0 00-2 2v3a2 2 0 110 4v3a2 2 0 002 2h14a2 2 0 002-2v-3a2 2 0 110-4V7a2 2 0 00-2-2H5z\" />\n </svg>\n <p className=\"text-white/60 mb-4\">No printed tickets created yet</p>\n <button\n onClick={() => setShowModal(true)}\n className=\"px-6 py-3 bg-gradient-to-r from-blue-600 to-purple-600 hover:from-blue-700 hover:to-purple-700 text-white rounded-lg font-medium transition-all duration-200\"\n >\n Create Printed Tickets\n </button>\n </div>\n ) : (\n <div className=\"overflow-x-auto\">\n <table className=\"w-full\">\n <thead className=\"bg-white/10\">\n <tr>\n <th className=\"text-left py-3 px-4 text-white/80 font-medium\">Barcode</th>\n <th className=\"text-left py-3 px-4 text-white/80 font-medium\">Status</th>\n <th className=\"text-left py-3 px-4 text-white/80 font-medium\">Notes</th>\n <th className=\"text-left py-3 px-4 text-white/80 font-medium\">Created</th>\n <th className=\"text-left py-3 px-4 text-white/80 font-medium\">Actions</th>\n </tr>\n </thead>\n <tbody>\n {printedTickets.map((ticket) => (\n <tr key={ticket.id} className=\"border-b border-white/10 hover:bg-white/5 transition-colors\">\n <td className=\"py-3 px-4\">\n <div className=\"font-mono text-white\">{ticket.barcode}</div>\n </td>\n <td className=\"py-3 px-4\">\n <span className={`flex items-center gap-2 px-2 py-1 text-xs rounded-full border ${getStatusColor(ticket.status)}`}>\n {getStatusIcon(ticket.status)}\n {ticket.status.charAt(0).toUpperCase() + ticket.status.slice(1)}\n </span>\n </td>\n <td className=\"py-3 px-4\">\n <div className=\"text-white/80 text-sm\">\n {ticket.notes || '-'}\n </div>\n </td>\n <td className=\"py-3 px-4\">\n <div className=\"text-white/80 text-sm\">\n {new Date(ticket.created_at).toLocaleDateString()}\n </div>\n </td>\n <td className=\"py-3 px-4\">\n <div className=\"flex items-center gap-2\">\n <button\n onClick={() => handleEditTicket(ticket)}\n className=\"p-2 text-white/60 hover:text-white transition-colors\"\n title=\"Edit\"\n >\n <svg className=\"w-4 h-4\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z\" />\n </svg>\n </button>\n <button\n onClick={() => handleDeleteTicket(ticket)}\n className=\"p-2 text-white/60 hover:text-red-400 transition-colors\"\n title=\"Delete\"\n >\n <svg className=\"w-4 h-4\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16\" />\n </svg>\n </button>\n </div>\n </td>\n </tr>\n ))}\n </tbody>\n </table>\n </div>\n )}\n </div>\n\n {/* Create Modal */}\n {showModal && (\n <div className=\"fixed inset-0 bg-black/50 backdrop-blur-sm flex items-center justify-center p-4 z-50\">\n <div className=\"bg-white/10 backdrop-blur-xl border border-white/20 rounded-2xl shadow-2xl max-w-lg w-full\">\n <div className=\"p-6\">\n <div className=\"flex justify-between items-center mb-6\">\n <h3 className=\"text-2xl font-light text-white\">Add Printed Tickets</h3>\n <button\n onClick={() => setShowModal(false)}\n className=\"text-white/60 hover:text-white transition-colors\"\n >\n <svg className=\"w-6 h-6\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M6 18L18 6M6 6l12 12\" />\n </svg>\n </button>\n </div>\n\n <div className=\"space-y-4\">\n <div>\n <label className=\"block text-sm font-medium text-white/80 mb-2\">Input Method</label>\n <div className=\"flex space-x-4\">\n <label className=\"flex items-center\">\n <input\n type=\"radio\"\n value=\"generate\"\n checked={barcodeMethod === 'generate'}\n onChange={(e) => setBarcodeMethod(e.target.value as 'generate' | 'manual')}\n className=\"w-4 h-4 text-blue-600 bg-white/10 border-white/20\"\n />\n <span className=\"ml-2 text-white text-sm\">Generate Sequence</span>\n </label>\n <label className=\"flex items-center\">\n <input\n type=\"radio\"\n value=\"manual\"\n checked={barcodeMethod === 'manual'}\n onChange={(e) => setBarcodeMethod(e.target.value as 'generate' | 'manual')}\n className=\"w-4 h-4 text-blue-600 bg-white/10 border-white/20\"\n />\n <span className=\"ml-2 text-white text-sm\">Manual Input</span>\n </label>\n </div>\n </div>\n\n {barcodeMethod === 'generate' ? (\n <div className=\"space-y-4\">\n <div className=\"grid grid-cols-2 gap-4\">\n <div>\n <label className=\"block text-sm font-medium text-white/80 mb-2\">Prefix</label>\n <input\n type=\"text\"\n value={barcodeData.prefix}\n onChange={(e) => setBarcodeData(prev => ({ ...prev, prefix: e.target.value }))}\n className=\"w-full px-3 py-2 bg-white/10 border border-white/20 rounded-lg text-white placeholder-white/50 focus:ring-2 focus:ring-blue-500 focus:border-transparent\"\n placeholder=\"BCT\"\n />\n </div>\n <div>\n <label className=\"block text-sm font-medium text-white/80 mb-2\">Padding</label>\n <input\n type=\"number\"\n value={barcodeData.padding}\n onChange={(e) => setBarcodeData(prev => ({ ...prev, padding: parseInt(e.target.value) || 6 }))}\n className=\"w-full px-3 py-2 bg-white/10 border border-white/20 rounded-lg text-white placeholder-white/50 focus:ring-2 focus:ring-blue-500 focus:border-transparent\"\n min=\"1\"\n max=\"10\"\n />\n </div>\n </div>\n <div className=\"grid grid-cols-2 gap-4\">\n <div>\n <label className=\"block text-sm font-medium text-white/80 mb-2\">Start Number</label>\n <input\n type=\"number\"\n value={barcodeData.startNumber}\n onChange={(e) => setBarcodeData(prev => ({ ...prev, startNumber: parseInt(e.target.value) || 1 }))}\n className=\"w-full px-3 py-2 bg-white/10 border border-white/20 rounded-lg text-white placeholder-white/50 focus:ring-2 focus:ring-blue-500 focus:border-transparent\"\n min=\"1\"\n />\n </div>\n <div>\n <label className=\"block text-sm font-medium text-white/80 mb-2\">Quantity</label>\n <input\n type=\"number\"\n value={barcodeData.quantity}\n onChange={(e) => setBarcodeData(prev => ({ ...prev, quantity: parseInt(e.target.value) || 1 }))}\n className=\"w-full px-3 py-2 bg-white/10 border border-white/20 rounded-lg text-white placeholder-white/50 focus:ring-2 focus:ring-blue-500 focus:border-transparent\"\n min=\"1\"\n max=\"1000\"\n />\n </div>\n </div>\n <div className=\"bg-white/5 border border-white/20 rounded-lg p-3\">\n <div className=\"text-sm text-white/60 mb-2\">Preview:</div>\n <div className=\"font-mono text-white text-sm\">\n {barcodeData.prefix}{barcodeData.startNumber.toString().padStart(barcodeData.padding, '0')} - {barcodeData.prefix}{(barcodeData.startNumber + barcodeData.quantity - 1).toString().padStart(barcodeData.padding, '0')}\n </div>\n </div>\n </div>\n ) : (\n <div>\n <label className=\"block text-sm font-medium text-white/80 mb-2\">Barcodes (one per line)</label>\n <textarea\n value={manualBarcodes}\n onChange={(e) => setManualBarcodes(e.target.value)}\n rows={8}\n className=\"w-full px-3 py-2 bg-white/10 border border-white/20 rounded-lg text-white placeholder-white/50 focus:ring-2 focus:ring-blue-500 focus:border-transparent resize-none font-mono\"\n placeholder=\"Enter barcodes, one per line&#10;Example:&#10;BCT000001&#10;BCT000002&#10;BCT000003\"\n />\n </div>\n )}\n </div>\n\n <div className=\"flex justify-end space-x-3 mt-6\">\n <button\n onClick={() => setShowModal(false)}\n className=\"px-6 py-3 text-white/80 hover:text-white transition-colors\"\n >\n Cancel\n </button>\n <button\n onClick={handleCreateTickets}\n disabled={processing}\n className=\"px-6 py-3 bg-gradient-to-r from-blue-600 to-purple-600 hover:from-blue-700 hover:to-purple-700 text-white rounded-lg font-medium transition-all duration-200 disabled:opacity-50\"\n >\n {processing ? 'Creating...' : 'Create Tickets'}\n </button>\n </div>\n </div>\n </div>\n </div>\n )}\n\n {/* Edit Modal */}\n {editingTicket && (\n <div className=\"fixed inset-0 bg-black/50 backdrop-blur-sm flex items-center justify-center p-4 z-50\">\n <div className=\"bg-white/10 backdrop-blur-xl border border-white/20 rounded-2xl shadow-2xl max-w-md w-full\">\n <div className=\"p-6\">\n <div className=\"flex justify-between items-center mb-6\">\n <h3 className=\"text-2xl font-light text-white\">Edit Printed Ticket</h3>\n <button\n onClick={() => setEditingTicket(null)}\n className=\"text-white/60 hover:text-white transition-colors\"\n >\n <svg className=\"w-6 h-6\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M6 18L18 6M6 6l12 12\" />\n </svg>\n </button>\n </div>\n\n <div className=\"space-y-4\">\n <div>\n <label className=\"block text-sm font-medium text-white/80 mb-2\">Barcode</label>\n <div className=\"px-3 py-2 bg-white/5 border border-white/20 rounded-lg text-white font-mono\">\n {editingTicket.barcode}\n </div>\n </div>\n\n <div>\n <label className=\"block text-sm font-medium text-white/80 mb-2\">Status</label>\n <select\n value={editForm.status}\n onChange={(e) => setEditForm(prev => ({ ...prev, status: e.target.value as any }))}\n className=\"w-full px-3 py-2 bg-white/10 border border-white/20 rounded-lg text-white focus:ring-2 focus:ring-blue-500 focus:border-transparent\"\n >\n <option value=\"pending\">Pending</option>\n <option value=\"printed\">Printed</option>\n <option value=\"distributed\">Distributed</option>\n <option value=\"used\">Used</option>\n </select>\n </div>\n\n <div>\n <label className=\"block text-sm font-medium text-white/80 mb-2\">Notes</label>\n <textarea\n value={editForm.notes}\n onChange={(e) => setEditForm(prev => ({ ...prev, notes: e.target.value }))}\n rows={3}\n className=\"w-full px-3 py-2 bg-white/10 border border-white/20 rounded-lg text-white placeholder-white/50 focus:ring-2 focus:ring-blue-500 focus:border-transparent resize-none\"\n placeholder=\"Optional notes...\"\n />\n </div>\n </div>\n\n <div className=\"flex justify-end space-x-3 mt-6\">\n <button\n onClick={() => setEditingTicket(null)}\n className=\"px-6 py-3 text-white/80 hover:text-white transition-colors\"\n >\n Cancel\n </button>\n <button\n onClick={handleUpdateTicket}\n className=\"px-6 py-3 bg-gradient-to-r from-blue-600 to-purple-600 hover:from-blue-700 hover:to-purple-700 text-white rounded-lg font-medium transition-all duration-200\"\n >\n Update\n </button>\n </div>\n </div>\n </div>\n </div>\n )}\n\n {/* Ticket Preview Modal */}\n <TicketPreviewModal\n isOpen={showPreviewModal}\n onClose={() => setShowPreviewModal(false)}\n eventId={eventId}\n organizationId={organizationId}\n />\n </div>\n );\n}","usedDeprecatedRules":[]},{"filePath":"/home/tyler/apps/bct-whitelabel/src/components/manage/PromotionsTab.tsx","messages":[{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'formatCurrency' is defined but never used. Allowed unused vars must match /^_/u.","line":3,"column":10,"nodeType":null,"messageId":"unusedVar","endLine":3,"endColumn":24},{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":21,"column":15,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":21,"endColumn":18,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[594,597],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[594,597],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":61,"column":14,"nodeType":null,"messageId":"unusedVar","endLine":61,"endColumn":19},{"ruleId":"no-empty","severity":1,"message":"Empty block statement.","line":61,"column":21,"nodeType":"BlockStatement","messageId":"unexpected","endLine":63,"endColumn":6,"suggestions":[{"messageId":"suggestComment","data":{"type":"block"},"fix":{"range":[1667,1673],"text":" /* empty */ "},"desc":"Add comment inside empty block statement."}]},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":129,"column":14,"nodeType":null,"messageId":"unusedVar","endLine":129,"endColumn":19},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":147,"column":16,"nodeType":null,"messageId":"unusedVar","endLine":147,"endColumn":21},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":163,"column":14,"nodeType":null,"messageId":"unusedVar","endLine":163,"endColumn":19},{"ruleId":"no-undef","severity":2,"message":"'React' is not defined.","line":438,"column":26,"nodeType":"Identifier","messageId":"undef","endLine":438,"endColumn":31},{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":459,"column":96,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":459,"endColumn":99,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[19325,19328],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[19325,19328],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"no-undef","severity":2,"message":"'React' is not defined.","line":463,"column":28,"nodeType":"Identifier","messageId":"undef","endLine":463,"endColumn":33},{"ruleId":"no-undef","severity":2,"message":"'React' is not defined.","line":482,"column":26,"nodeType":"Identifier","messageId":"undef","endLine":482,"endColumn":31},{"ruleId":"no-undef","severity":2,"message":"'React' is not defined.","line":499,"column":28,"nodeType":"Identifier","messageId":"undef","endLine":499,"endColumn":33},{"ruleId":"no-undef","severity":2,"message":"'React' is not defined.","line":512,"column":28,"nodeType":"Identifier","messageId":"undef","endLine":512,"endColumn":33},{"ruleId":"no-undef","severity":2,"message":"'React' is not defined.","line":526,"column":26,"nodeType":"Identifier","messageId":"undef","endLine":526,"endColumn":31}],"suppressedMessages":[],"errorCount":6,"fatalErrorCount":0,"warningCount":8,"fixableErrorCount":0,"fixableWarningCount":0,"source":"import { useState, useEffect } from 'react';\nimport { createClient } from '@supabase/supabase-js';\nimport { formatCurrency } from '../../lib/event-management';\n\nconst supabase = createClient(\n import.meta.env.PUBLIC_SUPABASE_URL,\n import.meta.env.PUBLIC_SUPABASE_ANON_KEY\n);\n\ninterface Promotion {\n id: string;\n name: string;\n description: string;\n type: 'early_bird' | 'flash_sale' | 'group_discount' | 'loyalty_reward' | 'referral';\n discount_percentage: number;\n start_date: string;\n end_date: string;\n max_uses: number;\n current_uses: number;\n is_active: boolean;\n conditions: any;\n created_at: string;\n}\n\ninterface PromotionsTabProps {\n eventId: string;\n}\n\nexport default function PromotionsTab({ eventId }: PromotionsTabProps) {\n const [promotions, setPromotions] = useState<Promotion[]>([]);\n const [showModal, setShowModal] = useState(false);\n const [editingPromotion, setEditingPromotion] = useState<Promotion | null>(null);\n const [formData, setFormData] = useState({\n name: '',\n description: '',\n type: 'early_bird' as const,\n discount_percentage: 10,\n start_date: '',\n end_date: '',\n max_uses: 100,\n conditions: {}\n });\n const [loading, setLoading] = useState(true);\n const [saving, setSaving] = useState(false);\n\n useEffect(() => {\n loadPromotions();\n }, [eventId]);\n\n const loadPromotions = async () => {\n setLoading(true);\n try {\n const { data, error } = await supabase\n .from('promotions')\n .select('*')\n .eq('event_id', eventId)\n .order('created_at', { ascending: false });\n\n if (error) throw error;\n setPromotions(data || []);\n } catch (error) {\n\n } finally {\n setLoading(false);\n }\n };\n\n const handleCreatePromotion = () => {\n setEditingPromotion(null);\n setFormData({\n name: '',\n description: '',\n type: 'early_bird',\n discount_percentage: 10,\n start_date: new Date().toISOString().split('T')[0],\n end_date: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000).toISOString().split('T')[0],\n max_uses: 100,\n conditions: {}\n });\n setShowModal(true);\n };\n\n const handleEditPromotion = (promotion: Promotion) => {\n setEditingPromotion(promotion);\n setFormData({\n name: promotion.name,\n description: promotion.description,\n type: promotion.type,\n discount_percentage: promotion.discount_percentage,\n start_date: promotion.start_date.split('T')[0],\n end_date: promotion.end_date.split('T')[0],\n max_uses: promotion.max_uses,\n conditions: promotion.conditions || {}\n });\n setShowModal(true);\n };\n\n const handleSavePromotion = async () => {\n setSaving(true);\n try {\n const promotionData = {\n ...formData,\n event_id: eventId,\n start_date: new Date(formData.start_date).toISOString(),\n end_date: new Date(formData.end_date).toISOString()\n };\n\n if (editingPromotion) {\n const { error } = await supabase\n .from('promotions')\n .update(promotionData)\n .eq('id', editingPromotion.id);\n\n if (error) throw error;\n } else {\n const { error } = await supabase\n .from('promotions')\n .insert({\n ...promotionData,\n is_active: true,\n current_uses: 0\n });\n\n if (error) throw error;\n }\n\n setShowModal(false);\n loadPromotions();\n } catch (error) {\n\n alert('Failed to save promotion');\n } finally {\n setSaving(false);\n }\n };\n\n const handleDeletePromotion = async (promotion: Promotion) => {\n if (confirm(`Are you sure you want to delete \"${promotion.name}\"?`)) {\n try {\n const { error } = await supabase\n .from('promotions')\n .delete()\n .eq('id', promotion.id);\n\n if (error) throw error;\n loadPromotions();\n } catch (error) {\n\n alert('Failed to delete promotion');\n }\n }\n };\n\n const handleTogglePromotion = async (promotion: Promotion) => {\n try {\n const { error } = await supabase\n .from('promotions')\n .update({ is_active: !promotion.is_active })\n .eq('id', promotion.id);\n\n if (error) throw error;\n loadPromotions();\n } catch (error) {\n\n alert('Failed to toggle promotion');\n }\n };\n\n const getPromotionIcon = (type: string) => {\n switch (type) {\n case 'early_bird':\n return (\n <svg className=\"w-5 h-5\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z\" />\n </svg>\n );\n case 'flash_sale':\n return (\n <svg className=\"w-5 h-5\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M13 10V3L4 14h7v7l9-11h-7z\" />\n </svg>\n );\n case 'group_discount':\n return (\n <svg className=\"w-5 h-5\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z\" />\n </svg>\n );\n case 'loyalty_reward':\n return (\n <svg className=\"w-5 h-5\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M11.049 2.927c.3-.921 1.603-.921 1.902 0l1.519 4.674a1 1 0 00.95.69h4.915c.969 0 1.371 1.24.588 1.81l-3.976 2.888a1 1 0 00-.363 1.118l1.518 4.674c.3.922-.755 1.688-1.538 1.118l-3.976-2.888a1 1 0 00-1.176 0l-3.976 2.888c-.783.57-1.838-.197-1.538-1.118l1.518-4.674a1 1 0 00-.363-1.118l-3.976-2.888c-.784-.57-.38-1.81.588-1.81h4.914a1 1 0 00.951-.69l1.519-4.674z\" />\n </svg>\n );\n case 'referral':\n return (\n <svg className=\"w-5 h-5\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M8.684 13.342C8.886 12.938 9 12.482 9 12c0-.482-.114-.938-.316-1.342m0 2.684a3 3 0 110-2.684m0 2.684l6.632 3.316m-6.632-6l6.632-3.316m0 0a3 3 0 105.367-2.684 3 3 0 00-5.367 2.684zm0 9.316a3 3 0 105.368 2.684 3 3 0 00-5.368-2.684z\" />\n </svg>\n );\n default:\n return null;\n }\n };\n\n const getPromotionColor = (type: string) => {\n switch (type) {\n case 'early_bird':\n return 'text-blue-400 bg-blue-500/20';\n case 'flash_sale':\n return 'text-red-400 bg-red-500/20';\n case 'group_discount':\n return 'text-green-400 bg-green-500/20';\n case 'loyalty_reward':\n return 'text-yellow-400 bg-yellow-500/20';\n case 'referral':\n return 'text-purple-400 bg-purple-500/20';\n default:\n return 'text-white/60 bg-white/10';\n }\n };\n\n const isPromotionActive = (promotion: Promotion) => {\n const now = new Date();\n const start = new Date(promotion.start_date);\n const end = new Date(promotion.end_date);\n return promotion.is_active && now >= start && now <= end;\n };\n\n const getPromotionStats = () => {\n const total = promotions.length;\n const active = promotions.filter(p => isPromotionActive(p)).length;\n const scheduled = promotions.filter(p => p.is_active && new Date(p.start_date) > new Date()).length;\n const expired = promotions.filter(p => new Date(p.end_date) < new Date()).length;\n \n return { total, active, scheduled, expired };\n };\n\n const stats = getPromotionStats();\n\n if (loading) {\n return (\n <div className=\"flex items-center justify-center py-12\">\n <div className=\"animate-spin rounded-full h-8 w-8 border-b-2 border-white\"></div>\n </div>\n );\n }\n\n return (\n <div className=\"space-y-6\">\n <div className=\"flex justify-between items-center\">\n <h2 className=\"text-2xl font-light text-white\">Promotions & Campaigns</h2>\n <button\n onClick={handleCreatePromotion}\n className=\"flex items-center gap-2 px-4 py-2 rounded-lg font-medium transition-all duration-200\"\n style={{\n background: 'var(--glass-text-accent)',\n color: 'white'\n }}\n >\n <svg className=\"w-4 h-4\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M12 6v6m0 0v6m0-6h6m-6 0H6\" />\n </svg>\n Create Promotion\n </button>\n </div>\n\n {/* Stats Cards */}\n <div className=\"grid grid-cols-1 md:grid-cols-4 gap-4\">\n <div className=\"bg-white/10 backdrop-blur-xl border border-white/20 rounded-xl p-4\">\n <div className=\"text-sm text-white/60\">Total Promotions</div>\n <div className=\"text-2xl font-bold text-white\">{stats.total}</div>\n </div>\n <div className=\"bg-white/10 backdrop-blur-xl border border-white/20 rounded-xl p-4\">\n <div className=\"text-sm text-white/60\">Active</div>\n <div className=\"text-2xl font-bold text-green-400\">{stats.active}</div>\n </div>\n <div className=\"bg-white/10 backdrop-blur-xl border border-white/20 rounded-xl p-4\">\n <div className=\"text-sm text-white/60\">Scheduled</div>\n <div className=\"text-2xl font-bold text-blue-400\">{stats.scheduled}</div>\n </div>\n <div className=\"bg-white/10 backdrop-blur-xl border border-white/20 rounded-xl p-4\">\n <div className=\"text-sm text-white/60\">Expired</div>\n <div className=\"text-2xl font-bold text-gray-400\">{stats.expired}</div>\n </div>\n </div>\n\n {/* Promotions Grid */}\n {promotions.length === 0 ? (\n <div className=\"text-center py-12\">\n <svg className=\"w-12 h-12 text-white/40 mx-auto mb-4\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M9.663 17h4.673M12 3v1m6.364 1.636l-.707.707M21 12h-1M4 12H3m3.343-5.657l-.707-.707m2.828 9.9a5 5 0 117.072 0l-.548.547A3.374 3.374 0 0014 18.469V19a2 2 0 11-4 0v-.531c0-.895-.356-1.754-.988-2.386l-.548-.547z\" />\n </svg>\n <p className=\"text-white/60 mb-4\">No promotions created yet</p>\n <button\n onClick={handleCreatePromotion}\n className=\"px-6 py-3 rounded-lg font-medium transition-all duration-200\"\n style={{\n background: 'var(--glass-text-accent)',\n color: 'white'\n }}\n >\n Create Your First Promotion\n </button>\n </div>\n ) : (\n <div className=\"grid grid-cols-1 lg:grid-cols-2 gap-6\">\n {promotions.map((promotion) => (\n <div key={promotion.id} className=\"bg-white/5 border border-white/20 rounded-xl p-6 hover:bg-white/10 transition-all duration-200\">\n <div className=\"flex justify-between items-start mb-4\">\n <div className=\"flex-1\">\n <div className=\"flex items-center gap-3 mb-2\">\n <div className={`p-2 rounded-lg ${getPromotionColor(promotion.type)}`}>\n {getPromotionIcon(promotion.type)}\n </div>\n <div>\n <h3 className=\"text-xl font-semibold text-white\">{promotion.name}</h3>\n <div className=\"text-sm text-white/60 capitalize\">{promotion.type.replace('_', ' ')}</div>\n </div>\n </div>\n {promotion.description && (\n <p className=\"text-white/70 text-sm mb-3\">{promotion.description}</p>\n )}\n <div className=\"text-3xl font-bold text-purple-400 mb-2\">\n {promotion.discount_percentage}% OFF\n </div>\n </div>\n <div className=\"flex items-center gap-2\">\n <button\n onClick={() => handleEditPromotion(promotion)}\n className=\"p-2 text-white/60 hover:text-white transition-colors\"\n title=\"Edit\"\n >\n <svg className=\"w-4 h-4\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z\" />\n </svg>\n </button>\n <button\n onClick={() => handleTogglePromotion(promotion)}\n className=\"p-2 text-white/60 hover:text-white transition-colors\"\n title={promotion.is_active ? 'Deactivate' : 'Activate'}\n >\n {promotion.is_active ? (\n <svg className=\"w-4 h-4\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M13.875 18.825A10.05 10.05 0 0112 19c-4.478 0-8.268-2.943-9.543-7a9.97 9.97 0 011.563-3.029m5.858.908a3 3 0 114.142 4.142M9.878 9.878l4.242 4.242M9.878 9.878L3 3m6.878 6.878L21 21\" />\n </svg>\n ) : (\n <svg className=\"w-4 h-4\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M15 12a3 3 0 11-6 0 3 3 0 016 0z\" />\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z\" />\n </svg>\n )}\n </button>\n <button\n onClick={() => handleDeletePromotion(promotion)}\n className=\"p-2 text-white/60 hover:text-red-400 transition-colors\"\n title=\"Delete\"\n >\n <svg className=\"w-4 h-4\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16\" />\n </svg>\n </button>\n </div>\n </div>\n\n <div className=\"space-y-3\">\n <div className=\"grid grid-cols-3 gap-4 text-sm\">\n <div>\n <div className=\"text-white/60\">Status</div>\n <div className={`font-semibold ${\n isPromotionActive(promotion) ? 'text-green-400' :\n new Date(promotion.start_date) > new Date() ? 'text-blue-400' :\n 'text-gray-400'\n }`}>\n {isPromotionActive(promotion) ? 'Active' :\n new Date(promotion.start_date) > new Date() ? 'Scheduled' :\n 'Expired'}\n </div>\n </div>\n <div>\n <div className=\"text-white/60\">Usage</div>\n <div className=\"text-white font-semibold\">\n {promotion.current_uses} / {promotion.max_uses}\n </div>\n </div>\n <div>\n <div className=\"text-white/60\">Ends</div>\n <div className=\"text-white font-semibold\">\n {new Date(promotion.end_date).toLocaleDateString()}\n </div>\n </div>\n </div>\n\n <div className=\"w-full bg-white/10 rounded-full h-2\">\n <div \n className=\"bg-gradient-to-r from-purple-500 to-pink-500 h-2 rounded-full transition-all duration-300\"\n style={{ width: `${(promotion.current_uses / promotion.max_uses) * 100}%` }}\n />\n </div>\n <div className=\"text-xs text-white/60\">\n {((promotion.current_uses / promotion.max_uses) * 100).toFixed(1)}% used\n </div>\n </div>\n </div>\n ))}\n </div>\n )}\n\n {/* Modal */}\n {showModal && (\n <div className=\"fixed inset-0 bg-black/50 backdrop-blur-sm flex items-center justify-center p-4 z-50\">\n <div className=\"bg-white/10 backdrop-blur-xl border border-white/20 rounded-2xl shadow-2xl max-w-lg w-full\">\n <div className=\"p-6\">\n <div className=\"flex justify-between items-center mb-6\">\n <h3 className=\"text-2xl font-light text-white\">\n {editingPromotion ? 'Edit Promotion' : 'Create Promotion'}\n </h3>\n <button\n onClick={() => setShowModal(false)}\n className=\"text-white/60 hover:text-white transition-colors\"\n >\n <svg className=\"w-6 h-6\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M6 18L18 6M6 6l12 12\" />\n </svg>\n </button>\n </div>\n\n <div className=\"space-y-4\">\n <div>\n <label className=\"block text-sm font-medium text-white/80 mb-2\">Name</label>\n <input\n type=\"text\"\n value={formData.name}\n onChange={(e) => setFormData(prev => ({ ...prev, name: e.target.value }))}\n className=\"w-full px-4 py-3 bg-white/10 border border-white/20 rounded-lg text-white placeholder-white/50 focus:ring-2 focus:border-transparent\"\n style={{\n '--tw-ring-color': 'var(--glass-text-accent)'\n } as React.CSSProperties}\n placeholder=\"Early Bird Special\"\n />\n </div>\n\n <div>\n <label className=\"block text-sm font-medium text-white/80 mb-2\">Description</label>\n <textarea\n value={formData.description}\n onChange={(e) => setFormData(prev => ({ ...prev, description: e.target.value }))}\n rows={3}\n className=\"w-full px-4 py-3 bg-white/10 border border-white/20 rounded-lg text-white placeholder-white/50 focus:ring-2 focus:ring-blue-500 focus:border-transparent resize-none\"\n placeholder=\"Limited time offer for early purchasers...\"\n />\n </div>\n\n <div className=\"grid grid-cols-2 gap-4\">\n <div>\n <label className=\"block text-sm font-medium text-white/80 mb-2\">Type</label>\n <select\n value={formData.type}\n onChange={(e) => setFormData(prev => ({ ...prev, type: e.target.value as any }))}\n className=\"w-full px-4 py-3 bg-white/10 border border-white/20 rounded-lg text-white focus:ring-2 focus:border-transparent\"\n style={{\n '--tw-ring-color': 'var(--glass-text-accent)'\n } as React.CSSProperties}\n >\n <option value=\"early_bird\">Early Bird</option>\n <option value=\"flash_sale\">Flash Sale</option>\n <option value=\"group_discount\">Group Discount</option>\n <option value=\"loyalty_reward\">Loyalty Reward</option>\n <option value=\"referral\">Referral</option>\n </select>\n </div>\n\n <div>\n <label className=\"block text-sm font-medium text-white/80 mb-2\">Discount (%)</label>\n <input\n type=\"number\"\n value={formData.discount_percentage}\n onChange={(e) => setFormData(prev => ({ ...prev, discount_percentage: parseInt(e.target.value) || 0 }))}\n className=\"w-full px-4 py-3 bg-white/10 border border-white/20 rounded-lg text-white placeholder-white/50 focus:ring-2 focus:border-transparent\"\n style={{\n '--tw-ring-color': 'var(--glass-text-accent)'\n } as React.CSSProperties}\n min=\"1\"\n max=\"100\"\n />\n </div>\n </div>\n\n <div className=\"grid grid-cols-2 gap-4\">\n <div>\n <label className=\"block text-sm font-medium text-white/80 mb-2\">Start Date</label>\n <input\n type=\"date\"\n value={formData.start_date}\n onChange={(e) => setFormData(prev => ({ ...prev, start_date: e.target.value }))}\n className=\"w-full px-4 py-3 bg-white/10 border border-white/20 rounded-lg text-white focus:ring-2 focus:border-transparent\"\n style={{\n '--tw-ring-color': 'var(--glass-text-accent)'\n } as React.CSSProperties}\n />\n </div>\n\n <div>\n <label className=\"block text-sm font-medium text-white/80 mb-2\">End Date</label>\n <input\n type=\"date\"\n value={formData.end_date}\n onChange={(e) => setFormData(prev => ({ ...prev, end_date: e.target.value }))}\n className=\"w-full px-4 py-3 bg-white/10 border border-white/20 rounded-lg text-white focus:ring-2 focus:border-transparent\"\n style={{\n '--tw-ring-color': 'var(--glass-text-accent)'\n } as React.CSSProperties}\n />\n </div>\n </div>\n\n <div>\n <label className=\"block text-sm font-medium text-white/80 mb-2\">Max Uses</label>\n <input\n type=\"number\"\n value={formData.max_uses}\n onChange={(e) => setFormData(prev => ({ ...prev, max_uses: parseInt(e.target.value) || 0 }))}\n className=\"w-full px-4 py-3 bg-white/10 border border-white/20 rounded-lg text-white placeholder-white/50 focus:ring-2 focus:border-transparent\"\n style={{\n '--tw-ring-color': 'var(--glass-text-accent)'\n } as React.CSSProperties}\n min=\"1\"\n />\n </div>\n </div>\n\n <div className=\"flex justify-end space-x-3 mt-6\">\n <button\n onClick={() => setShowModal(false)}\n className=\"px-6 py-3 text-white/80 hover:text-white transition-colors\"\n >\n Cancel\n </button>\n <button\n onClick={handleSavePromotion}\n disabled={saving}\n className=\"px-6 py-3 rounded-lg font-medium transition-all duration-200 disabled:opacity-50\"\n style={{\n background: 'var(--glass-text-accent)',\n color: 'white'\n }}\n >\n {saving ? 'Saving...' : editingPromotion ? 'Update' : 'Create'}\n </button>\n </div>\n </div>\n </div>\n </div>\n )}\n </div>\n );\n}","usedDeprecatedRules":[]},{"filePath":"/home/tyler/apps/bct-whitelabel/src/components/manage/SettingsTab.tsx","messages":[{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'supabase' is assigned a value but never used. Allowed unused vars must match /^_/u.","line":5,"column":7,"nodeType":null,"messageId":"unusedVar","endLine":5,"endColumn":15},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'eventData' is assigned a value but never used. Allowed unused vars must match /^_/u.","line":16,"column":10,"nodeType":null,"messageId":"unusedVar","endLine":16,"endColumn":19},{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":16,"column":46,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":16,"endColumn":49,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[501,504],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[501,504],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":62,"column":14,"nodeType":null,"messageId":"unusedVar","endLine":62,"endColumn":19},{"ruleId":"no-empty","severity":1,"message":"Empty block statement.","line":62,"column":21,"nodeType":"BlockStatement","messageId":"unexpected","endLine":64,"endColumn":6,"suggestions":[{"messageId":"suggestComment","data":{"type":"block"},"fix":{"range":[2118,2124],"text":" /* empty */ "},"desc":"Add comment inside empty block statement."}]},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":81,"column":14,"nodeType":null,"messageId":"unusedVar","endLine":81,"endColumn":19},{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":275,"column":96,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":275,"endColumn":99,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[11606,11609],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[11606,11609],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":7,"fixableErrorCount":0,"fixableWarningCount":0,"source":"import { useState, useEffect } from 'react';\nimport { loadEventData, updateEventData } from '../../lib/event-management';\nimport { createClient } from '@supabase/supabase-js';\n\nconst supabase = createClient(\n import.meta.env.PUBLIC_SUPABASE_URL,\n import.meta.env.PUBLIC_SUPABASE_ANON_KEY\n);\n\ninterface SettingsTabProps {\n eventId: string;\n organizationId: string;\n}\n\nexport default function SettingsTab({ eventId, organizationId }: SettingsTabProps) {\n const [eventData, setEventData] = useState<any>(null);\n const [availabilitySettings, setAvailabilitySettings] = useState({\n show_remaining_tickets: true,\n show_sold_out_message: true,\n hide_event_after_sold_out: false,\n sales_start_date: '',\n sales_end_date: '',\n auto_close_sales: false,\n require_phone_number: false,\n require_address: false,\n custom_fields: [] as Array<{\n id: string;\n label: string;\n type: 'text' | 'select' | 'checkbox';\n required: boolean;\n options?: string[];\n }>\n });\n const [loading, setLoading] = useState(true);\n const [saving, setSaving] = useState(false);\n\n useEffect(() => {\n loadData();\n }, [eventId]);\n\n const loadData = async () => {\n setLoading(true);\n try {\n const event = await loadEventData(eventId, organizationId);\n if (event) {\n setEventData(event);\n \n // Load availability settings\n const settings = event.availability_settings || {};\n setAvailabilitySettings({\n show_remaining_tickets: settings.show_remaining_tickets ?? true,\n show_sold_out_message: settings.show_sold_out_message ?? true,\n hide_event_after_sold_out: settings.hide_event_after_sold_out ?? false,\n sales_start_date: settings.sales_start_date || '',\n sales_end_date: settings.sales_end_date || '',\n auto_close_sales: settings.auto_close_sales ?? false,\n require_phone_number: settings.require_phone_number ?? false,\n require_address: settings.require_address ?? false,\n custom_fields: settings.custom_fields || []\n });\n }\n } catch (error) {\n\n } finally {\n setLoading(false);\n }\n };\n\n const handleSaveSettings = async () => {\n setSaving(true);\n try {\n const success = await updateEventData(eventId, {\n availability_settings: availabilitySettings\n });\n \n if (success) {\n alert('Settings saved successfully!');\n } else {\n alert('Failed to save settings');\n }\n } catch (error) {\n\n alert('Failed to save settings');\n } finally {\n setSaving(false);\n }\n };\n\n const addCustomField = () => {\n const newField = {\n id: Date.now().toString(),\n label: '',\n type: 'text' as const,\n required: false,\n options: []\n };\n setAvailabilitySettings(prev => ({\n ...prev,\n custom_fields: [...prev.custom_fields, newField]\n }));\n };\n\n const updateCustomField = (id: string, updates: Partial<typeof availabilitySettings.custom_fields[0]>) => {\n setAvailabilitySettings(prev => ({\n ...prev,\n custom_fields: prev.custom_fields.map(field => \n field.id === id ? { ...field, ...updates } : field\n )\n }));\n };\n\n const removeCustomField = (id: string) => {\n setAvailabilitySettings(prev => ({\n ...prev,\n custom_fields: prev.custom_fields.filter(field => field.id !== id)\n }));\n };\n\n if (loading) {\n return (\n <div className=\"flex items-center justify-center py-12\">\n <div className=\"animate-spin rounded-full h-8 w-8 border-b-2 border-white\"></div>\n </div>\n );\n }\n\n return (\n <div className=\"space-y-6\">\n <div className=\"flex justify-between items-center\">\n <h2 className=\"text-2xl font-light text-white\">Event Settings</h2>\n <button\n onClick={handleSaveSettings}\n disabled={saving}\n className=\"px-4 py-2 bg-gradient-to-r from-blue-600 to-purple-600 hover:from-blue-700 hover:to-purple-700 text-white rounded-lg font-medium transition-all duration-200 disabled:opacity-50\"\n >\n {saving ? 'Saving...' : 'Save Settings'}\n </button>\n </div>\n\n <div className=\"space-y-8\">\n {/* Ticket Availability Display */}\n <div className=\"bg-white/5 border border-white/20 rounded-xl p-6\">\n <h3 className=\"text-lg font-semibold text-white mb-4\">Ticket Availability Display</h3>\n <div className=\"space-y-4\">\n <label className=\"flex items-center\">\n <input\n type=\"checkbox\"\n checked={availabilitySettings.show_remaining_tickets}\n onChange={(e) => setAvailabilitySettings(prev => ({ ...prev, show_remaining_tickets: e.target.checked }))}\n className=\"w-4 h-4 text-blue-600 bg-white/10 border-white/20 rounded focus:ring-blue-500\"\n />\n <span className=\"ml-2 text-white\">Show remaining ticket count</span>\n </label>\n <label className=\"flex items-center\">\n <input\n type=\"checkbox\"\n checked={availabilitySettings.show_sold_out_message}\n onChange={(e) => setAvailabilitySettings(prev => ({ ...prev, show_sold_out_message: e.target.checked }))}\n className=\"w-4 h-4 text-blue-600 bg-white/10 border-white/20 rounded focus:ring-blue-500\"\n />\n <span className=\"ml-2 text-white\">Show \"sold out\" message when tickets are unavailable</span>\n </label>\n <label className=\"flex items-center\">\n <input\n type=\"checkbox\"\n checked={availabilitySettings.hide_event_after_sold_out}\n onChange={(e) => setAvailabilitySettings(prev => ({ ...prev, hide_event_after_sold_out: e.target.checked }))}\n className=\"w-4 h-4 text-blue-600 bg-white/10 border-white/20 rounded focus:ring-blue-500\"\n />\n <span className=\"ml-2 text-white\">Hide event completely when sold out</span>\n </label>\n </div>\n </div>\n\n {/* Sales Schedule */}\n <div className=\"bg-white/5 border border-white/20 rounded-xl p-6\">\n <h3 className=\"text-lg font-semibold text-white mb-4\">Sales Schedule</h3>\n <div className=\"space-y-4\">\n <div className=\"grid grid-cols-1 md:grid-cols-2 gap-4\">\n <div>\n <label className=\"block text-sm font-medium text-white/80 mb-2\">Sales Start Date</label>\n <input\n type=\"datetime-local\"\n value={availabilitySettings.sales_start_date}\n onChange={(e) => setAvailabilitySettings(prev => ({ ...prev, sales_start_date: e.target.value }))}\n className=\"w-full px-3 py-2 bg-white/10 border border-white/20 rounded-lg text-white focus:ring-2 focus:ring-blue-500 focus:border-transparent\"\n />\n <p className=\"text-xs text-white/60 mt-1\">Leave empty to start sales immediately</p>\n </div>\n <div>\n <label className=\"block text-sm font-medium text-white/80 mb-2\">Sales End Date</label>\n <input\n type=\"datetime-local\"\n value={availabilitySettings.sales_end_date}\n onChange={(e) => setAvailabilitySettings(prev => ({ ...prev, sales_end_date: e.target.value }))}\n className=\"w-full px-3 py-2 bg-white/10 border border-white/20 rounded-lg text-white focus:ring-2 focus:ring-blue-500 focus:border-transparent\"\n />\n <p className=\"text-xs text-white/60 mt-1\">Leave empty to continue sales until event date</p>\n </div>\n </div>\n <label className=\"flex items-center\">\n <input\n type=\"checkbox\"\n checked={availabilitySettings.auto_close_sales}\n onChange={(e) => setAvailabilitySettings(prev => ({ ...prev, auto_close_sales: e.target.checked }))}\n className=\"w-4 h-4 text-blue-600 bg-white/10 border-white/20 rounded focus:ring-blue-500\"\n />\n <span className=\"ml-2 text-white\">Automatically close sales 1 hour before event</span>\n </label>\n </div>\n </div>\n\n {/* Customer Information Requirements */}\n <div className=\"bg-white/5 border border-white/20 rounded-xl p-6\">\n <h3 className=\"text-lg font-semibold text-white mb-4\">Customer Information Requirements</h3>\n <div className=\"space-y-4\">\n <label className=\"flex items-center\">\n <input\n type=\"checkbox\"\n checked={availabilitySettings.require_phone_number}\n onChange={(e) => setAvailabilitySettings(prev => ({ ...prev, require_phone_number: e.target.checked }))}\n className=\"w-4 h-4 text-blue-600 bg-white/10 border-white/20 rounded focus:ring-blue-500\"\n />\n <span className=\"ml-2 text-white\">Require phone number</span>\n </label>\n <label className=\"flex items-center\">\n <input\n type=\"checkbox\"\n checked={availabilitySettings.require_address}\n onChange={(e) => setAvailabilitySettings(prev => ({ ...prev, require_address: e.target.checked }))}\n className=\"w-4 h-4 text-blue-600 bg-white/10 border-white/20 rounded focus:ring-blue-500\"\n />\n <span className=\"ml-2 text-white\">Require address</span>\n </label>\n </div>\n </div>\n\n {/* Custom Fields */}\n <div className=\"bg-white/5 border border-white/20 rounded-xl p-6\">\n <div className=\"flex justify-between items-center mb-4\">\n <h3 className=\"text-lg font-semibold text-white\">Custom Fields</h3>\n <button\n onClick={addCustomField}\n className=\"px-3 py-1 rounded-lg text-sm transition-all duration-200 hover:shadow-lg hover:scale-105\"\n style={{\n background: 'var(--glass-text-accent)',\n color: 'white'\n }}\n >\n Add Field\n </button>\n </div>\n \n {availabilitySettings.custom_fields.length === 0 ? (\n <p className=\"text-white/60\">No custom fields configured</p>\n ) : (\n <div className=\"space-y-4\">\n {availabilitySettings.custom_fields.map((field) => (\n <div key={field.id} className=\"bg-white/5 border border-white/10 rounded-lg p-4\">\n <div className=\"grid grid-cols-1 md:grid-cols-3 gap-4\">\n <div>\n <label className=\"block text-sm font-medium text-white/80 mb-1\">Label</label>\n <input\n type=\"text\"\n value={field.label}\n onChange={(e) => updateCustomField(field.id, { label: e.target.value })}\n className=\"w-full px-3 py-2 bg-white/10 border border-white/20 rounded-lg text-white text-sm focus:ring-2 focus:ring-blue-500 focus:border-transparent\"\n placeholder=\"Field label\"\n />\n </div>\n <div>\n <label className=\"block text-sm font-medium text-white/80 mb-1\">Type</label>\n <select\n value={field.type}\n onChange={(e) => updateCustomField(field.id, { type: e.target.value as any })}\n className=\"w-full px-3 py-2 bg-white/10 border border-white/20 rounded-lg text-white text-sm focus:ring-2 focus:ring-blue-500 focus:border-transparent\"\n >\n <option value=\"text\">Text</option>\n <option value=\"select\">Select</option>\n <option value=\"checkbox\">Checkbox</option>\n </select>\n </div>\n <div className=\"flex items-end gap-2\">\n <label className=\"flex items-center\">\n <input\n type=\"checkbox\"\n checked={field.required}\n onChange={(e) => updateCustomField(field.id, { required: e.target.checked })}\n className=\"w-4 h-4 text-blue-600 bg-white/10 border-white/20 rounded focus:ring-blue-500\"\n />\n <span className=\"ml-2 text-white text-sm\">Required</span>\n </label>\n <button\n onClick={() => removeCustomField(field.id)}\n className=\"p-2 text-white/60 hover:text-red-400 transition-colors\"\n title=\"Remove field\"\n >\n <svg className=\"w-4 h-4\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16\" />\n </svg>\n </button>\n </div>\n </div>\n \n {field.type === 'select' && (\n <div className=\"mt-3\">\n <label className=\"block text-sm font-medium text-white/80 mb-1\">Options (one per line)</label>\n <textarea\n value={field.options?.join('\\n') || ''}\n onChange={(e) => updateCustomField(field.id, { options: e.target.value.split('\\n').filter(o => o.trim()) })}\n rows={3}\n className=\"w-full px-3 py-2 bg-white/10 border border-white/20 rounded-lg text-white text-sm focus:ring-2 focus:ring-blue-500 focus:border-transparent resize-none\"\n placeholder=\"Option 1&#10;Option 2&#10;Option 3\"\n />\n </div>\n )}\n </div>\n ))}\n </div>\n )}\n </div>\n\n {/* Preview Section */}\n <div className=\"bg-white/5 border border-white/20 rounded-xl p-6\">\n <h3 className=\"text-lg font-semibold text-white mb-4\">Checkout Form Preview</h3>\n <div className=\"bg-white/10 rounded-lg p-4 border border-white/10\">\n <div className=\"space-y-4\">\n <div className=\"grid grid-cols-1 md:grid-cols-2 gap-4\">\n <div>\n <label className=\"block text-sm font-medium text-white/80 mb-1\">Name *</label>\n <div className=\"w-full px-3 py-2 bg-white/20 border border-white/30 rounded-lg text-white/60 text-sm\">\n John Doe\n </div>\n </div>\n <div>\n <label className=\"block text-sm font-medium text-white/80 mb-1\">Email *</label>\n <div className=\"w-full px-3 py-2 bg-white/20 border border-white/30 rounded-lg text-white/60 text-sm\">\n john@example.com\n </div>\n </div>\n </div>\n \n {availabilitySettings.require_phone_number && (\n <div>\n <label className=\"block text-sm font-medium text-white/80 mb-1\">Phone Number *</label>\n <div className=\"w-full px-3 py-2 bg-white/20 border border-white/30 rounded-lg text-white/60 text-sm\">\n (555) 123-4567\n </div>\n </div>\n )}\n \n {availabilitySettings.require_address && (\n <div>\n <label className=\"block text-sm font-medium text-white/80 mb-1\">Address *</label>\n <div className=\"w-full px-3 py-2 bg-white/20 border border-white/30 rounded-lg text-white/60 text-sm\">\n 123 Main St, City, State 12345\n </div>\n </div>\n )}\n \n {availabilitySettings.custom_fields.map((field) => (\n <div key={field.id}>\n <label className=\"block text-sm font-medium text-white/80 mb-1\">\n {field.label} {field.required && '*'}\n </label>\n {field.type === 'text' && (\n <div className=\"w-full px-3 py-2 bg-white/20 border border-white/30 rounded-lg text-white/60 text-sm\">\n Sample text input\n </div>\n )}\n {field.type === 'select' && (\n <div className=\"w-full px-3 py-2 bg-white/20 border border-white/30 rounded-lg text-white/60 text-sm\">\n {field.options?.[0] || 'Select an option'}\n </div>\n )}\n {field.type === 'checkbox' && (\n <label className=\"flex items-center\">\n <input\n type=\"checkbox\"\n className=\"w-4 h-4 text-blue-600 bg-white/10 border-white/20 rounded focus:ring-blue-500\"\n disabled\n />\n <span className=\"ml-2 text-white/60 text-sm\">{field.label}</span>\n </label>\n )}\n </div>\n ))}\n </div>\n </div>\n </div>\n </div>\n </div>\n );\n}","usedDeprecatedRules":[]},{"filePath":"/home/tyler/apps/bct-whitelabel/src/components/manage/TabNavigation.tsx","messages":[{"ruleId":"no-undef","severity":2,"message":"'React' is not defined.","line":7,"column":9,"nodeType":"Identifier","messageId":"undef","endLine":7,"endColumn":14},{"ruleId":"no-undef","severity":2,"message":"'React' is not defined.","line":8,"column":14,"nodeType":"Identifier","messageId":"undef","endLine":8,"endColumn":19},{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":8,"column":34,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":8,"endColumn":37,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[200,203],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[200,203],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"no-undef","severity":2,"message":"'React' is not defined.","line":47,"column":20,"nodeType":"Identifier","messageId":"undef","endLine":47,"endColumn":25}],"suppressedMessages":[],"errorCount":3,"fatalErrorCount":0,"warningCount":1,"fixableErrorCount":0,"fixableWarningCount":0,"source":"import { useState } from 'react';\nimport type { EventData } from '../../lib/event-management';\n\ninterface Tab {\n id: string;\n name: string;\n icon: React.ReactNode;\n component: React.ComponentType<any>;\n}\n\ninterface TabNavigationProps {\n tabs: Tab[];\n activeTab: string;\n onTabChange: (tabId: string) => void;\n eventId: string;\n organizationId: string;\n eventData: EventData | null;\n eventSlug?: string | null;\n}\n\nexport default function TabNavigation({ \n tabs, \n activeTab, \n onTabChange, \n eventId, \n organizationId,\n eventData,\n eventSlug\n}: TabNavigationProps) {\n const [mobileMenuOpen, setMobileMenuOpen] = useState(false);\n\n const currentTab = tabs.find(tab => tab.id === activeTab);\n const CurrentTabComponent = currentTab?.component;\n\n return (\n <div className=\"bg-white/10 backdrop-blur-xl border border-white/20 rounded-3xl shadow-2xl overflow-hidden\">\n {/* Tab Navigation */}\n <div className=\"border-b border-white/20\">\n {/* Mobile Tab Dropdown */}\n <div className=\"lg:hidden px-4 py-3\">\n <div className=\"relative\">\n <button\n onClick={() => setMobileMenuOpen(!mobileMenuOpen)}\n className=\"flex items-center justify-between w-full bg-white/10 backdrop-blur-lg border border-white/20 text-white px-4 py-3 rounded-xl focus:ring-2 transition-all duration-200\"\n style={{\n '--tw-ring-color': 'var(--glass-border-focus)'\n } as React.CSSProperties}\n >\n <span className=\"flex items-center gap-2\">\n <span>{currentTab?.icon}</span>\n <span>{currentTab?.name}</span>\n </span>\n <svg \n className={`w-5 h-5 transition-transform duration-200 ${mobileMenuOpen ? 'rotate-180' : ''}`}\n fill=\"none\" \n stroke=\"currentColor\" \n viewBox=\"0 0 24 24\"\n >\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M19 9l-7 7-7-7\" />\n </svg>\n </button>\n \n {mobileMenuOpen && (\n <div className=\"absolute top-full left-0 right-0 mt-2 bg-white/10 backdrop-blur-xl border border-white/20 rounded-xl shadow-2xl z-50\">\n {tabs.map((tab) => (\n <button\n key={tab.id}\n onClick={() => {\n onTabChange(tab.id);\n setMobileMenuOpen(false);\n }}\n className={`w-full text-left px-4 py-3 text-white hover:bg-white/20 transition-colors duration-200 flex items-center gap-2 ${\n activeTab === tab.id ? 'bg-white/20' : ''\n } ${tab.id === tabs[0].id ? 'rounded-t-xl' : ''} ${tab.id === tabs[tabs.length - 1].id ? 'rounded-b-xl' : ''}`}\n >\n <span>{tab.icon}</span>\n <span>{tab.name}</span>\n </button>\n ))}\n </div>\n )}\n </div>\n </div>\n\n {/* Desktop Tab Navigation */}\n <div className=\"hidden lg:flex overflow-x-auto scrollbar-thin scrollbar-thumb-white/20 scrollbar-track-transparent\">\n {tabs.map((tab) => (\n <button\n key={tab.id}\n onClick={() => onTabChange(tab.id)}\n className={`flex items-center gap-2 px-6 py-4 text-sm font-medium transition-colors duration-200 whitespace-nowrap border-b-2 ${\n activeTab === tab.id\n ? 'border-transparent bg-white/5'\n : 'border-transparent text-white/60 hover:text-white hover:bg-white/5'\n }`}\n style={activeTab === tab.id ? {\n borderBottomColor: 'var(--glass-text-accent)',\n color: 'var(--glass-text-accent)'\n } : {}}\n >\n <span>{tab.icon}</span>\n <span>{tab.name}</span>\n </button>\n ))}\n </div>\n </div>\n\n {/* Tab Content */}\n <div className=\"p-6 min-h-[600px]\">\n {CurrentTabComponent && eventData ? (\n <CurrentTabComponent \n eventId={eventId} \n organizationId={organizationId}\n eventSlug={eventSlug}\n />\n ) : (\n <div className=\"flex items-center justify-center py-12\">\n <div className=\"text-center\">\n <div className=\"text-white/60 mb-4\">\n {!CurrentTabComponent ? 'Tab component not found' : 'Event data not loaded'}\n </div>\n <div className=\"text-white/40 text-sm\">\n Debug: activeTab={activeTab}, eventData={eventData ? 'loaded' : 'null'}\n </div>\n </div>\n </div>\n )}\n </div>\n </div>\n );\n}","usedDeprecatedRules":[]},{"filePath":"/home/tyler/apps/bct-whitelabel/src/components/manage/TicketingAccessTab.tsx","messages":[{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":29,"column":54,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":29,"endColumn":57,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[935,938],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[935,938],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":1,"fixableErrorCount":0,"fixableWarningCount":0,"source":"import { useState } from 'react';\nimport TicketsTab from './TicketsTab';\nimport PresaleTab from './PresaleTab';\nimport DiscountTab from './DiscountTab';\nimport PrintedTab from './PrintedTab';\n\ninterface TicketingAccessTabProps {\n eventId: string;\n organizationId: string;\n}\n\nexport default function TicketingAccessTab({ eventId, organizationId }: TicketingAccessTabProps) {\n const [activeSubTab, setActiveSubTab] = useState<'tickets' | 'presale' | 'discount' | 'printed'>('tickets');\n\n const subTabs = [\n { id: 'tickets', label: 'Ticket Types' },\n { id: 'presale', label: 'Access Codes' },\n { id: 'discount', label: 'Discounts' },\n { id: 'printed', label: 'Printed Tickets' }\n ];\n\n return (\n <div>\n {/* Sub-navigation */}\n <div className=\"flex border-b border-white/20 mb-6\">\n {subTabs.map((tab) => (\n <button\n key={tab.id}\n onClick={() => setActiveSubTab(tab.id as any)}\n className={`px-4 py-2 text-sm font-medium transition-colors duration-200 border-b-2 ${\n activeSubTab === tab.id\n ? 'border-blue-500 text-white'\n : 'border-transparent text-white/60 hover:text-white'\n }`}\n >\n {tab.label}\n </button>\n ))}\n </div>\n\n {/* Sub-tab content */}\n <div>\n {activeSubTab === 'tickets' && (\n <TicketsTab eventId={eventId} organizationId={organizationId} />\n )}\n {activeSubTab === 'presale' && (\n <PresaleTab eventId={eventId} organizationId={organizationId} />\n )}\n {activeSubTab === 'discount' && (\n <DiscountTab eventId={eventId} organizationId={organizationId} />\n )}\n {activeSubTab === 'printed' && (\n <PrintedTab eventId={eventId} organizationId={organizationId} />\n )}\n </div>\n </div>\n );\n}","usedDeprecatedRules":[]},{"filePath":"/home/tyler/apps/bct-whitelabel/src/components/manage/TicketsTab.tsx","messages":[{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":14,"column":46,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":14,"endColumn":49,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[644,647],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[644,647],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":34,"column":14,"nodeType":null,"messageId":"unusedVar","endLine":34,"endColumn":19},{"ruleId":"no-empty","severity":1,"message":"Empty block statement.","line":34,"column":21,"nodeType":"BlockStatement","messageId":"unexpected","endLine":36,"endColumn":6,"suggestions":[{"messageId":"suggestComment","data":{"type":"block"},"fix":{"range":[1282,1288],"text":" /* empty */ "},"desc":"Add comment inside empty block statement."}]},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":59,"column":14,"nodeType":null,"messageId":"unusedVar","endLine":59,"endColumn":19},{"ruleId":"no-empty","severity":1,"message":"Empty block statement.","line":59,"column":21,"nodeType":"BlockStatement","messageId":"unexpected","endLine":61,"endColumn":6,"suggestions":[{"messageId":"suggestComment","data":{"type":"block"},"fix":{"range":[1864,1870],"text":" /* empty */ "},"desc":"Add comment inside empty block statement."}]},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":68,"column":14,"nodeType":null,"messageId":"unusedVar","endLine":68,"endColumn":19},{"ruleId":"no-empty","severity":1,"message":"Empty block statement.","line":68,"column":21,"nodeType":"BlockStatement","messageId":"unexpected","endLine":70,"endColumn":6,"suggestions":[{"messageId":"suggestComment","data":{"type":"block"},"fix":{"range":[2072,2078],"text":" /* empty */ "},"desc":"Add comment inside empty block statement."}]}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":7,"fixableErrorCount":0,"fixableWarningCount":0,"source":"import { useState, useEffect } from 'react';\nimport { loadTicketTypes, deleteTicketType, toggleTicketTypeStatus } from '../../lib/ticket-management';\nimport { loadSalesData, calculateSalesMetrics } from '../../lib/sales-analytics';\nimport { formatCurrency } from '../../lib/event-management';\nimport TicketTypeModal from '../modals/TicketTypeModal';\nimport type { TicketType } from '../../lib/ticket-management';\n\ninterface TicketsTabProps {\n eventId: string;\n}\n\nexport default function TicketsTab({ eventId }: TicketsTabProps) {\n const [ticketTypes, setTicketTypes] = useState<TicketType[]>([]);\n const [salesData, setSalesData] = useState<any[]>([]);\n const [viewMode, setViewMode] = useState<'card' | 'list'>('card');\n const [showModal, setShowModal] = useState(false);\n const [editingTicketType, setEditingTicketType] = useState<TicketType | undefined>();\n const [loading, setLoading] = useState(true);\n\n useEffect(() => {\n loadData();\n }, [eventId]);\n\n const loadData = async () => {\n setLoading(true);\n try {\n const [ticketTypesData, salesDataResult] = await Promise.all([\n loadTicketTypes(eventId),\n loadSalesData(eventId)\n ]);\n \n setTicketTypes(ticketTypesData);\n setSalesData(salesDataResult);\n } catch (error) {\n\n } finally {\n setLoading(false);\n }\n };\n\n const handleCreateTicketType = () => {\n setEditingTicketType(undefined);\n setShowModal(true);\n };\n\n const handleEditTicketType = (ticketType: TicketType) => {\n setEditingTicketType(ticketType);\n setShowModal(true);\n };\n\n const handleDeleteTicketType = async (ticketTypeId: string) => {\n if (!confirm('Are you sure you want to delete this ticket type? This action cannot be undone.')) {\n return;\n }\n\n try {\n await deleteTicketType(ticketTypeId);\n await loadData();\n } catch (error) {\n\n }\n };\n\n const handleToggleStatus = async (ticketType: TicketType) => {\n try {\n await toggleTicketTypeStatus(ticketType.id, !ticketType.is_active);\n await loadData();\n } catch (error) {\n\n }\n };\n\n const getSalesStats = (ticketTypeId: string) => {\n const salesMetrics = calculateSalesMetrics(salesData.filter(sale => sale.ticket_type_id === ticketTypeId));\n return {\n sold: salesMetrics.totalTickets,\n revenue: salesMetrics.totalRevenue,\n available: ticketTypes.find(tt => tt.id === ticketTypeId)?.available || 0\n };\n };\n\n const renderTicketCard = (ticketType: TicketType) => {\n const stats = getSalesStats(ticketType.id);\n const percentage = ticketType.quantity > 0 \n ? (stats.sold / ticketType.quantity) * 100 \n : 0;\n\n const isActive = ticketType.is_active && ticketType.sale_start <= new Date() && \n (!ticketType.sale_end || ticketType.sale_end >= new Date());\n\n return (\n <div \n key={ticketType.id} \n className=\"rounded-xl p-6 transition-all duration-200 glass-card hover:shadow-lg\"\n >\n <div className=\"flex justify-between items-start mb-4\">\n <div className=\"flex-1\">\n <div className=\"flex items-center gap-3 mb-2\">\n <h3 className=\"text-xl font-semibold\" style={{ color: 'var(--glass-text-primary)' }}>\n {ticketType.name}\n </h3>\n <span \n className={`px-2 py-1 text-xs font-medium rounded-full ${\n isActive ? 'premium-success' : 'premium-error'\n }`}\n >\n {isActive ? 'Active' : 'Inactive'}\n </span>\n </div>\n {ticketType.description && (\n <p className=\"text-sm mb-3\" style={{ color: 'var(--glass-text-secondary)' }}>\n {ticketType.description}\n </p>\n )}\n </div>\n <div className=\"text-2xl font-bold mb-2\" style={{ color: 'var(--glass-text-primary)' }}>\n {formatCurrency(ticketType.price)}\n {ticketType.fees_included && <span className=\"text-sm font-normal\" style={{ color: 'var(--glass-text-tertiary)' }}> (fees included)</span>}\n </div>\n </div>\n\n <div className=\"flex gap-2 mb-4\">\n <button\n onClick={() => handleEditTicketType(ticketType)}\n className=\"p-2 transition-colors hover:opacity-80\"\n style={{ color: 'var(--glass-text-tertiary)' }}\n >\n <svg className=\"w-5 h-5\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z\" />\n </svg>\n </button>\n <button\n onClick={() => handleToggleStatus(ticketType)}\n className=\"p-2 transition-colors hover:opacity-80\"\n style={{ color: 'var(--glass-text-tertiary)' }}\n >\n {ticketType.is_active ? (\n <svg className=\"w-5 h-5\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M10 9v6m4-6v6m7-3a9 9 0 11-18 0 9 9 0 0118 0z\" />\n </svg>\n ) : (\n <svg className=\"w-5 h-5\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M14.752 11.168l-3.197-2.132A1 1 0 0010 9.87v4.263a1 1 0 001.555.832l3.197-2.132a1 1 0 000-1.664z\" />\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M21 12a9 9 0 11-18 0 9 9 0 0118 0z\" />\n </svg>\n )}\n </button>\n <button\n onClick={() => handleDeleteTicketType(ticketType.id)}\n className=\"p-2 transition-colors hover:opacity-80\"\n style={{ color: 'var(--error-color)' }}\n >\n <svg className=\"w-5 h-5\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16\" />\n </svg>\n </button>\n </div>\n\n <div className=\"grid grid-cols-3 gap-4 mb-4\">\n <div className=\"text-center\">\n <div style={{ color: 'var(--glass-text-tertiary)' }}>Sold</div>\n <div className=\"font-semibold\" style={{ color: 'var(--glass-text-primary)' }}>{stats.sold}</div>\n </div>\n <div className=\"text-center\">\n <div style={{ color: 'var(--glass-text-tertiary)' }}>Available</div>\n <div className=\"font-semibold\" style={{ color: 'var(--glass-text-primary)' }}>{stats.available}</div>\n </div>\n <div className=\"text-center\">\n <div style={{ color: 'var(--glass-text-tertiary)' }}>Revenue</div>\n <div className=\"font-semibold\" style={{ color: 'var(--glass-text-primary)' }}>{formatCurrency(stats.revenue)}</div>\n </div>\n </div>\n \n <div className=\"space-y-2\">\n <div className=\"w-full rounded-full h-2\" style={{ background: 'var(--glass-bg)' }}>\n <div \n className=\"h-2 rounded-full bg-gradient-to-r from-blue-600 to-purple-600\"\n style={{ width: `${Math.min(percentage, 100)}%` }}\n />\n </div>\n <div className=\"text-xs\" style={{ color: 'var(--glass-text-tertiary)' }}>\n {percentage.toFixed(1)}% sold ({stats.sold} of {ticketType.quantity})\n </div>\n </div>\n </div>\n );\n };\n\n const renderTicketRow = (ticketType: TicketType) => {\n const stats = getSalesStats(ticketType.id);\n const percentage = ticketType.quantity > 0 \n ? (stats.sold / ticketType.quantity) * 100 \n : 0;\n\n const isActive = ticketType.is_active && ticketType.sale_start <= new Date() && \n (!ticketType.sale_end || ticketType.sale_end >= new Date());\n\n return (\n <tr key={ticketType.id} className=\"border-b transition-colors hover:opacity-90\" style={{ borderColor: 'var(--glass-border)' }}>\n <td className=\"py-4 px-4\">\n <div>\n <div className=\"font-semibold\" style={{ color: 'var(--glass-text-primary)' }}>{ticketType.name}</div>\n {ticketType.description && (\n <div className=\"text-sm\" style={{ color: 'var(--glass-text-secondary)' }}>{ticketType.description}</div>\n )}\n <span \n className={`inline-block mt-1 px-2 py-1 text-xs font-medium rounded-full ${\n isActive ? 'premium-success' : 'premium-error'\n }`}\n >\n {isActive ? 'Active' : 'Inactive'}\n </span>\n </div>\n </td>\n <td className=\"py-4 px-4 font-semibold\" style={{ color: 'var(--glass-text-primary)' }}>\n {formatCurrency(ticketType.price)}\n </td>\n <td className=\"py-4 px-4\" style={{ color: 'var(--glass-text-primary)' }}>{stats.sold}</td>\n <td className=\"py-4 px-4\" style={{ color: 'var(--glass-text-primary)' }}>{stats.available}</td>\n <td className=\"py-4 px-4 font-semibold\" style={{ color: 'var(--glass-text-primary)' }}>\n {formatCurrency(stats.revenue)}\n </td>\n <td className=\"py-4 px-4\">\n <div className=\"flex items-center gap-2\">\n <div className=\"w-20 rounded-full h-2\" style={{ background: 'var(--glass-bg)' }}>\n <div \n className=\"h-2 rounded-full bg-gradient-to-r from-blue-600 to-purple-600\"\n style={{ width: `${Math.min(percentage, 100)}%` }}\n />\n </div>\n <span className=\"text-sm\" style={{ color: 'var(--glass-text-tertiary)' }}>{percentage.toFixed(1)}%</span>\n </div>\n </td>\n <td className=\"py-4 px-4\">\n <div className=\"flex gap-2\">\n <button\n onClick={() => handleEditTicketType(ticketType)}\n className=\"p-2 transition-colors hover:opacity-80\"\n style={{ color: 'var(--glass-text-tertiary)' }}\n >\n <svg className=\"w-5 h-5\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z\" />\n </svg>\n </button>\n <button\n onClick={() => handleToggleStatus(ticketType)}\n className=\"p-2 transition-colors hover:opacity-80\"\n style={{ color: 'var(--glass-text-tertiary)' }}\n >\n {ticketType.is_active ? (\n <svg className=\"w-5 h-5\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M10 9v6m4-6v6m7-3a9 9 0 11-18 0 9 9 0 0118 0z\" />\n </svg>\n ) : (\n <svg className=\"w-5 h-5\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M14.752 11.168l-3.197-2.132A1 1 0 0010 9.87v4.263a1 1 0 001.555.832l3.197-2.132a1 1 0 000-1.664z\" />\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M21 12a9 9 0 11-18 0 9 9 0 0118 0z\" />\n </svg>\n )}\n </button>\n <button\n onClick={() => handleDeleteTicketType(ticketType.id)}\n className=\"p-2 transition-colors hover:opacity-80\"\n style={{ color: 'var(--error-color)' }}\n >\n <svg className=\"w-5 h-5\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16\" />\n </svg>\n </button>\n </div>\n </td>\n </tr>\n );\n };\n\n if (loading) {\n return (\n <div className=\"flex items-center justify-center py-12\">\n <div className=\"animate-spin rounded-full h-8 w-8 border-b-2\" style={{ borderColor: 'var(--glass-text-primary)' }}></div>\n </div>\n );\n }\n\n return (\n <div className=\"space-y-6\">\n <div className=\"flex justify-between items-center\">\n <h2 className=\"text-2xl font-light\" style={{ color: 'var(--glass-text-primary)' }}>Ticket Types & Pricing</h2>\n <div className=\"flex items-center gap-3\">\n <div className=\"flex items-center rounded-lg p-1\" style={{ background: 'var(--glass-bg)' }}>\n <button\n onClick={() => setViewMode('card')}\n className={`px-3 py-1.5 rounded-md transition-all ${\n viewMode === 'card' \n ? '' \n : ''\n }`}\n style={{\n background: viewMode === 'card' ? 'var(--glass-bg-lg)' : 'transparent',\n color: viewMode === 'card' ? 'var(--glass-text-primary)' : 'var(--glass-text-tertiary)'\n }}\n >\n <svg className=\"w-5 h-5\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M4 6a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2H6a2 2 0 01-2-2V6zM14 6a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2V6zM4 16a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2H6a2 2 0 01-2-2v-2zM14 16a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2v-2z\" />\n </svg>\n </button>\n <button\n onClick={() => setViewMode('list')}\n className={`px-3 py-1.5 rounded-md transition-all ${\n viewMode === 'list' \n ? '' \n : ''\n }`}\n style={{\n background: viewMode === 'list' ? 'var(--glass-bg-lg)' : 'transparent',\n color: viewMode === 'list' ? 'var(--glass-text-primary)' : 'var(--glass-text-tertiary)'\n }}\n >\n <svg className=\"w-5 h-5\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M4 6h16M4 12h16M4 18h16\" />\n </svg>\n </button>\n </div>\n <button\n onClick={handleCreateTicketType}\n className=\"flex items-center gap-2 px-4 py-2 bg-gradient-to-r from-blue-600 to-purple-600 hover:from-blue-700 hover:to-purple-700 rounded-lg font-medium transition-all duration-200\"\n style={{ color: '#ffffff' }}\n >\n <svg className=\"w-5 h-5\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M12 6v6m0 0v6m0-6h6m-6 0H6\" />\n </svg>\n Add Ticket Type\n </button>\n </div>\n </div>\n\n {ticketTypes.length === 0 ? (\n <div className=\"text-center py-12 rounded-xl glass-card\">\n <svg className=\"w-12 h-12 mx-auto mb-4\" style={{ color: 'var(--glass-text-tertiary)' }} fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M15 5v2m0 4v2m0 4v2M5 5a2 2 0 00-2 2v3a2 2 0 110 4v3a2 2 0 002 2h14a2 2 0 002-2v-3a2 2 0 110-4V7a2 2 0 00-2-2H5z\" />\n </svg>\n <p className=\"mb-4\" style={{ color: 'var(--glass-text-secondary)' }}>No ticket types created yet</p>\n <button\n onClick={handleCreateTicketType}\n className=\"px-6 py-3 bg-gradient-to-r from-blue-600 to-purple-600 hover:from-blue-700 hover:to-purple-700 rounded-lg font-medium transition-all duration-200\"\n style={{ color: '#ffffff' }}\n >\n Create Your First Ticket Type\n </button>\n </div>\n ) : (\n <>\n {viewMode === 'card' ? (\n <div className=\"grid grid-cols-1 md:grid-cols-2 gap-6\">\n {ticketTypes.map(renderTicketCard)}\n </div>\n ) : (\n <div className=\"rounded-xl overflow-hidden glass-card\">\n <table className=\"w-full\">\n <thead style={{ background: 'var(--glass-bg-lg)' }}>\n <tr>\n <th className=\"text-left py-3 px-4 font-medium\" style={{ color: 'var(--glass-text-secondary)' }}>Name</th>\n <th className=\"text-left py-3 px-4 font-medium\" style={{ color: 'var(--glass-text-secondary)' }}>Price</th>\n <th className=\"text-left py-3 px-4 font-medium\" style={{ color: 'var(--glass-text-secondary)' }}>Sold</th>\n <th className=\"text-left py-3 px-4 font-medium\" style={{ color: 'var(--glass-text-secondary)' }}>Available</th>\n <th className=\"text-left py-3 px-4 font-medium\" style={{ color: 'var(--glass-text-secondary)' }}>Revenue</th>\n <th className=\"text-left py-3 px-4 font-medium\" style={{ color: 'var(--glass-text-secondary)' }}>Progress</th>\n <th className=\"text-left py-3 px-4 font-medium\" style={{ color: 'var(--glass-text-secondary)' }}>Actions</th>\n </tr>\n </thead>\n <tbody>\n {ticketTypes.map(renderTicketRow)}\n </tbody>\n </table>\n </div>\n )}\n </>\n )}\n\n {showModal && (\n <TicketTypeModal\n eventId={eventId}\n ticketType={editingTicketType}\n onClose={() => setShowModal(false)}\n onSave={loadData}\n />\n )}\n </div>\n );\n}","usedDeprecatedRules":[]},{"filePath":"/home/tyler/apps/bct-whitelabel/src/components/manage/VenueTab.tsx","messages":[{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'createSeatingMap' is defined but never used. Allowed unused vars must match /^_/u.","line":4,"column":3,"nodeType":null,"messageId":"unusedVar","endLine":4,"endColumn":19},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'venueData' is assigned a value but never used. Allowed unused vars must match /^_/u.","line":23,"column":10,"nodeType":null,"messageId":"unusedVar","endLine":23,"endColumn":19},{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":23,"column":46,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":23,"endColumn":49,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[834,837],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[834,837],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":56,"column":14,"nodeType":null,"messageId":"unusedVar","endLine":56,"endColumn":19},{"ruleId":"no-empty","severity":1,"message":"Empty block statement.","line":56,"column":21,"nodeType":"BlockStatement","messageId":"unexpected","endLine":58,"endColumn":6,"suggestions":[{"messageId":"suggestComment","data":{"type":"block"},"fix":{"range":[1801,1807],"text":" /* empty */ "},"desc":"Add comment inside empty block statement."}]}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":5,"fixableErrorCount":0,"fixableWarningCount":0,"source":"import { useState, useEffect } from 'react';\nimport { \n loadSeatingMaps, \n createSeatingMap, \n deleteSeatingMap, \n applySeatingMapToEvent,\n type SeatingMap,\n type LayoutItem\n} from '../../lib/seating-management';\nimport { loadEventData, updateEventData } from '../../lib/event-management';\nimport SeatingMapModal from '../modals/SeatingMapModal';\n\ninterface VenueTabProps {\n eventId: string;\n organizationId: string;\n}\n\nexport default function VenueTab({ eventId, organizationId }: VenueTabProps) {\n const [seatingMaps, setSeatingMaps] = useState<SeatingMap[]>([]);\n const [currentSeatingMap, setCurrentSeatingMap] = useState<SeatingMap | null>(null);\n const [showModal, setShowModal] = useState(false);\n const [editingMap, setEditingMap] = useState<SeatingMap | undefined>();\n const [venueData, setVenueData] = useState<any>(null);\n const [seatingType, setSeatingType] = useState<'general' | 'assigned'>('general');\n const [loading, setLoading] = useState(true);\n\n useEffect(() => {\n loadData();\n }, [eventId, organizationId]);\n\n const loadData = async () => {\n setLoading(true);\n try {\n\n const [mapsData, eventData] = await Promise.all([\n loadSeatingMaps(organizationId),\n loadEventData(eventId, organizationId)\n ]);\n\n // Validate seating maps data\n const validMaps = mapsData.filter(map => {\n if (!map.layout_data) {\n\n return false;\n }\n return true;\n });\n \n setSeatingMaps(validMaps);\n setVenueData({\n venue: eventData?.venue || '',\n seating_type: eventData?.seating_type || 'general_admission'\n });\n setCurrentSeatingMap(eventData?.seating_map || null);\n setSeatingType(eventData?.seating_type === 'assigned' ? 'assigned' : 'general');\n } catch (error) {\n\n } finally {\n setLoading(false);\n }\n };\n\n const handleCreateSeatingMap = () => {\n setEditingMap(undefined);\n setShowModal(true);\n };\n\n const handleEditSeatingMap = (seatingMap: SeatingMap) => {\n setEditingMap(seatingMap);\n setShowModal(true);\n };\n\n const handleDeleteSeatingMap = async (seatingMap: SeatingMap) => {\n if (confirm(`Are you sure you want to delete \"${seatingMap.name}\"?`)) {\n const success = await deleteSeatingMap(seatingMap.id);\n if (success) {\n setSeatingMaps(prev => prev.filter(m => m.id !== seatingMap.id));\n if (currentSeatingMap?.id === seatingMap.id) {\n setCurrentSeatingMap(null);\n }\n }\n }\n };\n\n const handleApplySeatingMap = async (seatingMap: SeatingMap) => {\n const success = await applySeatingMapToEvent(eventId, seatingMap.id);\n if (success) {\n setCurrentSeatingMap(seatingMap);\n setSeatingType('assigned');\n }\n };\n\n const handleRemoveSeatingMap = async () => {\n const success = await updateEventData(eventId, { seating_map_id: null });\n if (success) {\n setCurrentSeatingMap(null);\n setSeatingType('general');\n }\n };\n\n const handleModalSave = (seatingMap: SeatingMap) => {\n if (editingMap) {\n setSeatingMaps(prev => prev.map(m => \n m.id === seatingMap.id ? seatingMap : m\n ));\n } else {\n setSeatingMaps(prev => [...prev, seatingMap]);\n }\n setShowModal(false);\n };\n\n const renderSeatingPreview = (seatingMap: SeatingMap) => {\n const layoutItems = Array.isArray(seatingMap.layout_data) \n ? seatingMap.layout_data as LayoutItem[] \n : [];\n const totalCapacity = layoutItems.reduce((sum, item) => sum + (item.capacity || 0), 0);\n\n return (\n <div className=\"bg-white/5 border border-white/20 rounded-xl p-6 hover:bg-white/10 transition-all duration-200\">\n <div className=\"flex justify-between items-start mb-4\">\n <div>\n <h3 className=\"text-xl font-semibold text-white mb-2\">{seatingMap.name}</h3>\n <div className=\"flex items-center gap-4 text-sm text-white/60\">\n <span>{layoutItems.length} sections</span>\n <span>{totalCapacity} capacity</span>\n <span>Created {new Date(seatingMap.created_at).toLocaleDateString()}</span>\n </div>\n </div>\n <div className=\"flex items-center gap-2\">\n <button\n onClick={() => handleEditSeatingMap(seatingMap)}\n className=\"p-2 text-white/60 hover:text-white transition-colors\"\n title=\"Edit\"\n >\n <svg className=\"w-4 h-4\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z\" />\n </svg>\n </button>\n <button\n onClick={() => handleDeleteSeatingMap(seatingMap)}\n className=\"p-2 text-white/60 hover:text-red-400 transition-colors\"\n title=\"Delete\"\n >\n <svg className=\"w-4 h-4\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16\" />\n </svg>\n </button>\n </div>\n </div>\n\n <div className=\"mb-4\">\n <div className=\"bg-white/5 border border-white/10 rounded-lg p-4 h-48 relative overflow-hidden\">\n <div className=\"absolute inset-0 flex items-center justify-center\">\n <div className=\"text-center\">\n <div className=\"text-white/40 mb-2\">Layout Preview</div>\n <div className=\"grid grid-cols-4 gap-2 max-w-32\">\n {layoutItems.slice(0, 16).map((item, index) => (\n <div\n key={index}\n className={`w-6 h-6 rounded border-2 border-dashed ${\n item.type === 'table' ? 'border-blue-400/60 bg-blue-500/20' :\n item.type === 'seat_row' ? 'border-green-400/60 bg-green-500/20' :\n 'border-purple-400/60 bg-purple-500/20'\n }`}\n />\n ))}\n </div>\n {layoutItems.length > 16 && (\n <div className=\"text-xs text-white/40 mt-2\">\n +{layoutItems.length - 16} more sections\n </div>\n )}\n </div>\n </div>\n </div>\n </div>\n\n <div className=\"flex items-center justify-between\">\n <div className=\"text-sm text-white/60\">\n Capacity: {totalCapacity} people\n </div>\n <div className=\"flex items-center gap-2\">\n {currentSeatingMap?.id === seatingMap.id ? (\n <div className=\"flex items-center gap-2\">\n <span className=\"px-3 py-1 bg-green-500/20 text-green-300 border border-green-500/30 rounded-lg text-sm\">\n Currently Applied\n </span>\n <button\n onClick={handleRemoveSeatingMap}\n className=\"px-3 py-1 bg-red-500/20 text-red-300 border border-red-500/30 rounded-lg text-sm hover:bg-red-500/30 transition-colors\"\n >\n Remove\n </button>\n </div>\n ) : (\n <button\n onClick={() => handleApplySeatingMap(seatingMap)}\n className=\"px-4 py-2 text-white rounded-lg text-sm transition-colors\"\n style={{\n background: 'var(--glass-text-accent)'\n }}\n >\n Apply to Event\n </button>\n )}\n </div>\n </div>\n </div>\n );\n };\n\n if (loading) {\n return (\n <div className=\"flex items-center justify-center py-12\">\n <div className=\"animate-spin rounded-full h-8 w-8 border-b-2 border-white\"></div>\n </div>\n );\n }\n\n return (\n <div className=\"space-y-6\">\n <div className=\"flex justify-between items-center\">\n <h2 className=\"text-2xl font-light text-white\">Venue & Seating</h2>\n <button\n onClick={handleCreateSeatingMap}\n className=\"flex items-center gap-2 px-4 py-2 rounded-lg font-medium transition-all duration-200\"\n style={{\n background: 'var(--glass-text-accent)',\n color: 'white'\n }}\n >\n <svg className=\"w-4 h-4\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M12 6v6m0 0v6m0-6h6m-6 0H6\" />\n </svg>\n Create Seating Map\n </button>\n </div>\n\n {/* Seating Type Selection */}\n <div className=\"bg-white/5 border border-white/20 rounded-xl p-6\">\n <h3 className=\"text-lg font-semibold text-white mb-4\">Seating Configuration</h3>\n <div className=\"grid grid-cols-1 md:grid-cols-2 gap-4\">\n <div \n className={`p-4 border-2 rounded-lg cursor-pointer transition-all ${\n seatingType === 'general' \n ? 'border-blue-500 bg-blue-500/10' \n : 'border-white/20 hover:border-white/40'\n }`}\n onClick={() => setSeatingType('general')}\n >\n <div className=\"flex items-center justify-between mb-2\">\n <h4 className=\"font-semibold text-white\">General Admission</h4>\n <div className={`w-4 h-4 rounded-full border-2 ${\n seatingType === 'general' ? 'border-blue-500 bg-blue-500' : 'border-white/40'\n }`} />\n </div>\n <p className=\"text-white/60 text-sm\">\n No assigned seats. First-come, first-served seating arrangement.\n </p>\n </div>\n\n <div \n className={`p-4 border-2 rounded-lg cursor-pointer transition-all ${\n seatingType === 'assigned' \n ? 'border-blue-500 bg-blue-500/10' \n : 'border-white/20 hover:border-white/40'\n }`}\n onClick={() => setSeatingType('assigned')}\n >\n <div className=\"flex items-center justify-between mb-2\">\n <h4 className=\"font-semibold text-white\">Assigned Seating</h4>\n <div className={`w-4 h-4 rounded-full border-2 ${\n seatingType === 'assigned' ? 'border-blue-500 bg-blue-500' : 'border-white/40'\n }`} />\n </div>\n <p className=\"text-white/60 text-sm\">\n Specific seat assignments with custom venue layout.\n </p>\n </div>\n </div>\n </div>\n\n {/* Current Seating Map */}\n {currentSeatingMap && (\n <div className=\"bg-white/5 border border-white/20 rounded-xl p-6\">\n <h3 className=\"text-lg font-semibold text-white mb-4\">Current Seating Map</h3>\n {renderSeatingPreview(currentSeatingMap)}\n </div>\n )}\n\n {/* Available Seating Maps */}\n <div>\n <h3 className=\"text-lg font-semibold text-white mb-4\">\n Available Seating Maps ({seatingMaps.length})\n </h3>\n \n {seatingMaps.length === 0 ? (\n <div className=\"text-center py-12\">\n <svg className=\"w-12 h-12 text-white/40 mx-auto mb-4\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0h2m-2 0h-5m-9 0H3m2 0h5M9 7h1m-1 4h1m4-4h1m-1 4h1m-5 10v-5a1 1 0 011-1h2a1 1 0 011 1v5m-4 0h4\" />\n </svg>\n <p className=\"text-white/60 mb-4\">No seating maps created yet</p>\n <button\n onClick={handleCreateSeatingMap}\n className=\"px-6 py-3 rounded-lg font-medium transition-all duration-200\"\n style={{\n background: 'var(--glass-text-accent)',\n color: 'white'\n }}\n >\n Create Your First Seating Map\n </button>\n </div>\n ) : (\n <div className=\"grid grid-cols-1 lg:grid-cols-2 gap-6\">\n {seatingMaps.map((seatingMap) => (\n <div key={seatingMap.id}>\n {renderSeatingPreview(seatingMap)}\n </div>\n ))}\n </div>\n )}\n </div>\n\n <SeatingMapModal\n isOpen={showModal}\n onClose={() => setShowModal(false)}\n onSave={handleModalSave}\n organizationId={organizationId}\n seatingMap={editingMap}\n />\n </div>\n );\n}","usedDeprecatedRules":[]},{"filePath":"/home/tyler/apps/bct-whitelabel/src/components/modals/EmbedCodeModal.tsx","messages":[{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'useEffect' is defined but never used. Allowed unused vars must match /^_/u.","line":1,"column":20,"nodeType":null,"messageId":"unusedVar","endLine":1,"endColumn":29},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'eventId' is defined but never used. Allowed unused args must match /^_/u.","line":14,"column":3,"nodeType":null,"messageId":"unusedVar","endLine":14,"endColumn":10},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":58,"column":14,"nodeType":null,"messageId":"unusedVar","endLine":58,"endColumn":19},{"ruleId":"no-empty","severity":1,"message":"Empty block statement.","line":58,"column":21,"nodeType":"BlockStatement","messageId":"unexpected","endLine":60,"endColumn":6,"suggestions":[{"messageId":"suggestComment","data":{"type":"block"},"fix":{"range":[1797,1803],"text":" /* empty */ "},"desc":"Add comment inside empty block statement."}]}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":4,"fixableErrorCount":0,"fixableWarningCount":0,"source":"import { useState, useEffect } from 'react';\nimport { copyToClipboard } from '../../lib/marketing-kit';\n\ninterface EmbedCodeModalProps {\n isOpen: boolean;\n onClose: () => void;\n eventId: string;\n eventSlug: string;\n}\n\nexport default function EmbedCodeModal({ \n isOpen, \n onClose, \n eventId, \n eventSlug \n}: EmbedCodeModalProps) {\n const [embedType, setEmbedType] = useState<'basic' | 'custom'>('basic');\n const [width, setWidth] = useState(400);\n const [height, setHeight] = useState(600);\n const [theme, setTheme] = useState<'light' | 'dark'>('light');\n const [showHeader, setShowHeader] = useState(true);\n const [showDescription, setShowDescription] = useState(true);\n const [primaryColor, setPrimaryColor] = useState('#2563eb');\n const [copied, setCopied] = useState<string | null>(null);\n\n const baseUrl = 'https://portal.blackcanyontickets.com';\n const directLink = `${baseUrl}/e/${eventSlug}`;\n const embedUrl = `${baseUrl}/embed/${eventSlug}`;\n\n const generateEmbedCode = () => {\n const params = new URLSearchParams();\n \n if (embedType === 'custom') {\n params.append('theme', theme);\n params.append('header', showHeader.toString());\n params.append('description', showDescription.toString());\n params.append('color', primaryColor.replace('#', ''));\n }\n\n const paramString = params.toString();\n const finalUrl = paramString ? `${embedUrl}?${paramString}` : embedUrl;\n\n return `<iframe \n src=\"${finalUrl}\" \n width=\"${width}\" \n height=\"${height}\" \n frameborder=\"0\" \n scrolling=\"no\"\n title=\"Event Tickets\">\n</iframe>`;\n };\n\n const handleCopy = async (content: string, type: string) => {\n try {\n await copyToClipboard(content);\n setCopied(type);\n setTimeout(() => setCopied(null), 2000);\n } catch (error) {\n\n }\n };\n\n const previewUrl = embedType === 'custom' \n ? `${embedUrl}?theme=${theme}&header=${showHeader}&description=${showDescription}&color=${primaryColor.replace('#', '')}`\n : embedUrl;\n\n if (!isOpen) return null;\n\n return (\n <div className=\"fixed inset-0 bg-black/50 backdrop-blur-sm flex items-center justify-center p-4 z-50\">\n <div className=\"bg-white/10 backdrop-blur-xl border border-white/20 rounded-2xl shadow-2xl w-full max-w-sm sm:max-w-2xl lg:max-w-4xl max-h-[90vh] overflow-y-auto\">\n <div className=\"p-6\">\n <div className=\"flex justify-between items-center mb-6\">\n <h2 className=\"text-2xl font-light text-white\">Embed Event Widget</h2>\n <button\n onClick={onClose}\n className=\"text-white/60 hover:text-white transition-colors p-2 rounded-full hover:bg-white/10 touch-manipulation\"\n aria-label=\"Close modal\"\n >\n <svg className=\"w-5 h-5 sm:w-6 sm:h-6\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M6 18L18 6M6 6l12 12\" />\n </svg>\n </button>\n </div>\n\n <div className=\"grid grid-cols-1 xl:grid-cols-2 gap-6\">\n {/* Configuration Panel */}\n <div className=\"space-y-6\">\n <div>\n <h3 className=\"text-lg font-medium text-white mb-4\">Direct Link</h3>\n <div className=\"bg-white/5 border border-white/20 rounded-lg p-4\">\n <div className=\"mb-2\">\n <label className=\"text-sm text-white/80\">Event URL</label>\n </div>\n <div className=\"flex\">\n <input\n type=\"text\"\n value={directLink}\n readOnly\n className=\"flex-1 px-3 py-2 bg-white/10 border border-white/20 rounded-l-lg text-white text-sm\"\n />\n <button\n onClick={() => handleCopy(directLink, 'link')}\n className=\"px-4 py-2 rounded-r-lg transition-all duration-200 hover:shadow-lg hover:scale-105\"\n style={{\n background: 'var(--glass-text-accent)',\n color: 'white'\n }}\n >\n {copied === 'link' ? '✓' : 'Copy'}\n </button>\n </div>\n </div>\n </div>\n\n <div>\n <h3 className=\"text-lg font-medium text-white mb-4\">Embed Options</h3>\n \n <div className=\"space-y-4\">\n <div>\n <label className=\"block text-sm text-white/80 mb-2\">Embed Type</label>\n <div className=\"flex space-x-4\">\n <label className=\"flex items-center\">\n <input\n type=\"radio\"\n value=\"basic\"\n checked={embedType === 'basic'}\n onChange={(e) => setEmbedType(e.target.value as 'basic' | 'custom')}\n className=\"w-4 h-4 text-blue-600 bg-white/10 border-white/20\"\n />\n <span className=\"ml-2 text-white text-sm\">Basic</span>\n </label>\n <label className=\"flex items-center\">\n <input\n type=\"radio\"\n value=\"custom\"\n checked={embedType === 'custom'}\n onChange={(e) => setEmbedType(e.target.value as 'basic' | 'custom')}\n className=\"w-4 h-4 text-blue-600 bg-white/10 border-white/20\"\n />\n <span className=\"ml-2 text-white text-sm\">Custom</span>\n </label>\n </div>\n </div>\n\n <div className=\"grid grid-cols-2 gap-4\">\n <div>\n <label className=\"block text-sm text-white/80 mb-2\">Width</label>\n <input\n type=\"number\"\n value={width}\n onChange={(e) => setWidth(parseInt(e.target.value) || 400)}\n className=\"w-full px-3 py-2 bg-white/10 border border-white/20 rounded-lg text-white text-sm\"\n min=\"300\"\n max=\"800\"\n />\n </div>\n <div>\n <label className=\"block text-sm text-white/80 mb-2\">Height</label>\n <input\n type=\"number\"\n value={height}\n onChange={(e) => setHeight(parseInt(e.target.value) || 600)}\n className=\"w-full px-3 py-2 bg-white/10 border border-white/20 rounded-lg text-white text-sm\"\n min=\"400\"\n max=\"1000\"\n />\n </div>\n </div>\n\n {embedType === 'custom' && (\n <div className=\"space-y-4\">\n <div>\n <label className=\"block text-sm text-white/80 mb-2\">Theme</label>\n <select\n value={theme}\n onChange={(e) => setTheme(e.target.value as 'light' | 'dark')}\n className=\"w-full px-3 py-2 bg-white/10 border border-white/20 rounded-lg text-white text-sm\"\n >\n <option value=\"light\">Light</option>\n <option value=\"dark\">Dark</option>\n </select>\n </div>\n\n <div>\n <label className=\"block text-sm text-white/80 mb-2\">Primary Color</label>\n <input\n type=\"color\"\n value={primaryColor}\n onChange={(e) => setPrimaryColor(e.target.value)}\n className=\"w-full h-10 bg-white/10 border border-white/20 rounded-lg\"\n />\n </div>\n\n <div className=\"space-y-2\">\n <label className=\"flex items-center\">\n <input\n type=\"checkbox\"\n checked={showHeader}\n onChange={(e) => setShowHeader(e.target.checked)}\n className=\"w-4 h-4 text-blue-600 bg-white/10 border-white/20 rounded\"\n />\n <span className=\"ml-2 text-white text-sm\">Show Header</span>\n </label>\n <label className=\"flex items-center\">\n <input\n type=\"checkbox\"\n checked={showDescription}\n onChange={(e) => setShowDescription(e.target.checked)}\n className=\"w-4 h-4 text-blue-600 bg-white/10 border-white/20 rounded\"\n />\n <span className=\"ml-2 text-white text-sm\">Show Description</span>\n </label>\n </div>\n </div>\n )}\n </div>\n </div>\n\n <div>\n <h3 className=\"text-lg font-medium text-white mb-4\">Embed Code</h3>\n <div className=\"bg-white/5 border border-white/20 rounded-lg p-4\">\n <textarea\n value={generateEmbedCode()}\n readOnly\n rows={6}\n className=\"w-full bg-transparent text-white text-sm font-mono resize-none\"\n />\n <div className=\"mt-3 flex justify-end\">\n <button\n onClick={() => handleCopy(generateEmbedCode(), 'embed')}\n className=\"px-4 py-2 rounded-lg transition-all duration-200 hover:shadow-lg hover:scale-105\"\n style={{\n background: 'var(--glass-text-accent)',\n color: 'white'\n }}\n >\n {copied === 'embed' ? '✓ Copied' : 'Copy Code'}\n </button>\n </div>\n </div>\n </div>\n </div>\n\n {/* Preview Panel */}\n <div>\n <h3 className=\"text-lg font-medium text-white mb-4\">Preview</h3>\n <div className=\"bg-white/5 border border-white/20 rounded-lg p-4\">\n <div className=\"bg-white rounded-lg overflow-hidden\">\n <iframe\n src={previewUrl}\n width=\"100%\"\n height=\"400\"\n frameBorder=\"0\"\n scrolling=\"no\"\n title=\"Event Tickets Preview\"\n className=\"w-full\"\n />\n </div>\n </div>\n </div>\n </div>\n\n <div className=\"flex justify-end mt-6\">\n <button\n onClick={onClose}\n className=\"px-6 py-3 bg-gradient-to-r from-blue-600 to-purple-600 hover:from-blue-700 hover:to-purple-700 text-white rounded-lg font-medium transition-all duration-200\"\n >\n Done\n </button>\n </div>\n </div>\n </div>\n </div>\n );\n}","usedDeprecatedRules":[]},{"filePath":"/home/tyler/apps/bct-whitelabel/src/components/modals/SeatingMapModal.tsx","messages":[{"ruleId":"no-undef","severity":2,"message":"'React' is not defined.","line":45,"column":34,"nodeType":"Identifier","messageId":"undef","endLine":45,"endColumn":39},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'updateLayoutItem' is assigned a value but never used. Allowed unused vars must match /^_/u.","line":100,"column":9,"nodeType":null,"messageId":"unusedVar","endLine":100,"endColumn":25}],"suppressedMessages":[],"errorCount":1,"fatalErrorCount":0,"warningCount":1,"fixableErrorCount":0,"fixableWarningCount":0,"source":"import { useState, useEffect } from 'react';\nimport type { SeatingMap, LayoutItem, LayoutType } from '../../lib/seating-management';\nimport { createSeatingMap, updateSeatingMap, generateInitialLayout } from '../../lib/seating-management';\n\ninterface SeatingMapModalProps {\n isOpen: boolean;\n onClose: () => void;\n onSave: (seatingMap: SeatingMap) => void;\n organizationId: string;\n seatingMap?: SeatingMap;\n}\n\nexport default function SeatingMapModal({ \n isOpen, \n onClose, \n onSave, \n organizationId, \n seatingMap \n}: SeatingMapModalProps) {\n const [name, setName] = useState('');\n const [layoutType, setLayoutType] = useState<LayoutType>('theater');\n const [capacity, setCapacity] = useState(100);\n const [layoutItems, setLayoutItems] = useState<LayoutItem[]>([]);\n const [loading, setLoading] = useState(false);\n const [error, setError] = useState<string | null>(null);\n\n useEffect(() => {\n if (seatingMap) {\n setName(seatingMap.name);\n setLayoutItems(seatingMap.layout_data || []);\n } else {\n setName('');\n setLayoutItems([]);\n }\n setError(null);\n }, [seatingMap, isOpen]);\n\n useEffect(() => {\n if (!seatingMap) {\n const initialLayout = generateInitialLayout(layoutType, capacity);\n setLayoutItems(initialLayout);\n }\n }, [layoutType, capacity, seatingMap]);\n\n const handleSubmit = async (e: React.FormEvent) => {\n e.preventDefault();\n setLoading(true);\n setError(null);\n\n try {\n const seatingMapData = {\n name,\n layout_data: layoutItems\n };\n\n if (seatingMap) {\n // Update existing seating map\n const success = await updateSeatingMap(seatingMap.id, seatingMapData);\n if (success) {\n onSave({ ...seatingMap, ...seatingMapData });\n onClose();\n } else {\n setError('Failed to update seating map');\n }\n } else {\n // Create new seating map\n const newSeatingMap = await createSeatingMap(organizationId, seatingMapData);\n if (newSeatingMap) {\n onSave(newSeatingMap);\n onClose();\n } else {\n setError('Failed to create seating map');\n }\n }\n } catch (err) {\n setError(err instanceof Error ? err.message : 'An unexpected error occurred');\n } finally {\n setLoading(false);\n }\n };\n\n const addLayoutItem = (type: LayoutItem['type']) => {\n const newItem: LayoutItem = {\n id: `${type}-${Date.now()}`,\n type,\n x: 50 + (layoutItems.length * 20),\n y: 50 + (layoutItems.length * 20),\n width: type === 'table' ? 80 : type === 'seat_row' ? 200 : 150,\n height: type === 'table' ? 80 : type === 'seat_row' ? 40 : 100,\n label: `${type.replace('_', ' ')} ${layoutItems.length + 1}`,\n capacity: type === 'table' ? 8 : type === 'seat_row' ? 10 : 50\n };\n setLayoutItems(prev => [...prev, newItem]);\n };\n\n const removeLayoutItem = (id: string) => {\n setLayoutItems(prev => prev.filter(item => item.id !== id));\n };\n\n const updateLayoutItem = (id: string, updates: Partial<LayoutItem>) => {\n setLayoutItems(prev => prev.map(item => \n item.id === id ? { ...item, ...updates } : item\n ));\n };\n\n const totalCapacity = layoutItems.reduce((sum, item) => sum + (item.capacity || 0), 0);\n\n if (!isOpen) return null;\n\n return (\n <div className=\"fixed inset-0 bg-black/50 backdrop-blur-sm flex items-center justify-center p-4 z-50\">\n <div className=\"bg-white/10 backdrop-blur-xl border border-white/20 rounded-2xl shadow-2xl w-full max-w-sm sm:max-w-2xl lg:max-w-4xl max-h-[90vh] overflow-y-auto\">\n <div className=\"p-6\">\n <div className=\"flex justify-between items-center mb-6\">\n <h2 className=\"text-2xl font-light text-white\">\n {seatingMap ? 'Edit Seating Map' : 'Create Seating Map'}\n </h2>\n <button\n onClick={onClose}\n className=\"text-white/60 hover:text-white transition-colors p-2 rounded-full hover:bg-white/10 touch-manipulation\"\n aria-label=\"Close modal\"\n >\n <svg className=\"w-5 h-5 sm:w-6 sm:h-6\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M6 18L18 6M6 6l12 12\" />\n </svg>\n </button>\n </div>\n\n {error && (\n <div className=\"mb-4 p-3 bg-red-500/20 border border-red-500/30 rounded-lg text-red-200\">\n {error}\n </div>\n )}\n\n <form onSubmit={handleSubmit} className=\"space-y-6\">\n <div>\n <label htmlFor=\"name\" className=\"block text-sm font-medium text-white/80 mb-2\">\n Map Name *\n </label>\n <input\n type=\"text\"\n id=\"name\"\n required\n value={name}\n onChange={(e) => setName(e.target.value)}\n className=\"w-full px-4 py-3 bg-white/10 border border-white/20 rounded-lg text-white placeholder-white/50 focus:ring-2 focus:ring-blue-500 focus:border-transparent\"\n placeholder=\"e.g., Main Theater Layout\"\n />\n </div>\n\n {!seatingMap && (\n <div className=\"grid grid-cols-2 gap-4\">\n <div>\n <label htmlFor=\"layoutType\" className=\"block text-sm font-medium text-white/80 mb-2\">\n Layout Type\n </label>\n <select\n id=\"layoutType\"\n value={layoutType}\n onChange={(e) => setLayoutType(e.target.value as LayoutType)}\n className=\"w-full px-4 py-3 bg-white/10 border border-white/20 rounded-lg text-white focus:ring-2 focus:ring-blue-500 focus:border-transparent\"\n >\n <option value=\"theater\">Theater (Rows of Seats)</option>\n <option value=\"reception\">Reception (Tables)</option>\n <option value=\"concert_hall\">Concert Hall (Mixed)</option>\n <option value=\"general\">General Admission</option>\n </select>\n </div>\n\n <div>\n <label htmlFor=\"capacity\" className=\"block text-sm font-medium text-white/80 mb-2\">\n Target Capacity\n </label>\n <input\n type=\"number\"\n id=\"capacity\"\n min=\"1\"\n value={capacity}\n onChange={(e) => setCapacity(parseInt(e.target.value) || 100)}\n className=\"w-full px-4 py-3 bg-white/10 border border-white/20 rounded-lg text-white placeholder-white/50 focus:ring-2 focus:ring-blue-500 focus:border-transparent\"\n />\n </div>\n </div>\n )}\n\n <div className=\"border border-white/20 rounded-lg p-4\">\n <div className=\"flex justify-between items-center mb-4\">\n <h3 className=\"text-lg font-medium text-white\">Layout Editor</h3>\n <div className=\"text-sm text-white/60\">\n Total Capacity: {totalCapacity}\n </div>\n </div>\n\n <div className=\"mb-4 flex flex-wrap gap-2\">\n <button\n type=\"button\"\n onClick={() => addLayoutItem('table')}\n className=\"px-3 py-1 bg-blue-600/20 text-blue-300 rounded-lg text-sm hover:bg-blue-600/30 transition-colors\"\n >\n Add Table\n </button>\n <button\n type=\"button\"\n onClick={() => addLayoutItem('seat_row')}\n className=\"px-3 py-1 bg-green-600/20 text-green-300 rounded-lg text-sm hover:bg-green-600/30 transition-colors\"\n >\n Add Seat Row\n </button>\n <button\n type=\"button\"\n onClick={() => addLayoutItem('general_area')}\n className=\"px-3 py-1 bg-purple-600/20 text-purple-300 rounded-lg text-sm hover:bg-purple-600/30 transition-colors\"\n >\n Add General Area\n </button>\n </div>\n\n <div className=\"bg-white/5 border border-white/10 rounded-lg p-4 min-h-[300px] relative\">\n {layoutItems.map((item) => (\n <div\n key={item.id}\n className={`absolute border-2 border-dashed border-white/40 rounded-lg p-2 cursor-move ${\n item.type === 'table' ? 'bg-blue-500/20' :\n item.type === 'seat_row' ? 'bg-green-500/20' :\n 'bg-purple-500/20'\n }`}\n style={{\n left: `${item.x}px`,\n top: `${item.y}px`,\n width: `${item.width}px`,\n height: `${item.height}px`\n }}\n >\n <div className=\"text-xs text-white font-medium\">{item.label}</div>\n <div className=\"text-xs text-white/60\">Cap: {item.capacity}</div>\n <button\n type=\"button\"\n onClick={() => removeLayoutItem(item.id)}\n className=\"absolute top-1 right-1 w-4 h-4 bg-red-500/80 text-white rounded-full text-xs hover:bg-red-500 transition-colors\"\n >\n ×\n </button>\n </div>\n ))}\n {layoutItems.length === 0 && (\n <div className=\"absolute inset-0 flex items-center justify-center text-white/40\">\n Click \"Add\" buttons to start building your layout\n </div>\n )}\n </div>\n </div>\n\n <div className=\"flex justify-end space-x-3 pt-4\">\n <button\n type=\"button\"\n onClick={onClose}\n className=\"px-6 py-3 text-white/80 hover:text-white transition-colors\"\n >\n Cancel\n </button>\n <button\n type=\"submit\"\n disabled={loading}\n className=\"px-6 py-3 bg-gradient-to-r from-blue-600 to-purple-600 hover:from-blue-700 hover:to-purple-700 text-white rounded-lg font-medium transition-all duration-200 disabled:opacity-50\"\n >\n {loading ? 'Saving...' : seatingMap ? 'Update' : 'Create'}\n </button>\n </div>\n </form>\n </div>\n </div>\n </div>\n );\n}","usedDeprecatedRules":[]},{"filePath":"/home/tyler/apps/bct-whitelabel/src/components/modals/TicketPreviewModal.tsx","messages":[{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'organizationId' is defined but never used. Allowed unused args must match /^_/u.","line":27,"column":72,"nodeType":null,"messageId":"unusedVar","endLine":27,"endColumn":86},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":50,"column":14,"nodeType":null,"messageId":"unusedVar","endLine":50,"endColumn":19},{"ruleId":"no-empty","severity":1,"message":"Empty block statement.","line":50,"column":21,"nodeType":"BlockStatement","messageId":"unexpected","endLine":52,"endColumn":6,"suggestions":[{"messageId":"suggestComment","data":{"type":"block"},"fix":{"range":[1274,1280],"text":" /* empty */ "},"desc":"Add comment inside empty block statement."}]},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":92,"column":14,"nodeType":null,"messageId":"unusedVar","endLine":92,"endColumn":19},{"ruleId":"no-empty","severity":1,"message":"Empty block statement.","line":92,"column":21,"nodeType":"BlockStatement","messageId":"unexpected","endLine":94,"endColumn":6,"suggestions":[{"messageId":"suggestComment","data":{"type":"block"},"fix":{"range":[2442,2448],"text":" /* empty */ "},"desc":"Add comment inside empty block statement."}]}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":5,"fixableErrorCount":0,"fixableWarningCount":0,"source":"import { useState, useEffect } from 'react';\nimport { supabase } from '../../lib/supabase';\n\ninterface TicketPreviewModalProps {\n isOpen: boolean;\n onClose: () => void;\n eventId: string;\n organizationId: string;\n}\n\ninterface TicketType {\n id: string;\n name: string;\n price: number;\n}\n\ninterface PreviewData {\n eventTitle: string;\n eventDate: string;\n eventTime: string;\n venue: string;\n ticketType: string;\n price: string;\n barcode: string;\n}\n\nexport default function TicketPreviewModal({ isOpen, onClose, eventId, organizationId }: TicketPreviewModalProps) {\n const [ticketTypes, setTicketTypes] = useState<TicketType[]>([]);\n const [selectedTicketType, setSelectedTicketType] = useState('');\n const [previewData, setPreviewData] = useState<PreviewData | null>(null);\n const [loading, setLoading] = useState(false);\n\n useEffect(() => {\n if (isOpen) {\n loadTicketTypes();\n }\n }, [isOpen, eventId]);\n\n const loadTicketTypes = async () => {\n try {\n const { data, error } = await supabase\n .from('ticket_types')\n .select('id, name, price')\n .eq('event_id', eventId)\n .eq('is_active', true)\n .order('display_order');\n\n if (error) throw error;\n setTicketTypes(data || []);\n } catch (error) {\n\n }\n };\n\n const generateTicketPreview = async (ticketTypeId: string) => {\n if (!ticketTypeId) {\n setPreviewData(null);\n return;\n }\n\n setLoading(true);\n try {\n // Load event details\n const { data: event } = await supabase\n .from('events')\n .select('title, date, venue')\n .eq('id', eventId)\n .single();\n\n const ticketType = ticketTypes.find(t => t.id === ticketTypeId);\n \n if (event && ticketType) {\n const eventDate = new Date(event.date);\n setPreviewData({\n eventTitle: event.title,\n eventDate: eventDate.toLocaleDateString('en-US', { \n weekday: 'long', \n year: 'numeric', \n month: 'long', \n day: 'numeric' \n }),\n eventTime: eventDate.toLocaleTimeString('en-US', {\n hour: 'numeric',\n minute: '2-digit'\n }),\n venue: event.venue,\n ticketType: ticketType.name,\n price: `$${(ticketType.price / 100).toFixed(2)}`,\n barcode: 'SAMPLE-' + Math.random().toString(36).substr(2, 9).toUpperCase()\n });\n }\n } catch (error) {\n\n } finally {\n setLoading(false);\n }\n };\n\n const handlePrint = () => {\n const printContent = document.getElementById('ticket-preview-content');\n if (!printContent) return;\n\n const printWindow = window.open('', '_blank');\n if (!printWindow) return;\n\n printWindow.document.write(`\n <html>\n <head>\n <title>Sample Ticket</title>\n <style>\n body { font-family: Arial, sans-serif; margin: 20px; }\n .ticket-wrapper { max-width: 400px; margin: 0 auto; }\n @media print {\n body { margin: 0; }\n .ticket-wrapper { max-width: none; }\n }\n </style>\n </head>\n <body>\n ${printContent.innerHTML}\n </body>\n </html>\n `);\n printWindow.document.close();\n printWindow.print();\n };\n\n if (!isOpen) return null;\n\n return (\n <div className=\"fixed inset-0 bg-black/50 backdrop-blur-sm z-50\" onClick={onClose}>\n <div className=\"flex items-center justify-center min-h-screen p-4\">\n <div \n className=\"bg-white/10 backdrop-blur-xl border border-white/20 rounded-3xl shadow-2xl w-full max-w-sm sm:max-w-2xl lg:max-w-4xl max-h-[90vh] overflow-y-auto\"\n onClick={(e) => e.stopPropagation()}\n >\n <div className=\"p-4 sm:p-6 lg:p-8\">\n <div className=\"flex justify-between items-center mb-6 sm:mb-8\">\n <div>\n <h2 className=\"text-xl sm:text-2xl lg:text-3xl font-light text-white mb-2\">Sample Ticket Preview</h2>\n <p className=\"text-sm sm:text-base text-white/80\">Preview how your printed tickets will look</p>\n </div>\n <button\n onClick={onClose}\n className=\"text-white/60 hover:text-white transition-colors duration-200 p-2 rounded-full hover:bg-white/10 touch-manipulation\"\n aria-label=\"Close modal\"\n >\n <svg className=\"w-5 h-5 sm:w-6 sm:h-6\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M6 18L18 6M6 6l12 12\" />\n </svg>\n </button>\n </div>\n \n <div className=\"bg-white/5 backdrop-blur-sm rounded-2xl p-4 sm:p-6 mb-6\">\n <div className=\"flex flex-col sm:flex-row gap-4 mb-6\">\n <select\n value={selectedTicketType}\n onChange={(e) => {\n setSelectedTicketType(e.target.value);\n generateTicketPreview(e.target.value);\n }}\n className=\"bg-white/10 border border-white/20 text-white px-4 py-2 rounded-xl focus:outline-none focus:ring-2 focus:ring-blue-500\"\n >\n <option value=\"\">Select ticket type...</option>\n {ticketTypes.map(type => (\n <option key={type.id} value={type.id}>\n {type.name} - ${(type.price / 100).toFixed(2)}\n </option>\n ))}\n </select>\n <button\n onClick={handlePrint}\n disabled={!previewData}\n className=\"bg-gradient-to-r from-blue-600 to-purple-600 hover:from-blue-700 hover:to-purple-700 disabled:from-gray-600 disabled:to-gray-600 text-white px-6 py-2 rounded-xl font-medium transition-all duration-200 flex items-center gap-2\"\n >\n <svg className=\"w-4 h-4\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M17 17h2a2 2 0 002-2v-4a2 2 0 00-2-2H5a2 2 0 00-2 2v4a2 2 0 002 2h2m2 4h6a2 2 0 002-2v-4a2 2 0 00-2-2H9a2 2 0 00-2 2v4a2 2 0 002 2zm8-12V5a2 2 0 00-2-2H9a2 2 0 00-2 2v4h10z\" />\n </svg>\n Print Sample\n </button>\n </div>\n \n <div id=\"ticket-preview-content\" className=\"bg-white rounded-2xl p-8 relative min-h-[400px] flex items-center justify-center\">\n {loading ? (\n <div className=\"text-gray-500 text-center\">\n <div className=\"animate-spin rounded-full h-12 w-12 border-b-2 border-gray-500 mx-auto mb-4\"></div>\n <p className=\"text-lg font-medium\">Generating preview...</p>\n </div>\n ) : previewData ? (\n <div className=\"ticket-wrapper relative\">\n {/* SAMPLE Watermark */}\n <div className=\"absolute inset-0 flex items-center justify-center pointer-events-none z-10\">\n <div className=\"text-red-300 text-8xl font-bold opacity-40 transform rotate-45 select-none\">\n SAMPLE\n </div>\n </div>\n \n {/* Ticket Content */}\n <div className=\"bg-white border-2 border-gray-300 rounded-lg shadow-lg p-6 max-w-md mx-auto relative\">\n <div className=\"text-center mb-4\">\n <h3 className=\"text-2xl font-bold text-gray-900 mb-2\">{previewData.eventTitle}</h3>\n <p className=\"text-gray-700 font-medium\">{previewData.eventDate}</p>\n <p className=\"text-gray-700 font-medium\">{previewData.eventTime}</p>\n </div>\n \n <div className=\"border-t border-b border-gray-300 py-4 mb-4\">\n <p className=\"text-gray-600 text-sm uppercase tracking-wide mb-1\">Venue</p>\n <p className=\"text-gray-900 font-semibold\">{previewData.venue}</p>\n </div>\n \n <div className=\"grid grid-cols-2 gap-4 mb-4\">\n <div>\n <p className=\"text-gray-600 text-sm uppercase tracking-wide mb-1\">Ticket Type</p>\n <p className=\"text-gray-900 font-semibold\">{previewData.ticketType}</p>\n </div>\n <div className=\"text-right\">\n <p className=\"text-gray-600 text-sm uppercase tracking-wide mb-1\">Price</p>\n <p className=\"text-gray-900 font-semibold\">{previewData.price}</p>\n </div>\n </div>\n \n <div className=\"bg-gray-100 rounded-lg p-4 text-center\">\n <div className=\"bg-black text-white font-mono text-sm py-2 px-4 rounded mb-2\">\n {previewData.barcode}\n </div>\n <div className=\"flex justify-center\">\n <svg className=\"h-12\" viewBox=\"0 0 200 50\">\n {[...Array(40)].map((_, i) => (\n <rect\n key={i}\n x={i * 5}\n y=\"0\"\n width={Math.random() > 0.5 ? 3 : 2}\n height=\"50\"\n fill=\"#000\"\n />\n ))}\n </svg>\n </div>\n </div>\n \n <p className=\"text-center text-gray-500 text-xs mt-4\">\n This is a sample ticket for preview purposes only\n </p>\n </div>\n </div>\n ) : (\n <div className=\"text-gray-500 text-center\">\n <svg className=\"w-16 h-16 mx-auto mb-4 opacity-50\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M15 5v2m0 4v2m0 4v2M5 5a2 2 0 00-2 2v3a2 2 0 110 4v3a2 2 0 002 2h14a2 2 0 002-2v-3a2 2 0 110-4V7a2 2 0 00-2-2H5z\" />\n </svg>\n <p className=\"text-lg font-medium mb-2\">Select a ticket type to preview</p>\n <p className=\"text-sm opacity-75\">The preview will show how your ticket will look when printed</p>\n </div>\n )}\n </div>\n </div>\n \n <div className=\"flex justify-end\">\n <button\n onClick={onClose}\n className=\"px-6 py-3 rounded-xl font-medium text-white/80 hover:text-white transition-colors duration-200\"\n >\n Close\n </button>\n </div>\n </div>\n </div>\n </div>\n </div>\n );\n}","usedDeprecatedRules":[]},{"filePath":"/home/tyler/apps/bct-whitelabel/src/components/modals/TicketTypeModal.tsx","messages":[{"ruleId":"no-undef","severity":2,"message":"'React' is not defined.","line":51,"column":34,"nodeType":"Identifier","messageId":"undef","endLine":51,"endColumn":39},{"ruleId":"no-undef","severity":2,"message":"'React' is not defined.","line":83,"column":33,"nodeType":"Identifier","messageId":"undef","endLine":83,"endColumn":38},{"ruleId":"no-undef","severity":2,"message":"'HTMLTextAreaElement' is not defined.","line":83,"column":70,"nodeType":"Identifier","messageId":"undef","endLine":83,"endColumn":89},{"ruleId":"no-undef","severity":2,"message":"'React' is not defined.","line":91,"column":36,"nodeType":"Identifier","messageId":"undef","endLine":91,"endColumn":41}],"suppressedMessages":[],"errorCount":4,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"source":"import { useState, useEffect } from 'react';\nimport type { TicketType, TicketTypeFormData } from '../../lib/ticket-management';\nimport { createTicketType, updateTicketType } from '../../lib/ticket-management';\n\ninterface TicketTypeModalProps {\n isOpen: boolean;\n onClose: () => void;\n onSave: (ticketType: TicketType) => void;\n eventId: string;\n ticketType?: TicketType;\n}\n\nexport default function TicketTypeModal({ \n isOpen, \n onClose, \n onSave, \n eventId, \n ticketType \n}: TicketTypeModalProps) {\n const [formData, setFormData] = useState<TicketTypeFormData>({\n name: '',\n description: '',\n price_cents: 0,\n quantity: 100,\n is_active: true\n });\n const [loading, setLoading] = useState(false);\n const [error, setError] = useState<string | null>(null);\n\n useEffect(() => {\n if (ticketType) {\n setFormData({\n name: ticketType.name,\n description: ticketType.description,\n price_cents: ticketType.price_cents,\n quantity: ticketType.quantity,\n is_active: ticketType.is_active\n });\n } else {\n setFormData({\n name: '',\n description: '',\n price_cents: 0,\n quantity: 100,\n is_active: true\n });\n }\n setError(null);\n }, [ticketType, isOpen]);\n\n const handleSubmit = async (e: React.FormEvent) => {\n e.preventDefault();\n setLoading(true);\n setError(null);\n\n try {\n if (ticketType) {\n // Update existing ticket type\n const success = await updateTicketType(ticketType.id, formData);\n if (success) {\n onSave({ ...ticketType, ...formData });\n onClose();\n } else {\n setError('Failed to update ticket type');\n }\n } else {\n // Create new ticket type\n const newTicketType = await createTicketType(eventId, formData);\n if (newTicketType) {\n onSave(newTicketType);\n onClose();\n } else {\n setError('Failed to create ticket type');\n }\n }\n } catch (err) {\n setError(err instanceof Error ? err.message : 'An unexpected error occurred');\n } finally {\n setLoading(false);\n }\n };\n\n const handleInputChange = (e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {\n const { name, value, type } = e.target;\n setFormData(prev => ({\n ...prev,\n [name]: type === 'number' ? parseInt(value) || 0 : value\n }));\n };\n\n const handleCheckboxChange = (e: React.ChangeEvent<HTMLInputElement>) => {\n const { name, checked } = e.target;\n setFormData(prev => ({\n ...prev,\n [name]: checked\n }));\n };\n\n if (!isOpen) return null;\n\n return (\n <div className=\"fixed inset-0 backdrop-blur-sm flex items-center justify-center p-4 z-50\" style={{ background: 'rgba(0, 0, 0, 0.5)' }}>\n <div className=\"backdrop-blur-xl rounded-2xl shadow-2xl w-full max-w-md sm:max-w-lg max-h-[90vh] overflow-y-auto\" style={{ background: 'var(--glass-bg-lg)', border: '1px solid var(--glass-border)' }}>\n <div className=\"p-6\">\n <div className=\"flex justify-between items-center mb-6\">\n <h2 className=\"text-2xl font-light\" style={{ color: 'var(--glass-text-primary)' }}>\n {ticketType ? 'Edit Ticket Type' : 'Create Ticket Type'}\n </h2>\n <button\n onClick={onClose}\n className=\"transition-colors p-2 rounded-full touch-manipulation hover:opacity-80\"\n style={{ color: 'var(--glass-text-tertiary)', background: 'var(--glass-bg-button)' }}\n aria-label=\"Close modal\"\n >\n <svg className=\"w-5 h-5 sm:w-6 sm:h-6\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M6 18L18 6M6 6l12 12\" />\n </svg>\n </button>\n </div>\n\n {error && (\n <div className=\"mb-4 p-3 rounded-lg\" style={{ background: 'var(--error-bg)', border: '1px solid var(--error-border)', color: 'var(--error-color)' }}>\n {error}\n </div>\n )}\n\n <form onSubmit={handleSubmit} className=\"space-y-6\">\n <div>\n <label htmlFor=\"name\" className=\"block text-sm font-medium mb-2\" style={{ color: 'var(--glass-text-secondary)' }}>\n Ticket Name *\n </label>\n <input\n type=\"text\"\n id=\"name\"\n name=\"name\"\n required\n value={formData.name}\n onChange={handleInputChange}\n className=\"w-full px-4 py-3 bg-white/10 border border-white/20 rounded-lg text-white placeholder-white/50 focus:ring-2 focus:ring-blue-500 focus:border-transparent\"\n placeholder=\"e.g., General Admission\"\n />\n </div>\n\n <div>\n <label htmlFor=\"description\" className=\"block text-sm font-medium text-white/80 mb-2\">\n Description\n </label>\n <textarea\n id=\"description\"\n name=\"description\"\n value={formData.description}\n onChange={handleInputChange}\n rows={3}\n className=\"w-full px-4 py-3 bg-white/10 border border-white/20 rounded-lg text-white placeholder-white/50 focus:ring-2 focus:ring-blue-500 focus:border-transparent resize-none\"\n placeholder=\"Brief description of this ticket type...\"\n />\n </div>\n\n <div className=\"grid grid-cols-2 gap-4\">\n <div>\n <label htmlFor=\"price_cents\" className=\"block text-sm font-medium text-white/80 mb-2\">\n Price ($) *\n </label>\n <input\n type=\"number\"\n id=\"price_cents\"\n name=\"price_cents\"\n required\n min=\"0\"\n step=\"0.01\"\n value={formData.price_cents / 100}\n onChange={(e) => {\n const dollars = parseFloat(e.target.value) || 0;\n setFormData(prev => ({\n ...prev,\n price_cents: Math.round(dollars * 100)\n }));\n }}\n className=\"w-full px-4 py-3 bg-white/10 border border-white/20 rounded-lg text-white placeholder-white/50 focus:ring-2 focus:ring-blue-500 focus:border-transparent\"\n placeholder=\"0.00\"\n />\n </div>\n\n <div>\n <label htmlFor=\"quantity\" className=\"block text-sm font-medium text-white/80 mb-2\">\n Quantity *\n </label>\n <input\n type=\"number\"\n id=\"quantity\"\n name=\"quantity\"\n required\n min=\"1\"\n value={formData.quantity}\n onChange={handleInputChange}\n className=\"w-full px-4 py-3 bg-white/10 border border-white/20 rounded-lg text-white placeholder-white/50 focus:ring-2 focus:ring-blue-500 focus:border-transparent\"\n placeholder=\"100\"\n />\n </div>\n </div>\n\n <div className=\"flex items-center\">\n <input\n type=\"checkbox\"\n id=\"is_active\"\n name=\"is_active\"\n checked={formData.is_active}\n onChange={handleCheckboxChange}\n className=\"w-4 h-4 text-blue-600 bg-white/10 border-white/20 rounded focus:ring-blue-500 focus:ring-2\"\n />\n <label htmlFor=\"is_active\" className=\"ml-2 text-sm text-white/80\">\n Active (available for purchase)\n </label>\n </div>\n\n <div className=\"flex justify-end space-x-3 pt-4\">\n <button\n type=\"button\"\n onClick={onClose}\n className=\"px-6 py-3 text-white/80 hover:text-white transition-colors\"\n >\n Cancel\n </button>\n <button\n type=\"submit\"\n disabled={loading}\n className=\"px-6 py-3 bg-gradient-to-r from-blue-600 to-purple-600 hover:from-blue-700 hover:to-purple-700 text-white rounded-lg font-medium transition-all duration-200 disabled:opacity-50\"\n >\n {loading ? 'Saving...' : ticketType ? 'Update' : 'Create'}\n </button>\n </div>\n </form>\n </div>\n </div>\n </div>\n );\n}","usedDeprecatedRules":[]},{"filePath":"/home/tyler/apps/bct-whitelabel/src/components/tables/AttendeesTable.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/home/tyler/apps/bct-whitelabel/src/components/tables/OrdersTable.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/home/tyler/apps/bct-whitelabel/src/lib/accessibility.ts","messages":[{"ruleId":"no-undef","severity":2,"message":"'NodeListOf' is not defined.","line":32,"column":30,"nodeType":"Identifier","messageId":"undef","endLine":32,"endColumn":40},{"ruleId":"no-undef","severity":2,"message":"'HTMLAnchorElement' is not defined.","line":95,"column":62,"nodeType":"Identifier","messageId":"undef","endLine":95,"endColumn":79}],"suppressedMessages":[],"errorCount":2,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"source":"// Accessibility utilities and helpers\n\n/**\n * Generate unique IDs for form elements and ARIA relationships\n */\nexport function generateUniqueId(prefix: string = 'element'): string {\n return `${prefix}-${Math.random().toString(36).substr(2, 9)}`;\n}\n\n/**\n * Announce messages to screen readers\n */\nexport function announceToScreenReader(message: string, priority: 'polite' | 'assertive' = 'polite') {\n const announcement = document.createElement('div');\n announcement.setAttribute('aria-live', priority);\n announcement.setAttribute('aria-atomic', 'true');\n announcement.className = 'sr-only';\n announcement.textContent = message;\n \n document.body.appendChild(announcement);\n \n // Remove after announcement\n setTimeout(() => {\n document.body.removeChild(announcement);\n }, 1000);\n}\n\n/**\n * Manage focus for modal dialogs\n */\nexport class FocusManager {\n private focusableElements: NodeListOf<HTMLElement> | null = null;\n private firstFocusableElement: HTMLElement | null = null;\n private lastFocusableElement: HTMLElement | null = null;\n private previouslyFocusedElement: HTMLElement | null = null;\n\n /**\n * Initialize focus management for a container\n */\n public init(container: HTMLElement) {\n this.previouslyFocusedElement = document.activeElement as HTMLElement;\n this.focusableElements = container.querySelectorAll(\n 'a[href], button, textarea, input[type=\"text\"], input[type=\"radio\"], input[type=\"checkbox\"], select, [tabindex]:not([tabindex=\"-1\"])'\n );\n \n if (this.focusableElements.length > 0) {\n this.firstFocusableElement = this.focusableElements[0];\n this.lastFocusableElement = this.focusableElements[this.focusableElements.length - 1];\n \n // Focus first element\n this.firstFocusableElement.focus();\n }\n }\n\n /**\n * Handle keyboard navigation within the container\n */\n public handleKeyDown(event: KeyboardEvent) {\n if (event.key !== 'Tab') return;\n\n if (event.shiftKey) {\n // Shift + Tab\n if (document.activeElement === this.firstFocusableElement) {\n event.preventDefault();\n this.lastFocusableElement?.focus();\n }\n } else {\n // Tab\n if (document.activeElement === this.lastFocusableElement) {\n event.preventDefault();\n this.firstFocusableElement?.focus();\n }\n }\n }\n\n /**\n * Restore focus to previously focused element\n */\n public restoreFocus() {\n if (this.previouslyFocusedElement) {\n this.previouslyFocusedElement.focus();\n }\n }\n}\n\n/**\n * Skip link functionality\n */\nexport function initializeSkipLinks() {\n const skipLinks = document.querySelectorAll('.skip-link');\n \n skipLinks.forEach(link => {\n link.addEventListener('click', (event) => {\n event.preventDefault();\n const target = document.querySelector((event.target as HTMLAnchorElement).getAttribute('href')!);\n if (target) {\n (target as HTMLElement).focus();\n target.scrollIntoView();\n }\n });\n });\n}\n\n/**\n * Enhance form accessibility\n */\nexport function enhanceFormAccessibility() {\n const forms = document.querySelectorAll('form');\n \n forms.forEach(form => {\n // Add ARIA labels to form controls without labels\n const inputs = form.querySelectorAll('input, select, textarea');\n inputs.forEach(input => {\n if (!input.getAttribute('aria-label') && !input.getAttribute('aria-labelledby')) {\n const label = form.querySelector(`label[for=\"${input.id}\"]`);\n if (!label && input.getAttribute('placeholder')) {\n input.setAttribute('aria-label', input.getAttribute('placeholder')!);\n }\n }\n });\n\n // Add error message associations\n const errorMessages = form.querySelectorAll('[data-error-for]');\n errorMessages.forEach(error => {\n const inputId = error.getAttribute('data-error-for');\n const input = form.querySelector(`#${inputId}`);\n if (input) {\n const errorId = generateUniqueId('error');\n error.id = errorId;\n input.setAttribute('aria-describedby', errorId);\n input.setAttribute('aria-invalid', 'true');\n }\n });\n });\n}\n\n/**\n * Add keyboard navigation to custom components\n */\nexport function addKeyboardNavigation() {\n // Custom dropdown navigation\n const dropdowns = document.querySelectorAll('[role=\"combobox\"]');\n dropdowns.forEach(dropdown => {\n dropdown.addEventListener('keydown', (event) => {\n const key = event.key;\n if (key === 'ArrowDown' || key === 'ArrowUp') {\n event.preventDefault();\n // Handle dropdown navigation\n } else if (key === 'Escape') {\n // Close dropdown\n dropdown.blur();\n }\n });\n });\n\n // Tab navigation for card grids\n const cardGrids = document.querySelectorAll('[data-card-grid]');\n cardGrids.forEach(grid => {\n const cards = grid.querySelectorAll('[data-card]');\n cards.forEach((card, index) => {\n card.addEventListener('keydown', (event) => {\n const key = event.key;\n let nextIndex = index;\n \n if (key === 'ArrowRight' || key === 'ArrowDown') {\n nextIndex = Math.min(index + 1, cards.length - 1);\n } else if (key === 'ArrowLeft' || key === 'ArrowUp') {\n nextIndex = Math.max(index - 1, 0);\n } else if (key === 'Home') {\n nextIndex = 0;\n } else if (key === 'End') {\n nextIndex = cards.length - 1;\n }\n \n if (nextIndex !== index) {\n event.preventDefault();\n (cards[nextIndex] as HTMLElement).focus();\n }\n });\n });\n });\n}\n\n/**\n * Improve color contrast for dynamic content\n */\nexport function validateColorContrast() {\n // This would typically integrate with a color contrast checking library\n\n}\n\n/**\n * Initialize all accessibility enhancements\n */\nexport function initializeAccessibility() {\n // Wait for DOM to be ready\n if (document.readyState === 'loading') {\n document.addEventListener('DOMContentLoaded', () => {\n initializeSkipLinks();\n enhanceFormAccessibility();\n addKeyboardNavigation();\n validateColorContrast();\n });\n } else {\n initializeSkipLinks();\n enhanceFormAccessibility();\n addKeyboardNavigation();\n validateColorContrast();\n }\n}\n\n/**\n * Screen reader utility class\n */\nexport class ScreenReaderSupport {\n private static liveRegion: HTMLElement | null = null;\n\n public static announce(message: string, priority: 'off' | 'polite' | 'assertive' = 'polite') {\n if (!this.liveRegion) {\n this.createLiveRegion();\n }\n \n if (this.liveRegion) {\n this.liveRegion.setAttribute('aria-live', priority);\n this.liveRegion.textContent = message;\n \n // Clear after announcement\n setTimeout(() => {\n if (this.liveRegion) {\n this.liveRegion.textContent = '';\n }\n }, 1000);\n }\n }\n\n private static createLiveRegion() {\n this.liveRegion = document.createElement('div');\n this.liveRegion.className = 'sr-only';\n this.liveRegion.setAttribute('aria-live', 'polite');\n this.liveRegion.setAttribute('aria-atomic', 'true');\n document.body.appendChild(this.liveRegion);\n }\n}\n\n/**\n * High contrast mode detection and support\n */\nexport function initializeHighContrastSupport() {\n // Detect if user prefers high contrast\n const prefersHighContrast = window.matchMedia('(prefers-contrast: high)');\n \n function applyHighContrast(matches: boolean) {\n if (matches) {\n document.documentElement.classList.add('high-contrast');\n } else {\n document.documentElement.classList.remove('high-contrast');\n }\n }\n \n applyHighContrast(prefersHighContrast.matches);\n prefersHighContrast.addEventListener('change', (e) => applyHighContrast(e.matches));\n}\n\n/**\n * Reduced motion support\n */\nexport function initializeReducedMotionSupport() {\n const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)');\n \n function applyReducedMotion(matches: boolean) {\n if (matches) {\n document.documentElement.classList.add('reduce-motion');\n } else {\n document.documentElement.classList.remove('reduce-motion');\n }\n }\n \n applyReducedMotion(prefersReducedMotion.matches);\n prefersReducedMotion.addEventListener('change', (e) => applyReducedMotion(e.matches));\n}","usedDeprecatedRules":[]},{"filePath":"/home/tyler/apps/bct-whitelabel/src/lib/addons.ts","messages":[{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":27,"column":29,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":27,"endColumn":32,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[751,754],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[751,754],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":49,"column":28,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":49,"endColumn":31,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[1273,1276],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[1273,1276],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":64,"column":12,"nodeType":null,"messageId":"unusedVar","endLine":64,"endColumn":17},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":86,"column":12,"nodeType":null,"messageId":"unusedVar","endLine":86,"endColumn":17},{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":98,"column":29,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":98,"endColumn":32,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[2406,2409],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[2406,2409],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":145,"column":12,"nodeType":null,"messageId":"unusedVar","endLine":145,"endColumn":17},{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":228,"column":26,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":228,"endColumn":29,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[5784,5787],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[5784,5787],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":235,"column":12,"nodeType":null,"messageId":"unusedVar","endLine":235,"endColumn":17}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":8,"fixableErrorCount":0,"fixableWarningCount":0,"source":"// Add-ons management utilities for Black Canyon Tickets\nimport { supabase } from './supabase';\n\nexport interface AddOnType {\n id: string;\n slug: string;\n name: string;\n description: string;\n pricing_type: 'per_event' | 'monthly' | 'annual' | 'per_ticket';\n price_cents: number;\n category: 'feature' | 'service' | 'analytics' | 'marketing' | 'subscription';\n is_active: boolean;\n requires_setup: boolean;\n feature_flags: Record<string, boolean>;\n sort_order: number;\n}\n\nexport interface EventAddOn {\n id: string;\n event_id: string;\n add_on_type_id: string;\n organization_id: string;\n purchase_price_cents: number;\n status: 'active' | 'cancelled' | 'expired';\n purchased_at: string;\n expires_at?: string;\n metadata?: Record<string, any>;\n}\n\nexport interface AddOnWithAccess extends AddOnType {\n has_access: boolean;\n purchased_at?: string;\n}\n\n// Get all available add-ons for an organization/event\nexport async function getAvailableAddOns(\n organizationId: string, \n eventId?: string\n): Promise<AddOnWithAccess[]> {\n try {\n const { data, error } = await supabase\n .rpc('get_available_addons', {\n p_organization_id: organizationId,\n p_event_id: eventId || null\n });\n\n if (error) throw error;\n\n return data.map((item: any) => ({\n id: item.addon_id,\n slug: item.slug,\n name: item.name,\n description: item.description,\n pricing_type: item.pricing_type,\n price_cents: item.price_cents,\n category: item.category,\n is_active: true,\n requires_setup: false,\n feature_flags: {},\n sort_order: 0,\n has_access: item.has_access,\n purchased_at: item.purchased_at\n }));\n } catch (error) {\n\n return [];\n }\n}\n\n// Check if user has access to specific feature\nexport async function hasFeatureAccess(\n organizationId: string,\n eventId: string | null,\n featureFlag: string\n): Promise<boolean> {\n try {\n const { data, error } = await supabase\n .rpc('has_feature_access', {\n p_organization_id: organizationId,\n p_event_id: eventId,\n p_feature_flag: featureFlag\n });\n\n if (error) throw error;\n return data === true;\n } catch (error) {\n\n return false;\n }\n}\n\n// Purchase an add-on for an event\nexport async function purchaseEventAddOn(\n eventId: string,\n addOnTypeId: string,\n organizationId: string,\n priceCents: number,\n metadata?: Record<string, any>\n): Promise<{ success: boolean; addOnId?: string; error?: string }> {\n try {\n const { data, error } = await supabase\n .from('event_add_ons')\n .insert([{\n event_id: eventId,\n add_on_type_id: addOnTypeId,\n organization_id: organizationId,\n purchase_price_cents: priceCents,\n status: 'active',\n metadata: metadata || {}\n }])\n .select()\n .single();\n\n if (error) throw error;\n\n return { success: true, addOnId: data.id };\n } catch (error) {\n\n return { \n success: false, \n error: error instanceof Error ? error.message : 'Unknown error' \n };\n }\n}\n\n// Get event add-ons for a specific event\nexport async function getEventAddOns(eventId: string): Promise<EventAddOn[]> {\n try {\n const { data, error } = await supabase\n .from('event_add_ons')\n .select(`\n *,\n add_on_types (\n slug,\n name,\n description,\n feature_flags\n )\n `)\n .eq('event_id', eventId)\n .eq('status', 'active');\n\n if (error) throw error;\n return data || [];\n } catch (error) {\n\n return [];\n }\n}\n\n// Format price for display\nexport function formatAddOnPrice(priceCents: number, pricingType: string): string {\n const price = priceCents / 100;\n const formattedPrice = new Intl.NumberFormat('en-US', {\n style: 'currency',\n currency: 'USD'\n }).format(price);\n\n switch (pricingType) {\n case 'per_event':\n return `${formattedPrice} per event`;\n case 'monthly':\n return `${formattedPrice}/month`;\n case 'annual':\n return `${formattedPrice}/year`;\n case 'per_ticket':\n return `${formattedPrice} per ticket`;\n default:\n return formattedPrice;\n }\n}\n\n// Get add-on category icon\nexport function getAddOnCategoryIcon(category: string): string {\n const icons = {\n feature: '⚡',\n service: '🎯',\n analytics: '📊',\n marketing: '📢',\n subscription: '⭐'\n };\n return icons[category as keyof typeof icons] || '🔧';\n}\n\n// Get add-on category color\nexport function getAddOnCategoryColor(category: string): string {\n const colors = {\n feature: 'blue',\n service: 'green',\n analytics: 'purple',\n marketing: 'orange',\n subscription: 'indigo'\n };\n return colors[category as keyof typeof colors] || 'gray';\n}\n\n// Calculate total add-on revenue for organization\nexport async function calculateAddOnRevenue(organizationId: string): Promise<{\n totalRevenue: number;\n eventAddOns: number;\n subscriptionRevenue: number;\n}> {\n try {\n // Event add-ons revenue\n const { data: eventAddOns, error: eventError } = await supabase\n .from('event_add_ons')\n .select('purchase_price_cents')\n .eq('organization_id', organizationId)\n .eq('status', 'active');\n\n if (eventError) throw eventError;\n\n const eventRevenue = (eventAddOns || [])\n .reduce((sum, addon) => sum + addon.purchase_price_cents, 0);\n\n // Subscription revenue (simplified - would need proper subscription tracking)\n const { data: subscriptions, error: subError } = await supabase\n .from('organization_subscriptions')\n .select(`\n add_on_types (price_cents)\n `)\n .eq('organization_id', organizationId)\n .eq('status', 'active');\n\n if (subError) throw subError;\n\n const subscriptionRevenue = (subscriptions || [])\n .reduce((sum, sub: any) => sum + (sub.add_on_types?.price_cents || 0), 0);\n\n return {\n totalRevenue: eventRevenue + subscriptionRevenue,\n eventAddOns: eventRevenue,\n subscriptionRevenue\n };\n } catch (error) {\n\n return {\n totalRevenue: 0,\n eventAddOns: 0,\n subscriptionRevenue: 0\n };\n }\n}\n\n// Common feature flags\nexport const FEATURE_FLAGS = {\n SEATING_MAPS: 'seating_maps',\n AI_DESCRIPTION: 'ai_description',\n ADVANCED_ANALYTICS: 'advanced_analytics',\n EMAIL_MARKETING: 'email_marketing',\n PRIORITY_SUPPORT: 'priority_support',\n CUSTOM_BRANDING: 'custom_branding',\n SOCIAL_MEDIA_TOOLS: 'social_media_tools',\n ADVANCED_GUEST_MANAGEMENT: 'advanced_guest_management',\n TICKET_SCANNER: 'ticket_scanner',\n ALL_FEATURES: 'all_features'\n} as const;\n\n// Popular add-on bundles for upselling\nexport const POPULAR_BUNDLES = [\n {\n name: 'Starter Bundle',\n description: 'Perfect for your first premium event',\n addons: ['ai-event-description', 'ticket-scanner'],\n originalPrice: 1000, // $10\n bundlePrice: 800, // $8 (20% discount)\n savings: 200\n },\n {\n name: 'Professional Bundle',\n description: 'Everything you need for a successful event',\n addons: ['seating-maps', 'premium-analytics', 'ticket-scanner', 'guest-list-pro'],\n originalPrice: 4000, // $40\n bundlePrice: 3000, // $30 (25% discount)\n savings: 1000\n },\n {\n name: 'Complete Bundle',\n description: 'All automated features for maximum impact',\n addons: ['seating-maps', 'premium-analytics', 'ticket-scanner', 'guest-list-pro', 'ai-event-description', 'custom-event-branding'],\n originalPrice: 6000, // $60\n bundlePrice: 4500, // $45 (25% discount)\n savings: 1500\n }\n] as const;","usedDeprecatedRules":[]},{"filePath":"/home/tyler/apps/bct-whitelabel/src/lib/admin-api-router.ts","messages":[{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":8,"column":20,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":8,"endColumn":23,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[234,237],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[234,237],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":45,"column":14,"nodeType":null,"messageId":"unusedVar","endLine":45,"endColumn":19},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":94,"column":14,"nodeType":null,"messageId":"unusedVar","endLine":94,"endColumn":19},{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":103,"column":38,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":103,"endColumn":41,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[2732,2735],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[2732,2735],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":162,"column":14,"nodeType":null,"messageId":"unusedVar","endLine":162,"endColumn":19},{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":171,"column":49,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":171,"endColumn":52,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[4843,4846],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[4843,4846],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":192,"column":14,"nodeType":null,"messageId":"unusedVar","endLine":192,"endColumn":19},{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":201,"column":37,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":201,"endColumn":40,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[5381,5384],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[5381,5384],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":232,"column":14,"nodeType":null,"messageId":"unusedVar","endLine":232,"endColumn":19},{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":241,"column":29,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":241,"endColumn":32,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[6226,6229],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[6226,6229],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":264,"column":14,"nodeType":null,"messageId":"unusedVar","endLine":264,"endColumn":19},{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":273,"column":30,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":273,"endColumn":33,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[6810,6813],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[6810,6813],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":309,"column":14,"nodeType":null,"messageId":"unusedVar","endLine":309,"endColumn":19},{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":318,"column":31,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":318,"endColumn":34,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[7806,7809],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[7806,7809],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":353,"column":14,"nodeType":null,"messageId":"unusedVar","endLine":353,"endColumn":19},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":412,"column":14,"nodeType":null,"messageId":"unusedVar","endLine":412,"endColumn":19},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":500,"column":14,"nodeType":null,"messageId":"unusedVar","endLine":500,"endColumn":19},{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":509,"column":74,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":509,"endColumn":77,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[13335,13338],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[13335,13338],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"no-undef","severity":2,"message":"'AbortController' is not defined.","line":529,"column":30,"nodeType":"Identifier","messageId":"undef","endLine":529,"endColumn":45},{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":545,"column":21,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":545,"endColumn":24,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[14593,14596],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[14593,14596],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]}],"suppressedMessages":[],"errorCount":1,"fatalErrorCount":0,"warningCount":19,"fixableErrorCount":0,"fixableWarningCount":0,"source":"import { supabase } from './supabase';\n\n/**\n * Admin API Router for centralized admin dashboard API calls\n * This provides a centralized way to handle admin-specific API operations\n */\nexport class AdminApiRouter {\n private session: any = null;\n private isAdmin = false;\n\n /**\n * Initialize the admin API router with authentication\n */\n async initialize(): Promise<boolean> {\n try {\n const { data: { session }, error: sessionError } = await supabase.auth.getSession();\n \n if (sessionError) {\n\n return false;\n }\n \n if (!session) {\n\n return false;\n }\n\n this.session = session;\n\n // Check if user is admin\n const { data: userRecord, error: userError } = await supabase\n .from('users')\n .select('role, name, email')\n .eq('id', session.user.id)\n .single();\n\n if (userError || !userRecord || userRecord.role !== 'admin') {\n\n return false;\n }\n\n this.isAdmin = true;\n\n return true;\n } catch (error) {\n\n return false;\n }\n }\n\n /**\n * Get platform statistics for admin dashboard\n */\n async getPlatformStats(): Promise<{\n organizations: number;\n events: number;\n tickets: number;\n revenue: number;\n platformFees: number;\n users: number;\n error?: string;\n }> {\n try {\n if (!this.isAdmin) {\n const initialized = await this.initialize();\n if (!initialized) {\n return { organizations: 0, events: 0, tickets: 0, revenue: 0, platformFees: 0, users: 0, error: 'Admin authentication required' };\n }\n }\n\n const [organizationsResult, eventsResult, ticketsResult, usersResult] = await Promise.all([\n supabase.from('organizations').select('id'),\n supabase.from('events').select('id'),\n supabase.from('tickets').select('price'),\n supabase.from('users').select('id')\n ]);\n\n const organizations = organizationsResult.data?.length || 0;\n const events = eventsResult.data?.length || 0;\n const users = usersResult.data?.length || 0;\n const tickets = ticketsResult.data || [];\n const ticketCount = tickets.length;\n const revenue = tickets.reduce((sum, ticket) => sum + (ticket.price || 0), 0);\n const platformFees = revenue * 0.05; // Assuming 5% platform fee\n\n return {\n organizations,\n events,\n tickets: ticketCount,\n revenue,\n platformFees,\n users\n };\n } catch (error) {\n\n return { organizations: 0, events: 0, tickets: 0, revenue: 0, platformFees: 0, users: 0, error: 'Failed to load platform statistics' };\n }\n }\n\n /**\n * Get recent activity for admin dashboard\n */\n async getRecentActivity(): Promise<any[]> {\n try {\n if (!this.isAdmin) {\n const initialized = await this.initialize();\n if (!initialized) {\n return [];\n }\n }\n\n const [eventsResult, usersResult, ticketsResult] = await Promise.all([\n supabase.from('events').select('*, organizations(name)').order('created_at', { ascending: false }).limit(5),\n supabase.from('users').select('*, organizations(name)').order('created_at', { ascending: false }).limit(5),\n supabase.from('tickets').select('*, events(title)').order('created_at', { ascending: false }).limit(10)\n ]);\n\n const activities = [];\n\n // Add recent events\n if (eventsResult.data) {\n eventsResult.data.forEach(event => {\n activities.push({\n type: 'event',\n title: `New event created: ${event.title}`,\n subtitle: `by ${event.organizations?.name || 'Unknown'}`,\n time: new Date(event.created_at),\n icon: '📅'\n });\n });\n }\n\n // Add recent users\n if (usersResult.data) {\n usersResult.data.forEach(user => {\n activities.push({\n type: 'user',\n title: `New user registered: ${user.name || user.email}`,\n subtitle: `Organization: ${user.organizations?.name || 'None'}`,\n time: new Date(user.created_at),\n icon: '👤'\n });\n });\n }\n\n // Add recent tickets\n if (ticketsResult.data) {\n ticketsResult.data.slice(0, 5).forEach(ticket => {\n activities.push({\n type: 'ticket',\n title: `Ticket sold: $${ticket.price}`,\n subtitle: `for ${ticket.events?.title || 'Unknown Event'}`,\n time: new Date(ticket.created_at),\n icon: '🎫'\n });\n });\n }\n\n // Sort by time and take the most recent 10\n activities.sort((a, b) => b.time - a.time);\n return activities.slice(0, 10);\n } catch (error) {\n\n return [];\n }\n }\n\n /**\n * Get a single organization by ID\n */\n async getOrganization(orgId: string): Promise<any | null> {\n try {\n if (!this.isAdmin) {\n const initialized = await this.initialize();\n if (!initialized) {\n return null;\n }\n }\n\n const { data: org, error } = await supabase\n .from('organizations')\n .select('*')\n .eq('id', orgId)\n .single();\n\n if (error) {\n\n return null;\n }\n\n return org;\n } catch (error) {\n\n return null;\n }\n }\n\n /**\n * Get organizations with additional metadata\n */\n async getOrganizations(): Promise<any[]> {\n try {\n if (!this.isAdmin) {\n const initialized = await this.initialize();\n if (!initialized) {\n return [];\n }\n }\n\n const { data: orgs, error } = await supabase\n .from('organizations')\n .select('*')\n .order('created_at', { ascending: false });\n\n if (error) {\n\n return [];\n }\n\n // Get user counts for each organization\n if (orgs) {\n for (const org of orgs) {\n const { data: users } = await supabase\n .from('users')\n .select('id')\n .eq('organization_id', org.id);\n org.user_count = users ? users.length : 0;\n }\n }\n\n return orgs || [];\n } catch (error) {\n\n return [];\n }\n }\n\n /**\n * Get users with organization details\n */\n async getUsers(): Promise<any[]> {\n try {\n if (!this.isAdmin) {\n const initialized = await this.initialize();\n if (!initialized) {\n return [];\n }\n }\n\n const { data: users, error } = await supabase\n .from('users')\n .select(`\n *,\n organizations(name)\n `)\n .order('created_at', { ascending: false });\n\n if (error) {\n\n return [];\n }\n\n return users || [];\n } catch (error) {\n\n return [];\n }\n }\n\n /**\n * Get events with organization and user details\n */\n async getEvents(): Promise<any[]> {\n try {\n if (!this.isAdmin) {\n const initialized = await this.initialize();\n if (!initialized) {\n return [];\n }\n }\n\n const { data: events, error } = await supabase\n .from('events')\n .select(`\n *,\n organizations(name),\n users(name, email),\n venues(name)\n `)\n .order('created_at', { ascending: false });\n\n if (error) {\n\n return [];\n }\n\n // Get ticket type counts for each event\n if (events) {\n for (const event of events) {\n const { data: ticketTypes } = await supabase\n .from('ticket_types')\n .select('id')\n .eq('event_id', event.id);\n event.ticket_type_count = ticketTypes ? ticketTypes.length : 0;\n }\n }\n\n return events || [];\n } catch (error) {\n\n return [];\n }\n }\n\n /**\n * Get tickets with event and organization details\n */\n async getTickets(): Promise<any[]> {\n try {\n if (!this.isAdmin) {\n const initialized = await this.initialize();\n if (!initialized) {\n return [];\n }\n }\n\n const { data: tickets, error } = await supabase\n .from('tickets')\n .select(`\n *,\n ticket_types (\n name,\n price\n ),\n events (\n title,\n venue,\n start_time,\n organizations (\n name\n )\n )\n `)\n .order('created_at', { ascending: false })\n .limit(100);\n\n if (error) {\n\n return [];\n }\n\n return tickets || [];\n } catch (error) {\n\n return [];\n }\n }\n\n /**\n * Update organization platform fees\n */\n async updateOrganizationFees(orgId: string, feeData: {\n platform_fee_type: string;\n platform_fee_percentage?: number;\n platform_fee_fixed?: number;\n platform_fee_model: string;\n platform_fee_notes?: string;\n }): Promise<{ success: boolean; error?: string }> {\n try {\n if (!this.isAdmin) {\n const initialized = await this.initialize();\n if (!initialized) {\n return { success: false, error: 'Admin authentication required' };\n }\n }\n\n const updateData = {\n platform_fee_type: feeData.platform_fee_type,\n platform_fee_model: feeData.platform_fee_model,\n absorb_fee_in_price: feeData.platform_fee_model === 'absorbed_in_price',\n platform_fee_notes: feeData.platform_fee_notes || null\n };\n\n // Only set fields that should be used based on fee type\n switch (feeData.platform_fee_type) {\n case 'percentage_only':\n updateData.platform_fee_percentage = feeData.platform_fee_percentage || 0;\n updateData.platform_fee_fixed = 0;\n break;\n case 'fixed_only':\n updateData.platform_fee_percentage = 0;\n updateData.platform_fee_fixed = feeData.platform_fee_fixed || 0;\n break;\n case 'percentage_plus_fixed':\n default:\n updateData.platform_fee_percentage = feeData.platform_fee_percentage || 0;\n updateData.platform_fee_fixed = feeData.platform_fee_fixed || 0;\n break;\n }\n\n const { error } = await supabase\n .from('organizations')\n .update(updateData)\n .eq('id', orgId);\n\n if (error) {\n\n return { success: false, error: error.message };\n }\n\n return { success: true };\n } catch (error) {\n\n return { success: false, error: 'Failed to update organization fees' };\n }\n }\n\n /**\n * Create a test event for demonstration\n */\n async createTestEvent(): Promise<{ success: boolean; error?: string; eventId?: string }> {\n try {\n if (!this.isAdmin) {\n const initialized = await this.initialize();\n if (!initialized) {\n return { success: false, error: 'Admin authentication required' };\n }\n }\n\n // Create a test organization first if none exists\n let testOrgId;\n const { data: orgs } = await supabase.from('organizations').select('id').limit(1);\n \n if (!orgs || orgs.length === 0) {\n const { data: newOrg, error: orgError } = await supabase\n .from('organizations')\n .insert({\n name: 'Demo Organization',\n website: 'https://demo.blackcanyontickets.com',\n description: 'Test organization for platform demonstration'\n })\n .select()\n .single();\n \n if (orgError) {\n\n return { success: false, error: orgError.message };\n }\n testOrgId = newOrg.id;\n } else {\n testOrgId = orgs[0].id;\n }\n\n // Create test event\n const { data: event, error } = await supabase\n .from('events')\n .insert({\n title: 'Demo Concert - ' + new Date().toLocaleDateString(),\n slug: 'demo-concert-' + Date.now(),\n description: 'A demonstration event showcasing the Black Canyon Tickets platform features.',\n venue: 'Demo Venue',\n start_time: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000).toISOString(), // 30 days from now\n end_time: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000 + 3 * 60 * 60 * 1000).toISOString(), // +3 hours\n organization_id: testOrgId,\n is_published: true\n })\n .select()\n .single();\n\n if (error) {\n\n return { success: false, error: error.message };\n }\n\n // Create sample ticket types\n const ticketTypesResult = await supabase.from('ticket_types').insert([\n {\n event_id: event.id,\n name: 'General Admission',\n price: 25.00,\n quantity_available: 100,\n description: 'Standard entry ticket'\n },\n {\n event_id: event.id,\n name: 'VIP',\n price: 75.00,\n quantity_available: 20,\n description: 'VIP experience with premium benefits'\n }\n ]);\n\n if (ticketTypesResult.error) {\n\n // Event created successfully but ticket types failed\n return { success: true, eventId: event.id, error: 'Event created but ticket types failed' };\n }\n\n return { success: true, eventId: event.id };\n } catch (error) {\n\n return { success: false, error: 'Failed to create test event' };\n }\n }\n\n /**\n * Call the super analytics API with proper authentication\n */\n async getSuperAnalytics(metric: string = 'platform_overview'): Promise<any> {\n try {\n if (!this.session) {\n const initialized = await this.initialize();\n if (!initialized) {\n return { success: false, error: 'Authentication required' };\n }\n }\n\n // Construct full URL to handle localhost issues\n let baseUrl = window.location.origin;\n \n // Special handling for localhost to ensure proper resolution\n if (window.location.hostname === 'localhost' && window.location.port) {\n baseUrl = `${window.location.protocol}//localhost:${window.location.port}`;\n }\n \n const apiUrl = `${baseUrl}/api/admin/super-analytics?metric=${metric}`;\n\n // Add timeout to prevent hanging\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), 10000); // 10 second timeout\n \n const response = await fetch(apiUrl, {\n headers: {\n 'Authorization': `Bearer ${this.session.access_token}`,\n 'Content-Type': 'application/json'\n },\n signal: controller.signal\n }).finally(() => clearTimeout(timeoutId));\n\n if (!response.ok) {\n throw new Error(`HTTP error! status: ${response.status}`);\n }\n\n return await response.json();\n } catch (error: any) {\n\n if (error.name === 'AbortError') {\n return { success: false, error: 'Request timeout - the server took too long to respond' };\n }\n \n return { \n success: false, \n error: error.message || 'Failed to load analytics data',\n details: {\n errorType: error.name,\n metric: metric,\n url: window.location.href\n }\n };\n }\n }\n\n /**\n * Get user information\n */\n getUserInfo(): { name: string; email: string } | null {\n if (!this.session) return null;\n \n return {\n name: this.session.user.user_metadata?.name || this.session.user.email,\n email: this.session.user.email\n };\n }\n\n /**\n * Check if user is authenticated as admin\n */\n isAuthenticated(): boolean {\n return this.isAdmin && this.session !== null;\n }\n\n /**\n * Get Supabase client for direct access\n */\n getSupabaseClient() {\n return supabase;\n }\n\n /**\n * Get current session\n */\n getSession() {\n return this.session;\n }\n\n /**\n * Sign out\n */\n async signOut(): Promise<void> {\n await supabase.auth.signOut();\n this.session = null;\n this.isAdmin = false;\n }\n}\n\n// Export singleton instance\nexport const adminApi = new AdminApiRouter();","usedDeprecatedRules":[]},{"filePath":"/home/tyler/apps/bct-whitelabel/src/lib/ai.ts","messages":[{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":67,"column":12,"nodeType":null,"messageId":"unusedVar","endLine":67,"endColumn":17},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":238,"column":12,"nodeType":null,"messageId":"unusedVar","endLine":238,"endColumn":17},{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":245,"column":37,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":245,"endColumn":40,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[7725,7728],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[7725,7728],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'organizationId' is defined but never used. Allowed unused args must match /^_/u.","line":261,"column":3,"nodeType":null,"messageId":"unusedVar","endLine":261,"endColumn":17},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'feature' is defined but never used. Allowed unused args must match /^_/u.","line":262,"column":3,"nodeType":null,"messageId":"unusedVar","endLine":262,"endColumn":10},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'tokens' is defined but never used. Allowed unused args must match /^_/u.","line":263,"column":3,"nodeType":null,"messageId":"unusedVar","endLine":263,"endColumn":9},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'cost' is defined but never used. Allowed unused args must match /^_/u.","line":264,"column":3,"nodeType":null,"messageId":"unusedVar","endLine":264,"endColumn":7}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":7,"fixableErrorCount":0,"fixableWarningCount":0,"source":"// AI-powered features for Black Canyon Tickets\n// Uses OpenAI API for premium event description generation\n\nconst OPENAI_API_KEY = import.meta.env.OPENAI_API_KEY || process.env.OPENAI_API_KEY;\nconst OPENAI_API_URL = 'https://api.openai.com/v1/chat/completions';\n\nexport interface EventDescriptionRequest {\n eventTitle: string;\n venueName: string;\n venueType?: string; // 'hotel', 'theater', 'gallery', 'outdoor', etc.\n eventType: string; // 'gala', 'wedding', 'concert', 'dance', 'theater', etc.\n eventDate?: string;\n targetAudience?: string; // 'upscale', 'family', 'corporate', 'artistic', etc.\n keyFeatures?: string[]; // ['dinner', 'dancing', 'live music', 'auction', etc.]\n tone?: 'elegant' | 'casual' | 'luxurious' | 'artistic' | 'professional';\n length?: 'short' | 'medium' | 'long';\n}\n\nexport interface EventDescriptionResponse {\n description: string;\n tagline?: string;\n marketingPoints: string[];\n seoKeywords: string[];\n socialMediaVersion?: string;\n}\n\n// Generate professional event description using OpenAI\nexport async function generateEventDescription(request: EventDescriptionRequest): Promise<EventDescriptionResponse> {\n const prompt = buildDescriptionPrompt(request);\n \n try {\n const response = await fetch(OPENAI_API_URL, {\n method: 'POST',\n headers: {\n 'Authorization': `Bearer ${OPENAI_API_KEY}`,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({\n model: 'gpt-4',\n messages: [\n {\n role: 'system',\n content: `You are a professional event marketing copywriter specializing in upscale venues. You write compelling, sophisticated event descriptions that attract discerning clientele to high-end events. Your writing style is elegant but accessible, highlighting the unique aspects of luxury venues.`\n },\n {\n role: 'user',\n content: prompt\n }\n ],\n max_tokens: 800,\n temperature: 0.7,\n }),\n });\n\n if (!response.ok) {\n throw new Error(`OpenAI API error: ${response.status} ${response.statusText}`);\n }\n\n const data = await response.json();\n const generatedText = data.choices[0]?.message?.content;\n\n if (!generatedText) {\n throw new Error('No content generated from OpenAI');\n }\n\n return parseGeneratedDescription(generatedText);\n } catch (error) {\n\n throw new Error('Failed to generate AI event description. Please try again.');\n }\n}\n\n// Build the prompt for OpenAI based on event details\nfunction buildDescriptionPrompt(request: EventDescriptionRequest): string {\n const {\n eventTitle,\n venueName,\n venueType = 'venue',\n eventType,\n eventDate,\n targetAudience = 'upscale',\n keyFeatures = [],\n tone = 'elegant',\n length = 'medium'\n } = request;\n\n const lengthGuide = {\n short: '1-2 paragraphs (150-200 words)',\n medium: '2-3 paragraphs (250-350 words)', \n long: '3-4 paragraphs (400-500 words)'\n };\n\n const toneGuide = {\n elegant: 'sophisticated and refined, emphasizing exclusivity and prestige',\n casual: 'warm and inviting, approachable but still upscale',\n luxurious: 'opulent and lavish, highlighting premium amenities and experiences',\n artistic: 'creative and cultured, appealing to arts enthusiasts',\n professional: 'polished and corporate, suitable for business events'\n };\n\n return `Write a compelling event description for \"${eventTitle}\" at ${venueName} in the Aspen/Roaring Fork Valley area.\n\nEvent Details:\n- Event Type: ${eventType}\n- Venue: ${venueName} (${venueType})\n- Target Audience: ${targetAudience}\n- Tone: ${toneGuide[tone]}\n- Length: ${lengthGuide[length]}\n${eventDate ? `- Date: ${eventDate}` : ''}\n${keyFeatures.length > 0 ? `- Key Features: ${keyFeatures.join(', ')}` : ''}\n\nRequirements:\n1. Write a main event description that captures the essence and appeal of the event\n2. Include a short, catchy tagline (10-15 words)\n3. List 3-5 key marketing points that would appeal to the target audience\n4. Suggest 5-8 SEO keywords relevant to the event and location\n5. Create a shorter social media version (1-2 sentences)\n\nFocus on:\n- The unique appeal of the Aspen/mountain luxury setting\n- What makes this event special and worth attending\n- The experience attendees will have\n- The prestige and exclusivity appropriate for the market\n\nFormat your response as:\n\nDESCRIPTION:\n[Main event description here]\n\nTAGLINE:\n[Catchy tagline here]\n\nMARKETING POINTS:\n- [Point 1]\n- [Point 2]\n- [Point 3]\n- [etc.]\n\nSEO KEYWORDS:\n[keyword1, keyword2, keyword3, etc.]\n\nSOCIAL MEDIA:\n[Short social media version]`;\n}\n\n// Parse the generated text into structured response\nfunction parseGeneratedDescription(generatedText: string): EventDescriptionResponse {\n const sections = generatedText.split(/\\n(?=[A-Z ]+:)/);\n \n let description = '';\n let tagline = '';\n let marketingPoints: string[] = [];\n let seoKeywords: string[] = [];\n let socialMediaVersion = '';\n\n sections.forEach(section => {\n const trimmedSection = section.trim();\n \n if (trimmedSection.startsWith('DESCRIPTION:')) {\n description = trimmedSection.replace('DESCRIPTION:', '').trim();\n } else if (trimmedSection.startsWith('TAGLINE:')) {\n tagline = trimmedSection.replace('TAGLINE:', '').trim();\n } else if (trimmedSection.startsWith('MARKETING POINTS:')) {\n const pointsText = trimmedSection.replace('MARKETING POINTS:', '').trim();\n marketingPoints = pointsText\n .split('\\n')\n .map(point => point.replace(/^-\\s*/, '').trim())\n .filter(point => point.length > 0);\n } else if (trimmedSection.startsWith('SEO KEYWORDS:')) {\n const keywordsText = trimmedSection.replace('SEO KEYWORDS:', '').trim();\n seoKeywords = keywordsText\n .split(',')\n .map(keyword => keyword.trim())\n .filter(keyword => keyword.length > 0);\n } else if (trimmedSection.startsWith('SOCIAL MEDIA:')) {\n socialMediaVersion = trimmedSection.replace('SOCIAL MEDIA:', '').trim();\n }\n });\n\n return {\n description,\n tagline,\n marketingPoints,\n seoKeywords,\n socialMediaVersion\n };\n}\n\n// Generate event title suggestions\nexport async function generateEventTitleSuggestions(request: {\n eventType: string;\n venueName: string;\n theme?: string;\n tone?: string;\n}): Promise<string[]> {\n const { eventType, venueName, theme, tone = 'elegant' } = request;\n \n const prompt = `Generate 5 creative, sophisticated event titles for a ${eventType} at ${venueName} in Aspen, Colorado. ${theme ? `The theme is: ${theme}.` : ''} The tone should be ${tone}. Make them appealing to upscale mountain-town clientele.\n\nFormat as a simple numbered list:\n1. [Title]\n2. [Title]\netc.`;\n\n try {\n const response = await fetch(OPENAI_API_URL, {\n method: 'POST',\n headers: {\n 'Authorization': `Bearer ${OPENAI_API_KEY}`,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({\n model: 'gpt-4',\n messages: [\n {\n role: 'system',\n content: 'You are a creative event naming specialist for luxury venues in Aspen.'\n },\n {\n role: 'user',\n content: prompt\n }\n ],\n max_tokens: 300,\n temperature: 0.8,\n }),\n });\n\n const data = await response.json();\n const generatedText = data.choices[0]?.message?.content || '';\n \n // Parse numbered list\n return generatedText\n .split('\\n')\n .map(line => line.replace(/^\\d+\\.\\s*/, '').trim())\n .filter(title => title.length > 0);\n \n } catch (error) {\n\n return [];\n }\n}\n\n// Check if user has access to AI features\nexport function hasAIAccess(addons: any[]): boolean {\n return addons.some(addon => \n addon.slug === 'ai-event-description' || \n addon.slug === 'bct-pro-monthly' ||\n addon.slug === 'enterprise-package'\n );\n}\n\n// Cost calculation for AI features\nexport const AI_FEATURE_COSTS = {\n 'ai-event-description': 500, // $5.00 in cents\n 'ai-title-suggestions': 300, // $3.00 in cents (if offered separately)\n} as const;\n\n// Usage tracking for AI features\nexport async function trackAIUsage(\n organizationId: string, \n feature: string, \n tokens: number,\n cost: number\n) {\n // This could be logged to analytics or usage tracking system\n\n}","usedDeprecatedRules":[]},{"filePath":"/home/tyler/apps/bct-whitelabel/src/lib/analytics.ts","messages":[{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'Database' is defined but never used. Allowed unused vars must match /^_/u.","line":2,"column":15,"nodeType":null,"messageId":"unusedVar","endLine":2,"endColumn":23},{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":20,"column":29,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":20,"endColumn":32,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[541,544],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[541,544],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":90,"column":16,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":90,"endColumn":19,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[2006,2009],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[2006,2009],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":174,"column":14,"nodeType":null,"messageId":"unusedVar","endLine":174,"endColumn":19},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":218,"column":14,"nodeType":null,"messageId":"unusedVar","endLine":218,"endColumn":19},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'dateFormat' is assigned a value but never used. Allowed unused vars must match /^_/u.","line":233,"column":13,"nodeType":null,"messageId":"unusedVar","endLine":233,"endColumn":23},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'data' is assigned a value but never used. Allowed unused vars must match /^_/u.","line":235,"column":15,"nodeType":null,"messageId":"unusedVar","endLine":235,"endColumn":19},{"ruleId":"no-undef","severity":2,"message":"'tickets' is not defined.","line":251,"column":7,"nodeType":"Identifier","messageId":"undef","endLine":251,"endColumn":14},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":277,"column":14,"nodeType":null,"messageId":"unusedVar","endLine":277,"endColumn":19},{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":302,"column":68,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":302,"endColumn":71,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[8180,8183],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[8180,8183],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":315,"column":14,"nodeType":null,"messageId":"unusedVar","endLine":315,"endColumn":19},{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":322,"column":53,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":322,"endColumn":56,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[8743,8746],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[8743,8746],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":344,"column":14,"nodeType":null,"messageId":"unusedVar","endLine":344,"endColumn":19},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":388,"column":14,"nodeType":null,"messageId":"unusedVar","endLine":388,"endColumn":19},{"ruleId":"no-undef","severity":2,"message":"'NodeJS' is not defined.","line":468,"column":23,"nodeType":"Identifier","messageId":"undef","endLine":468,"endColumn":29},{"ruleId":"no-empty","severity":1,"message":"Empty block statement.","line":518,"column":18,"nodeType":"BlockStatement","messageId":"unexpected","endLine":520,"endColumn":8,"suggestions":[{"messageId":"suggestComment","data":{"type":"block"},"fix":{"range":[15008,15016],"text":" /* empty */ "},"desc":"Add comment inside empty block statement."}]},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":521,"column":14,"nodeType":null,"messageId":"unusedVar","endLine":521,"endColumn":19},{"ruleId":"no-empty","severity":1,"message":"Empty block statement.","line":521,"column":21,"nodeType":"BlockStatement","messageId":"unexpected","endLine":523,"endColumn":6,"suggestions":[{"messageId":"suggestComment","data":{"type":"block"},"fix":{"range":[15039,15045],"text":" /* empty */ "},"desc":"Add comment inside empty block statement."}]},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":537,"column":14,"nodeType":null,"messageId":"unusedVar","endLine":537,"endColumn":19},{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":600,"column":39,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":600,"endColumn":42,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[16888,16891],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[16888,16891],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":637,"column":14,"nodeType":null,"messageId":"unusedVar","endLine":637,"endColumn":19},{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":675,"column":31,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":675,"endColumn":34,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[18912,18915],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[18912,18915],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":706,"column":14,"nodeType":null,"messageId":"unusedVar","endLine":706,"endColumn":19},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":752,"column":14,"nodeType":null,"messageId":"unusedVar","endLine":752,"endColumn":19},{"ruleId":"no-empty","severity":1,"message":"Empty block statement.","line":752,"column":21,"nodeType":"BlockStatement","messageId":"unexpected","endLine":754,"endColumn":6,"suggestions":[{"messageId":"suggestComment","data":{"type":"block"},"fix":{"range":[21333,21339],"text":" /* empty */ "},"desc":"Add comment inside empty block statement."}]}],"suppressedMessages":[],"errorCount":2,"fatalErrorCount":0,"warningCount":23,"fixableErrorCount":0,"fixableWarningCount":0,"source":"import { supabase } from './supabase';\nimport type { Database } from './database.types';\n\n// New types for trending/popularity analytics\nexport interface EventAnalytic {\n eventId: string;\n metricType: 'page_view' | 'ticket_view' | 'checkout_start' | 'checkout_complete';\n metricValue?: number;\n sessionId?: string;\n userId?: string;\n ipAddress?: string;\n userAgent?: string;\n referrer?: string;\n locationData?: {\n latitude?: number;\n longitude?: number;\n city?: string;\n state?: string;\n };\n metadata?: Record<string, any>;\n}\n\nexport interface TrendingEvent {\n eventId: string;\n title: string;\n venue: string;\n venueId: string;\n category: string;\n startTime: string;\n popularityScore: number;\n viewCount: number;\n ticketsSold: number;\n isFeature: boolean;\n imageUrl?: string;\n slug: string;\n distanceMiles?: number;\n}\n\nexport interface PopularityMetrics {\n viewScore: number;\n ticketScore: number;\n recencyScore: number;\n engagementScore: number;\n finalScore: number;\n}\n\n// Types for analytics data\nexport interface SalesMetrics {\n totalRevenue: number;\n netRevenue: number;\n platformFees: number;\n ticketsSold: number;\n averageTicketPrice: number;\n conversionRate: number;\n refundRate: number;\n}\n\nexport interface SalesByTimeframe {\n date: string;\n revenue: number;\n ticketsSold: number;\n averagePrice: number;\n}\n\nexport interface TicketTypePerformance {\n ticketTypeId: string;\n name: string;\n price: number;\n quantitySold: number;\n quantityAvailable: number;\n revenue: number;\n sellThroughRate: number;\n}\n\nexport interface RevenueBreakdown {\n grossRevenue: number;\n platformFees: number;\n netRevenue: number;\n stripeFees: number;\n organizerPayout: number;\n}\n\nexport interface SalesAnalyticsData {\n metrics: SalesMetrics;\n revenueBreakdown: RevenueBreakdown;\n salesByDay: SalesByTimeframe[];\n salesByHour: SalesByTimeframe[];\n ticketTypePerformance: TicketTypePerformance[];\n topSellingTickets: TicketTypePerformance[];\n recentSales: any[];\n}\n\n// Analytics calculation functions\nexport class EventAnalytics {\n private eventId: string;\n\n constructor(eventId: string) {\n this.eventId = eventId;\n }\n\n // Get comprehensive analytics data for an event\n async getAnalyticsData(): Promise<SalesAnalyticsData> {\n const [\n metrics,\n revenueBreakdown,\n salesByDay,\n salesByHour,\n ticketTypePerformance,\n recentSales\n ] = await Promise.all([\n this.getSalesMetrics(),\n this.getRevenueBreakdown(),\n this.getSalesByTimeframe('day'),\n this.getSalesByTimeframe('hour'),\n this.getTicketTypePerformance(),\n this.getRecentSales()\n ]);\n\n return {\n metrics,\n revenueBreakdown,\n salesByDay,\n salesByHour,\n ticketTypePerformance,\n topSellingTickets: ticketTypePerformance.sort((a, b) => b.quantitySold - a.quantitySold).slice(0, 5),\n recentSales\n };\n }\n\n // Calculate key sales metrics\n async getSalesMetrics(): Promise<SalesMetrics> {\n try {\n // Get ticket sales data\n const { data: tickets, error: ticketsError } = await supabase\n .from('tickets')\n .select(`\n id,\n price,\n platform_fee_charged,\n created_at,\n ticket_types!inner(\n event_id\n )\n `)\n .eq('ticket_types.event_id', this.eventId);\n\n if (ticketsError) throw ticketsError;\n\n // Get ticket types for total capacity\n const { data: ticketTypes, error: typesError } = await supabase\n .from('ticket_types')\n .select('quantity_available')\n .eq('event_id', this.eventId);\n\n if (typesError) throw typesError;\n\n const ticketsSold = tickets?.length || 0;\n const totalCapacity = ticketTypes?.reduce((sum, type) => sum + (type.quantity_available || 0), 0) || 0;\n const totalRevenue = tickets?.reduce((sum, ticket) => sum + (ticket.price || 0), 0) || 0;\n const platformFees = tickets?.reduce((sum, ticket) => sum + (ticket.platform_fee_charged || 0), 0) || 0;\n const netRevenue = totalRevenue - platformFees;\n const averageTicketPrice = ticketsSold > 0 ? totalRevenue / ticketsSold : 0;\n const conversionRate = totalCapacity > 0 ? (ticketsSold / totalCapacity) * 100 : 0;\n\n return {\n totalRevenue,\n netRevenue,\n platformFees,\n ticketsSold,\n averageTicketPrice,\n conversionRate,\n refundRate: 0 // TODO: Implement refunds tracking\n };\n } catch (error) {\n\n return {\n totalRevenue: 0,\n netRevenue: 0,\n platformFees: 0,\n ticketsSold: 0,\n averageTicketPrice: 0,\n conversionRate: 0,\n refundRate: 0\n };\n }\n }\n\n // Get detailed revenue breakdown\n async getRevenueBreakdown(): Promise<RevenueBreakdown> {\n try {\n const { data: tickets, error } = await supabase\n .from('tickets')\n .select(`\n price,\n platform_fee_charged,\n stripe_fee_charged,\n ticket_types!inner(\n event_id\n )\n `)\n .eq('ticket_types.event_id', this.eventId);\n\n if (error) throw error;\n\n const grossRevenue = tickets?.reduce((sum, ticket) => sum + (ticket.price || 0), 0) || 0;\n const platformFees = tickets?.reduce((sum, ticket) => sum + (ticket.platform_fee_charged || 0), 0) || 0;\n const stripeFees = tickets?.reduce((sum, ticket) => sum + (ticket.stripe_fee_charged || 0), 0) || 0;\n const netRevenue = grossRevenue - platformFees;\n const organizerPayout = grossRevenue - platformFees - stripeFees;\n\n return {\n grossRevenue,\n platformFees,\n netRevenue,\n stripeFees,\n organizerPayout\n };\n } catch (error) {\n\n return {\n grossRevenue: 0,\n platformFees: 0,\n netRevenue: 0,\n stripeFees: 0,\n organizerPayout: 0\n };\n }\n }\n\n // Get sales data grouped by timeframe (day or hour)\n async getSalesByTimeframe(timeframe: 'day' | 'hour'): Promise<SalesByTimeframe[]> {\n try {\n const dateFormat = timeframe === 'day' ? 'YYYY-MM-DD' : 'YYYY-MM-DD HH24:00:00';\n \n const { data, error } = await supabase\n .from('tickets')\n .select(`\n price,\n created_at,\n ticket_types!inner(\n event_id\n )\n `)\n .eq('ticket_types.event_id', this.eventId);\n\n if (error) throw error;\n\n // Group sales by timeframe\n const salesMap = new Map<string, { revenue: number; count: number }>();\n \n tickets?.forEach(ticket => {\n const date = new Date(ticket.created_at);\n let key: string;\n \n if (timeframe === 'day') {\n key = date.toISOString().split('T')[0];\n } else {\n key = `${date.toISOString().split('T')[0]} ${date.getHours().toString().padStart(2, '0')}:00:00`;\n }\n \n const existing = salesMap.get(key) || { revenue: 0, count: 0 };\n salesMap.set(key, {\n revenue: existing.revenue + (ticket.price || 0),\n count: existing.count + 1\n });\n });\n\n // Convert to array and sort by date\n return Array.from(salesMap.entries())\n .map(([date, data]) => ({\n date,\n revenue: data.revenue,\n ticketsSold: data.count,\n averagePrice: data.count > 0 ? data.revenue / data.count : 0\n }))\n .sort((a, b) => a.date.localeCompare(b.date));\n } catch (error) {\n\n return [];\n }\n }\n\n // Get performance metrics for each ticket type\n async getTicketTypePerformance(): Promise<TicketTypePerformance[]> {\n try {\n // Get ticket types with sales data\n const { data: ticketTypes, error: typesError } = await supabase\n .from('ticket_types')\n .select(`\n id,\n name,\n price,\n quantity_available,\n tickets(id, price)\n `)\n .eq('event_id', this.eventId);\n\n if (typesError) throw typesError;\n\n return ticketTypes?.map(type => {\n const quantitySold = type.tickets?.length || 0;\n const revenue = type.tickets?.reduce((sum: number, ticket: any) => sum + (ticket.price || 0), 0) || 0;\n const sellThroughRate = type.quantity_available > 0 ? (quantitySold / type.quantity_available) * 100 : 0;\n\n return {\n ticketTypeId: type.id,\n name: type.name,\n price: type.price || 0,\n quantitySold,\n quantityAvailable: type.quantity_available || 0,\n revenue,\n sellThroughRate\n };\n }) || [];\n } catch (error) {\n\n return [];\n }\n }\n\n // Get recent sales transactions\n async getRecentSales(limit: number = 20): Promise<any[]> {\n try {\n const { data: tickets, error } = await supabase\n .from('tickets')\n .select(`\n id,\n price,\n purchaser_name,\n purchaser_email,\n created_at,\n ticket_types!inner(\n event_id,\n name\n )\n `)\n .eq('ticket_types.event_id', this.eventId)\n .order('created_at', { ascending: false })\n .limit(limit);\n\n if (error) throw error;\n\n return tickets || [];\n } catch (error) {\n\n return [];\n }\n }\n\n // Get sales velocity (sales per hour/day trends)\n async getSalesVelocity(): Promise<{ current: number; trend: 'up' | 'down' | 'stable' }> {\n try {\n const now = new Date();\n const oneDayAgo = new Date(now.getTime() - 24 * 60 * 60 * 1000);\n const twoDaysAgo = new Date(now.getTime() - 48 * 60 * 60 * 1000);\n\n const { data: recentSales, error: recentError } = await supabase\n .from('tickets')\n .select(`\n id,\n created_at,\n ticket_types!inner(event_id)\n `)\n .eq('ticket_types.event_id', this.eventId)\n .gte('created_at', oneDayAgo.toISOString());\n\n const { data: previousSales, error: previousError } = await supabase\n .from('tickets')\n .select(`\n id,\n created_at,\n ticket_types!inner(event_id)\n `)\n .eq('ticket_types.event_id', this.eventId)\n .gte('created_at', twoDaysAgo.toISOString())\n .lt('created_at', oneDayAgo.toISOString());\n\n if (recentError || previousError) throw recentError || previousError;\n\n const currentVelocity = recentSales?.length || 0;\n const previousVelocity = previousSales?.length || 0;\n\n let trend: 'up' | 'down' | 'stable' = 'stable';\n if (currentVelocity > previousVelocity * 1.1) trend = 'up';\n else if (currentVelocity < previousVelocity * 0.9) trend = 'down';\n\n return { current: currentVelocity, trend };\n } catch (error) {\n\n return { current: 0, trend: 'stable' };\n }\n }\n\n // Format currency values\n static formatCurrency(amount: number): string {\n return new Intl.NumberFormat('en-US', {\n style: 'currency',\n currency: 'USD'\n }).format(amount);\n }\n\n // Format percentage values\n static formatPercentage(value: number): string {\n return `${value.toFixed(1)}%`;\n }\n\n // Format large numbers\n static formatNumber(value: number): string {\n if (value >= 1000000) {\n return `${(value / 1000000).toFixed(1)}M`;\n } else if (value >= 1000) {\n return `${(value / 1000).toFixed(1)}K`;\n }\n return value.toString();\n }\n}\n\n// Export data to CSV\nexport function exportAnalyticsToCSV(data: SalesAnalyticsData, eventTitle: string): void {\n const csvContent = [\n // Summary metrics\n ['Sales Analytics Report', eventTitle],\n ['Generated', new Date().toISOString()],\n [''],\n ['SUMMARY METRICS'],\n ['Total Revenue', EventAnalytics.formatCurrency(data.metrics.totalRevenue)],\n ['Net Revenue', EventAnalytics.formatCurrency(data.metrics.netRevenue)],\n ['Platform Fees', EventAnalytics.formatCurrency(data.metrics.platformFees)],\n ['Tickets Sold', data.metrics.ticketsSold.toString()],\n ['Average Ticket Price', EventAnalytics.formatCurrency(data.metrics.averageTicketPrice)],\n ['Conversion Rate', EventAnalytics.formatPercentage(data.metrics.conversionRate)],\n [''],\n ['TICKET TYPE PERFORMANCE'],\n ['Ticket Type', 'Price', 'Sold', 'Available', 'Revenue', 'Sell-through Rate'],\n ...data.ticketTypePerformance.map(type => [\n type.name,\n EventAnalytics.formatCurrency(type.price),\n type.quantitySold.toString(),\n type.quantityAvailable.toString(),\n EventAnalytics.formatCurrency(type.revenue),\n EventAnalytics.formatPercentage(type.sellThroughRate)\n ]),\n [''],\n ['DAILY SALES'],\n ['Date', 'Revenue', 'Tickets Sold', 'Average Price'],\n ...data.salesByDay.map(day => [\n day.date,\n EventAnalytics.formatCurrency(day.revenue),\n day.ticketsSold.toString(),\n EventAnalytics.formatCurrency(day.averagePrice)\n ])\n ];\n\n const csv = csvContent.map(row => row.join(',')).join('\\n');\n const blob = new Blob([csv], { type: 'text/csv' });\n const url = window.URL.createObjectURL(blob);\n const link = document.createElement('a');\n link.href = url;\n link.download = `${eventTitle.replace(/[^a-z0-9]/gi, '_').toLowerCase()}_analytics_${new Date().toISOString().split('T')[0]}.csv`;\n link.click();\n window.URL.revokeObjectURL(url);\n}\n\n// New trending/popularity analytics service\nexport class TrendingAnalyticsService {\n private static instance: TrendingAnalyticsService;\n private batchedEvents: EventAnalytic[] = [];\n private batchTimer: NodeJS.Timeout | null = null;\n private readonly BATCH_SIZE = 10;\n private readonly BATCH_TIMEOUT = 5000; // 5 seconds\n\n static getInstance(): TrendingAnalyticsService {\n if (!TrendingAnalyticsService.instance) {\n TrendingAnalyticsService.instance = new TrendingAnalyticsService();\n }\n return TrendingAnalyticsService.instance;\n }\n\n async trackEvent(analytic: EventAnalytic): Promise<void> {\n this.batchedEvents.push(analytic);\n\n if (this.batchedEvents.length >= this.BATCH_SIZE) {\n await this.flushBatch();\n } else if (!this.batchTimer) {\n this.batchTimer = setTimeout(() => {\n this.flushBatch();\n }, this.BATCH_TIMEOUT);\n }\n }\n\n private async flushBatch(): Promise<void> {\n if (this.batchedEvents.length === 0) return;\n\n const events = [...this.batchedEvents];\n this.batchedEvents = [];\n\n if (this.batchTimer) {\n clearTimeout(this.batchTimer);\n this.batchTimer = null;\n }\n\n try {\n const { error } = await supabase\n .from('event_analytics')\n .insert(events.map(event => ({\n event_id: event.eventId,\n metric_type: event.metricType,\n metric_value: event.metricValue || 1,\n session_id: event.sessionId,\n user_id: event.userId,\n ip_address: event.ipAddress,\n user_agent: event.userAgent,\n referrer: event.referrer,\n location_data: event.locationData,\n metadata: event.metadata || {}\n })));\n\n if (error) {\n\n }\n } catch (error) {\n\n }\n }\n\n async updateEventPopularityScore(eventId: string): Promise<number> {\n try {\n const { data, error } = await supabase\n .rpc('calculate_event_popularity_score', { event_id_param: eventId });\n\n if (error) {\n\n return 0;\n }\n\n return data || 0;\n } catch (error) {\n\n return 0;\n }\n }\n\n async getTrendingEvents(\n latitude?: number,\n longitude?: number,\n radiusMiles: number = 50,\n limit: number = 20\n ): Promise<TrendingEvent[]> {\n try {\n const query = supabase\n .from('events')\n .select(`\n id,\n title,\n venue,\n venue_id,\n category,\n start_time,\n popularity_score,\n view_count,\n is_featured,\n image_url,\n slug,\n venues!inner (\n id,\n name,\n latitude,\n longitude\n )\n `)\n .eq('is_published', true)\n .eq('is_public', true)\n .gt('start_time', new Date().toISOString())\n .order('popularity_score', { ascending: false })\n .limit(limit);\n\n const { data: events, error } = await query;\n\n if (error) {\n\n return [];\n }\n\n if (!events) return [];\n\n // Get ticket sales for each event\n const eventIds = events.map(event => event.id);\n const { data: ticketData } = await supabase\n .from('tickets')\n .select('event_id')\n .in('event_id', eventIds);\n\n const ticketCounts = ticketData?.reduce((acc, ticket) => {\n acc[ticket.event_id] = (acc[ticket.event_id] || 0) + 1;\n return acc;\n }, {} as Record<string, number>) || {};\n\n // Calculate distances if user location is provided\n const trendingEvents: TrendingEvent[] = events.map(event => {\n const venue = event.venues as any;\n let distanceMiles: number | undefined;\n\n if (latitude && longitude && venue?.latitude && venue?.longitude) {\n distanceMiles = this.calculateDistance(\n latitude,\n longitude,\n venue.latitude,\n venue.longitude\n );\n }\n\n return {\n eventId: event.id,\n title: event.title,\n venue: event.venue,\n venueId: event.venue_id,\n category: event.category || 'General',\n startTime: event.start_time,\n popularityScore: event.popularity_score || 0,\n viewCount: event.view_count || 0,\n ticketsSold: ticketCounts[event.id] || 0,\n isFeature: event.is_featured || false,\n imageUrl: event.image_url,\n slug: event.slug,\n distanceMiles\n };\n });\n\n // Filter by location if provided\n if (latitude && longitude) {\n return trendingEvents.filter(event => \n event.distanceMiles === undefined || event.distanceMiles <= radiusMiles\n );\n }\n\n return trendingEvents;\n } catch (error) {\n\n return [];\n }\n }\n\n async getHotEventsInArea(\n latitude: number,\n longitude: number,\n radiusMiles: number = 25,\n limit: number = 10\n ): Promise<TrendingEvent[]> {\n try {\n const { data, error } = await supabase\n .rpc('get_events_within_radius', {\n user_lat: latitude,\n user_lng: longitude,\n radius_miles: radiusMiles,\n limit_count: limit\n });\n\n if (error) {\n\n return [];\n }\n\n if (!data) return [];\n\n // Get complete event data for each result\n const eventIds = data.map(event => event.event_id);\n const { data: eventDetails } = await supabase\n .from('events')\n .select('id, image_url, slug')\n .in('id', eventIds);\n\n const eventDetailsMap = eventDetails?.reduce((acc, event) => {\n acc[event.id] = event;\n return acc;\n }, {} as Record<string, any>) || {};\n\n // Get ticket sales for each event\n const { data: ticketData } = await supabase\n .from('tickets')\n .select('event_id')\n .in('event_id', eventIds);\n\n const ticketCounts = ticketData?.reduce((acc, ticket) => {\n acc[ticket.event_id] = (acc[ticket.event_id] || 0) + 1;\n return acc;\n }, {} as Record<string, number>) || {};\n\n return data.map(event => {\n const details = eventDetailsMap[event.event_id];\n return {\n eventId: event.event_id,\n title: event.title,\n venue: event.venue,\n venueId: event.venue_id,\n category: event.category || 'General',\n startTime: event.start_time,\n popularityScore: event.popularity_score || 0,\n viewCount: 0,\n ticketsSold: ticketCounts[event.event_id] || 0,\n isFeature: event.is_featured || false,\n imageUrl: details?.image_url,\n slug: details?.slug || '',\n distanceMiles: event.distance_miles\n };\n });\n } catch (error) {\n\n return [];\n }\n }\n\n private calculateDistance(lat1: number, lon1: number, lat2: number, lon2: number): number {\n const R = 3959; // Earth's radius in miles\n const dLat = this.toRad(lat2 - lat1);\n const dLon = this.toRad(lon2 - lon1);\n const a = \n Math.sin(dLat/2) * Math.sin(dLat/2) +\n Math.cos(this.toRad(lat1)) * Math.cos(this.toRad(lat2)) * \n Math.sin(dLon/2) * Math.sin(dLon/2);\n const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));\n return R * c;\n }\n\n private toRad(value: number): number {\n return value * Math.PI / 180;\n }\n\n async batchUpdatePopularityScores(): Promise<void> {\n try {\n const { data: events, error } = await supabase\n .from('events')\n .select('id')\n .eq('is_published', true)\n .gt('start_time', new Date().toISOString());\n\n if (error || !events) {\n\n return;\n }\n\n // Process in batches to avoid overwhelming the database\n const batchSize = 10;\n for (let i = 0; i < events.length; i += batchSize) {\n const batch = events.slice(i, i + batchSize);\n await Promise.all(\n batch.map(event => this.updateEventPopularityScore(event.id))\n );\n \n // Add a small delay between batches\n await new Promise(resolve => setTimeout(resolve, 100));\n }\n } catch (error) {\n\n }\n }\n}\n\nexport const trendingAnalyticsService = TrendingAnalyticsService.getInstance();","usedDeprecatedRules":[]},{"filePath":"/home/tyler/apps/bct-whitelabel/src/lib/api-client.ts","messages":[{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'Database' is defined but never used. Allowed unused vars must match /^_/u.","line":2,"column":15,"nodeType":null,"messageId":"unusedVar","endLine":2,"endColumn":23},{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":30,"column":20,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":30,"endColumn":23,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[581,584],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[581,584],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"no-empty","severity":1,"message":"Empty block statement.","line":60,"column":22,"nodeType":"BlockStatement","messageId":"unexpected","endLine":62,"endColumn":8,"suggestions":[{"messageId":"suggestComment","data":{"type":"block"},"fix":{"range":[1286,1294],"text":" /* empty */ "},"desc":"Add comment inside empty block statement."}]},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":67,"column":14,"nodeType":null,"messageId":"unusedVar","endLine":67,"endColumn":19},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":104,"column":14,"nodeType":null,"messageId":"unusedVar","endLine":104,"endColumn":19},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":169,"column":14,"nodeType":null,"messageId":"unusedVar","endLine":169,"endColumn":19},{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":175,"column":62,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":175,"endColumn":65,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[4557,4560],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[4557,4560],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":194,"column":14,"nodeType":null,"messageId":"unusedVar","endLine":194,"endColumn":19},{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":200,"column":57,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":200,"endColumn":60,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[5265,5268],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[5265,5268],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":224,"column":14,"nodeType":null,"messageId":"unusedVar","endLine":224,"endColumn":19},{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":230,"column":63,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":230,"endColumn":66,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[6029,6032],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[6029,6032],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":248,"column":14,"nodeType":null,"messageId":"unusedVar","endLine":248,"endColumn":19},{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":254,"column":64,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":254,"endColumn":67,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[6713,6716],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[6713,6716],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":272,"column":14,"nodeType":null,"messageId":"unusedVar","endLine":272,"endColumn":19},{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":278,"column":60,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":278,"endColumn":63,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[7395,7398],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[7395,7398],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":302,"column":14,"nodeType":null,"messageId":"unusedVar","endLine":302,"endColumn":19},{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":313,"column":14,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":313,"endColumn":17,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[8234,8237],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[8234,8237],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":317,"column":17,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":317,"endColumn":20,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[8300,8303],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[8300,8303],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"no-undef","severity":2,"message":"'RequestInit' is not defined.","line":335,"column":14,"nodeType":"Identifier","messageId":"undef","endLine":335,"endColumn":25},{"ruleId":"no-undef","severity":2,"message":"'RequestInit' is not defined.","line":386,"column":12,"nodeType":"Identifier","messageId":"undef","endLine":386,"endColumn":23}],"suppressedMessages":[],"errorCount":2,"fatalErrorCount":0,"warningCount":18,"fixableErrorCount":0,"fixableWarningCount":0,"source":"import { supabase } from './supabase';\nimport type { Database } from './database.types';\n\nexport interface ApiResponse<T> {\n data: T | null;\n error: string | null;\n success: boolean;\n}\n\nexport interface EventStats {\n ticketsSold: number;\n ticketsAvailable: number;\n checkedIn: number;\n totalRevenue: number;\n netRevenue: number;\n}\n\nexport interface EventDetails {\n id: string;\n title: string;\n description: string;\n venue: string;\n start_time: string;\n slug: string;\n organization_id: string;\n}\n\nclass ApiClient {\n private authenticated = false;\n private session: any = null;\n private userOrganizationId: string | null = null;\n\n async initialize(): Promise<boolean> {\n try {\n\n const { data: { session }, error: sessionError } = await supabase.auth.getSession();\n \n if (sessionError) {\n\n this.authenticated = false;\n return false;\n }\n \n if (!session) {\n\n this.authenticated = false;\n return false;\n }\n\n this.session = session;\n this.authenticated = true;\n\n // Get user's organization\n const { data: userRecord, error: userError } = await supabase\n .from('users')\n .select('organization_id')\n .eq('id', session.user.id)\n .single();\n\n if (userError) {\n\n }\n\n this.userOrganizationId = userRecord?.organization_id || null;\n\n return true;\n } catch (error) {\n\n this.authenticated = false;\n return false;\n }\n }\n\n private async ensureAuthenticated(): Promise<boolean> {\n if (!this.authenticated) {\n const initialized = await this.initialize();\n if (!initialized) {\n\n // Don't automatically redirect here - let the component handle it\n return false;\n }\n }\n return true;\n }\n\n async getEventDetails(eventId: string): Promise<ApiResponse<EventDetails>> {\n try {\n if (!(await this.ensureAuthenticated())) {\n return { data: null, error: 'Authentication required', success: false };\n }\n\n const { data: event, error } = await supabase\n .from('events')\n .select('*')\n .eq('id', eventId)\n .single();\n\n if (error) {\n\n return { data: null, error: error.message, success: false };\n }\n\n return { data: event, error: null, success: true };\n } catch (error) {\n\n return { data: null, error: 'Failed to load event details', success: false };\n }\n }\n\n async getEventStats(eventId: string): Promise<ApiResponse<EventStats>> {\n try {\n if (!(await this.ensureAuthenticated())) {\n return { data: null, error: 'Authentication required', success: false };\n }\n\n // Load ticket sales data\n const { data: tickets, error: ticketsError } = await supabase\n .from('tickets')\n .select(`\n id,\n price,\n checked_in,\n refund_status,\n ticket_types (\n id,\n quantity_available\n )\n `)\n .eq('event_id', eventId);\n\n if (ticketsError) {\n\n return { data: null, error: ticketsError.message, success: false };\n }\n\n // Load ticket types for capacity calculation\n const { data: ticketTypes, error: ticketTypesError } = await supabase\n .from('ticket_types')\n .select('id, quantity_available')\n .eq('event_id', eventId)\n .eq('is_active', true);\n\n if (ticketTypesError) {\n\n return { data: null, error: ticketTypesError.message, success: false };\n }\n\n // Calculate stats\n const ticketsSold = tickets?.length || 0;\n // Only count non-refunded tickets for revenue\n const activeTickets = tickets?.filter(ticket => \n !ticket.refund_status || ticket.refund_status === null\n ) || [];\n const totalRevenue = activeTickets.reduce((sum, ticket) => sum + ticket.price, 0) || 0;\n const netRevenue = totalRevenue * 0.97; // Assuming 3% platform fee\n const checkedIn = tickets?.filter(ticket => ticket.checked_in).length || 0;\n const totalCapacity = ticketTypes?.reduce((sum, type) => sum + type.quantity_available, 0) || 0;\n const ticketsAvailable = totalCapacity - ticketsSold;\n\n const stats: EventStats = {\n ticketsSold,\n ticketsAvailable,\n checkedIn,\n totalRevenue,\n netRevenue\n };\n\n return { data: stats, error: null, success: true };\n } catch (error) {\n\n return { data: null, error: 'Failed to load event statistics', success: false };\n }\n }\n\n async getTicketTypes(eventId: string): Promise<ApiResponse<any[]>> {\n try {\n if (!(await this.ensureAuthenticated())) {\n return { data: null, error: 'Authentication required', success: false };\n }\n\n const { data, error } = await supabase\n .from('ticket_types')\n .select('*')\n .eq('event_id', eventId)\n .eq('is_active', true)\n .order('display_order', { ascending: true });\n\n if (error) {\n\n return { data: null, error: error.message, success: false };\n }\n\n return { data: data || [], error: null, success: true };\n } catch (error) {\n\n return { data: null, error: 'Failed to load ticket types', success: false };\n }\n }\n\n async getOrders(eventId: string): Promise<ApiResponse<any[]>> {\n try {\n if (!(await this.ensureAuthenticated())) {\n return { data: null, error: 'Authentication required', success: false };\n }\n\n const { data, error } = await supabase\n .from('tickets')\n .select(`\n *,\n ticket_types (\n name,\n price\n )\n `)\n .eq('event_id', eventId)\n .order('created_at', { ascending: false });\n\n if (error) {\n\n return { data: null, error: error.message, success: false };\n }\n\n return { data: data || [], error: null, success: true };\n } catch (error) {\n\n return { data: null, error: 'Failed to load orders', success: false };\n }\n }\n\n async getPresaleCodes(eventId: string): Promise<ApiResponse<any[]>> {\n try {\n if (!(await this.ensureAuthenticated())) {\n return { data: null, error: 'Authentication required', success: false };\n }\n\n const { data, error } = await supabase\n .from('presale_codes')\n .select('*')\n .eq('event_id', eventId)\n .order('created_at', { ascending: false });\n\n if (error) {\n\n return { data: null, error: error.message, success: false };\n }\n\n return { data: data || [], error: null, success: true };\n } catch (error) {\n\n return { data: null, error: 'Failed to load presale codes', success: false };\n }\n }\n\n async getDiscountCodes(eventId: string): Promise<ApiResponse<any[]>> {\n try {\n if (!(await this.ensureAuthenticated())) {\n return { data: null, error: 'Authentication required', success: false };\n }\n\n const { data, error } = await supabase\n .from('discount_codes')\n .select('*')\n .eq('event_id', eventId)\n .order('created_at', { ascending: false });\n\n if (error) {\n\n return { data: null, error: error.message, success: false };\n }\n\n return { data: data || [], error: null, success: true };\n } catch (error) {\n\n return { data: null, error: 'Failed to load discount codes', success: false };\n }\n }\n\n async getAttendees(eventId: string): Promise<ApiResponse<any[]>> {\n try {\n if (!(await this.ensureAuthenticated())) {\n return { data: null, error: 'Authentication required', success: false };\n }\n\n const { data, error } = await supabase\n .from('tickets')\n .select(`\n *,\n ticket_types (\n name\n )\n `)\n .eq('event_id', eventId)\n .is('refund_status', null)\n .order('created_at', { ascending: false });\n\n if (error) {\n\n return { data: null, error: error.message, success: false };\n }\n\n return { data: data || [], error: null, success: true };\n } catch (error) {\n\n return { data: null, error: 'Failed to load attendees', success: false };\n }\n }\n\n // Utility methods\n getUserOrganizationId(): string | null {\n return this.userOrganizationId;\n }\n\n getUser(): any {\n return this.session?.user || null;\n }\n\n getSession(): any {\n return this.session;\n }\n\n isAuthenticated(): boolean {\n return this.authenticated;\n }\n\n async logout(): Promise<void> {\n await supabase.auth.signOut();\n this.authenticated = false;\n this.session = null;\n this.userOrganizationId = null;\n }\n\n // Generic authenticated request helper\n async makeAuthenticatedRequest<T>(\n url: string,\n options: RequestInit = {}\n ): Promise<ApiResponse<T>> {\n try {\n if (!(await this.ensureAuthenticated())) {\n return { data: null, error: 'Authentication required', success: false };\n }\n\n const response = await fetch(url, {\n ...options,\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${this.session?.access_token}`,\n ...options.headers,\n },\n });\n\n if (!response.ok) {\n const errorText = await response.text();\n return {\n data: null,\n error: errorText || `HTTP ${response.status}`,\n success: false\n };\n }\n\n const data = await response.json();\n return { data, error: null, success: true };\n } catch (error) {\n return {\n data: null,\n error: error instanceof Error ? error.message : 'Request failed',\n success: false\n };\n }\n }\n}\n\n// Export a singleton instance\nexport const apiClient = new ApiClient();\n\n// Export a hook-like function for easier use in components\nexport async function useApi() {\n if (!apiClient.isAuthenticated()) {\n await apiClient.initialize();\n }\n return apiClient;\n}\n\n// Export the makeAuthenticatedRequest function for direct use\nexport async function makeAuthenticatedRequest<T>(\n url: string,\n options: RequestInit = {}\n): Promise<ApiResponse<T>> {\n return apiClient.makeAuthenticatedRequest<T>(url, options);\n}","usedDeprecatedRules":[]},{"filePath":"/home/tyler/apps/bct-whitelabel/src/lib/api-router.ts","messages":[{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'apiClient' is defined but never used. Allowed unused vars must match /^_/u.","line":1,"column":10,"nodeType":null,"messageId":"unusedVar","endLine":1,"endColumn":19},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":34,"column":14,"nodeType":null,"messageId":"unusedVar","endLine":34,"endColumn":19},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":58,"column":14,"nodeType":null,"messageId":"unusedVar","endLine":58,"endColumn":19},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":78,"column":14,"nodeType":null,"messageId":"unusedVar","endLine":78,"endColumn":19},{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":87,"column":58,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":87,"endColumn":61,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[2171,2174],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[2171,2174],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":98,"column":14,"nodeType":null,"messageId":"unusedVar","endLine":98,"endColumn":19},{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":107,"column":53,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":107,"endColumn":56,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[2570,2573],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[2570,2573],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":118,"column":14,"nodeType":null,"messageId":"unusedVar","endLine":118,"endColumn":19},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":165,"column":14,"nodeType":null,"messageId":"unusedVar","endLine":165,"endColumn":19},{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":176,"column":11,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":176,"endColumn":14,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[4215,4218],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[4215,4218],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":195,"column":14,"nodeType":null,"messageId":"unusedVar","endLine":195,"endColumn":19},{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":208,"column":59,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":208,"endColumn":62,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[5055,5058],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[5055,5058],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":219,"column":14,"nodeType":null,"messageId":"unusedVar","endLine":219,"endColumn":19},{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":228,"column":60,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":228,"endColumn":63,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[5470,5473],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[5470,5473],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":239,"column":14,"nodeType":null,"messageId":"unusedVar","endLine":239,"endColumn":19},{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":248,"column":56,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":248,"endColumn":59,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[5877,5880],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[5877,5880],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":259,"column":14,"nodeType":null,"messageId":"unusedVar","endLine":259,"endColumn":19},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":272,"column":14,"nodeType":null,"messageId":"unusedVar","endLine":272,"endColumn":19},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":282,"column":14,"nodeType":null,"messageId":"unusedVar","endLine":282,"endColumn":19}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":19,"fixableErrorCount":0,"fixableWarningCount":0,"source":"import { apiClient } from './api-client';\nimport type { EventStats, EventDetails } from './api-client';\nimport type { EventData } from './event-management';\n\n/**\n * Browser-friendly API router that handles all the common API calls\n * This is designed to be used in Astro client-side scripts and React components\n */\nexport class ApiRouter {\n \n /**\n * Load event details and statistics for the event management page\n */\n static async loadEventPage(eventId: string): Promise<{\n event: EventDetails | null;\n stats: EventStats | null;\n error: string | null;\n }> {\n try {\n // Initialize API client\n const client = await import('./api-client').then(m => m.useApi());\n\n // Load event details and stats in parallel\n const [eventResult, statsResult] = await Promise.all([\n client.getEventDetails(eventId),\n client.getEventStats(eventId)\n ]);\n\n return {\n event: eventResult.data,\n stats: statsResult.data,\n error: eventResult.error || statsResult.error\n };\n } catch (error) {\n\n return {\n event: null,\n stats: null,\n error: 'Failed to load event data'\n };\n }\n }\n\n /**\n * Load only event statistics (for QuickStats component)\n */\n static async loadEventStats(eventId: string): Promise<EventStats | null> {\n try {\n const client = await import('./api-client').then(m => m.useApi());\n \n const result = await client.getEventStats(eventId);\n if (!result.success) {\n\n return null;\n }\n \n return result.data;\n } catch (error) {\n\n return null;\n }\n }\n\n /**\n * Load only event details (for EventHeader component)\n */\n static async loadEventDetails(eventId: string): Promise<EventDetails | null> {\n try {\n const client = await import('./api-client').then(m => m.useApi());\n \n const result = await client.getEventDetails(eventId);\n if (!result.success) {\n\n return null;\n }\n \n return result.data;\n } catch (error) {\n\n return null;\n }\n }\n\n /**\n * Load ticket types for an event\n */\n static async loadTicketTypes(eventId: string): Promise<any[]> {\n try {\n const client = await import('./api-client').then(m => m.useApi());\n \n const result = await client.getTicketTypes(eventId);\n if (!result.success) {\n\n return [];\n }\n \n return result.data || [];\n } catch (error) {\n\n return [];\n }\n }\n\n /**\n * Load orders for an event\n */\n static async loadOrders(eventId: string): Promise<any[]> {\n try {\n const client = await import('./api-client').then(m => m.useApi());\n \n const result = await client.getOrders(eventId);\n if (!result.success) {\n\n return [];\n }\n \n return result.data || [];\n } catch (error) {\n\n return [];\n }\n }\n\n /**\n * Utility method to format currency\n */\n static formatCurrency(amount: number): string {\n return new Intl.NumberFormat('en-US', {\n style: 'currency',\n currency: 'USD'\n }).format(amount / 100);\n }\n\n /**\n * Utility method to format date\n */\n static formatDate(dateString: string): string {\n return new Date(dateString).toLocaleDateString('en-US', {\n weekday: 'long',\n year: 'numeric',\n month: 'long',\n day: 'numeric',\n hour: 'numeric',\n minute: '2-digit'\n });\n }\n\n /**\n * Load complete event data (for EventManagement component)\n * This consolidates the loadEventData function into the API router\n */\n static async loadEventData(eventId: string): Promise<EventData | null> {\n try {\n const { loadEventData } = await import('./event-management');\n const client = await import('./api-client').then(m => m.useApi());\n \n // Get user's organization ID from the API client\n const userOrganizationId = await client.getUserOrganizationId();\n if (!userOrganizationId) {\n\n return null;\n }\n \n return await loadEventData(eventId, userOrganizationId);\n } catch (error) {\n\n return null;\n }\n }\n\n /**\n * Check authentication status\n */\n static async checkAuth(): Promise<{\n authenticated: boolean;\n user: any;\n organizationId: string | null;\n }> {\n try {\n const client = await import('./api-client').then(m => m.useApi());\n \n // The useApi function already initializes if needed, but let's double-check\n // by calling initialize again to ensure we have the latest session state\n await client.initialize();\n \n const isAuthenticated = client.isAuthenticated();\n const user = client.getUser();\n const organizationId = client.getUserOrganizationId();\n\n return {\n authenticated: isAuthenticated,\n user,\n organizationId\n };\n } catch (error) {\n\n return {\n authenticated: false,\n user: null,\n organizationId: null\n };\n }\n }\n\n /**\n * Load presale codes for an event\n */\n static async loadPresaleCodes(eventId: string): Promise<any[]> {\n try {\n const client = await import('./api-client').then(m => m.useApi());\n \n const result = await client.getPresaleCodes(eventId);\n if (!result.success) {\n\n return [];\n }\n \n return result.data || [];\n } catch (error) {\n\n return [];\n }\n }\n\n /**\n * Load discount codes for an event\n */\n static async loadDiscountCodes(eventId: string): Promise<any[]> {\n try {\n const client = await import('./api-client').then(m => m.useApi());\n \n const result = await client.getDiscountCodes(eventId);\n if (!result.success) {\n\n return [];\n }\n \n return result.data || [];\n } catch (error) {\n\n return [];\n }\n }\n\n /**\n * Load attendees for an event\n */\n static async loadAttendees(eventId: string): Promise<any[]> {\n try {\n const client = await import('./api-client').then(m => m.useApi());\n \n const result = await client.getAttendees(eventId);\n if (!result.success) {\n\n return [];\n }\n \n return result.data || [];\n } catch (error) {\n\n return [];\n }\n }\n\n /**\n * Territory Manager integration\n */\n static async isTerritoryManager(): Promise<boolean> {\n try {\n const { TerritoryManagerAuth } = await import('./territory-manager-auth');\n return await TerritoryManagerAuth.isTerritoryManager();\n } catch (error) {\n\n return false;\n }\n }\n\n static async getTerritoryManagerRouter() {\n try {\n const { territoryManagerRouter } = await import('./territory-manager-router');\n return territoryManagerRouter;\n } catch (error) {\n\n return null;\n }\n }\n}\n\n// Export the router for global use\nexport const api = ApiRouter;","usedDeprecatedRules":[]},{"filePath":"/home/tyler/apps/bct-whitelabel/src/lib/auth.ts","messages":[{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":108,"column":12,"nodeType":null,"messageId":"unusedVar","endLine":108,"endColumn":17},{"ruleId":"no-undef","severity":2,"message":"'crypto' is not defined.","line":187,"column":10,"nodeType":"Identifier","messageId":"undef","endLine":187,"endColumn":16}],"suppressedMessages":[],"errorCount":1,"fatalErrorCount":0,"warningCount":1,"fixableErrorCount":0,"fixableWarningCount":0,"source":"import { supabase } from './supabase';\nimport { logSecurityEvent, logUserActivity } from './logger';\nimport type { User, Session } from '@supabase/supabase-js';\n\n// Get the Supabase URL for cookie name generation\nconst supabaseUrl = import.meta.env.PUBLIC_SUPABASE_URL || import.meta.env.SUPABASE_URL;\n\nexport interface AuthContext {\n user: User;\n session: Session;\n isAdmin?: boolean;\n organizationId?: string;\n}\n\n/**\n * Server-side authentication verification\n * Validates the auth token from cookies or headers\n */\nexport async function verifyAuth(request: Request): Promise<AuthContext | null> {\n try {\n // Get auth token from Authorization header or cookies\n const authHeader = request.headers.get('Authorization');\n const cookieHeader = request.headers.get('Cookie');\n \n let accessToken: string | null = null;\n \n // Try Authorization header first\n if (authHeader && authHeader.startsWith('Bearer ')) {\n accessToken = authHeader.substring(7);\n }\n \n // Try cookies if no auth header\n if (!accessToken && cookieHeader) {\n const cookies = parseCookies(cookieHeader);\n \n // Extract Supabase project ref for cookie names\n const projectRef = supabaseUrl.split('//')[1].split('.')[0];\n \n // Check for various Supabase cookie patterns\n accessToken = cookies[`sb-${projectRef}-auth-token`] || \n cookies[`sb-${projectRef}-auth-token.0`] ||\n cookies[`sb-${projectRef}-auth-token.1`] ||\n cookies['sb-access-token'] || \n cookies['supabase-auth-token'] ||\n cookies['access_token'];\n \n // Log for debugging (only if no token found)\n if (!accessToken) {\n console.log('Auth debug - no token found:', {\n projectRef,\n cookieKeys: Object.keys(cookies).filter(k => k.includes('sb') || k.includes('supabase')),\n tokenSource: 'none'\n });\n }\n }\n \n if (!accessToken) {\n return null;\n }\n \n // Verify the token with Supabase\n const { data: { user }, error } = await supabase.auth.getUser(accessToken);\n \n if (error || !user) {\n // Log failed authentication attempt\n logSecurityEvent({\n type: 'auth_failure',\n ipAddress: getClientIPFromHeaders(request),\n userAgent: request.headers.get('User-Agent') || undefined,\n severity: 'medium',\n details: { error: error?.message, reason: 'invalid_token' }\n });\n return null;\n }\n \n // Get user's organization\n const { data: userRecord } = await supabase\n .from('users')\n .select('organization_id, role')\n .eq('id', user.id)\n .single();\n \n // Mock session object (since we're doing server-side verification)\n const session: Session = {\n access_token: accessToken,\n refresh_token: '', // Not needed for verification\n expires_in: 3600,\n expires_at: Date.now() / 1000 + 3600,\n token_type: 'bearer',\n user\n };\n \n // Log successful authentication\n logUserActivity({\n action: 'auth_success',\n userId: user.id,\n ipAddress: getClientIPFromHeaders(request),\n userAgent: request.headers.get('User-Agent') || undefined,\n details: { organizationId: userRecord?.organization_id, role: userRecord?.role }\n });\n\n return {\n user,\n session,\n isAdmin: userRecord?.role === 'admin',\n organizationId: userRecord?.organization_id\n };\n } catch (error) {\n\n return null;\n }\n}\n\n/**\n * Middleware function to protect routes\n */\nexport async function requireAuth(request: Request): Promise<AuthContext> {\n const auth = await verifyAuth(request);\n \n if (!auth) {\n logSecurityEvent({\n type: 'access_denied',\n ipAddress: getClientIPFromHeaders(request),\n userAgent: request.headers.get('User-Agent') || undefined,\n severity: 'low',\n details: { reason: 'no_authentication' }\n });\n throw new Error('Authentication required');\n }\n \n return auth;\n}\n\n/**\n * Middleware function to require admin access\n */\nexport async function requireAdmin(request: Request): Promise<AuthContext> {\n const auth = await requireAuth(request);\n \n if (!auth.isAdmin) {\n logSecurityEvent({\n type: 'access_denied',\n userId: auth.user.id,\n ipAddress: getClientIPFromHeaders(request),\n userAgent: request.headers.get('User-Agent') || undefined,\n severity: 'medium',\n details: { reason: 'insufficient_privileges', requiredRole: 'admin' }\n });\n throw new Error('Admin access required');\n }\n \n return auth;\n}\n\n/**\n * Check if user has access to a specific organization\n */\nexport async function requireOrganizationAccess(\n request: Request, \n organizationId: string\n): Promise<AuthContext> {\n const auth = await requireAuth(request);\n \n if (auth.organizationId !== organizationId && !auth.isAdmin) {\n logSecurityEvent({\n type: 'access_denied',\n userId: auth.user.id,\n ipAddress: getClientIPFromHeaders(request),\n userAgent: request.headers.get('User-Agent') || undefined,\n severity: 'high',\n details: { \n reason: 'organization_access_violation',\n userOrganization: auth.organizationId,\n requestedOrganization: organizationId\n }\n });\n throw new Error('Access denied to this organization');\n }\n \n return auth;\n}\n\n/**\n * Generate CSRF token\n */\nexport function generateCSRFToken(): string {\n return crypto.randomUUID();\n}\n\n/**\n * Verify CSRF token\n */\nexport function verifyCSRFToken(request: Request, sessionToken: string): boolean {\n const submittedToken = request.headers.get('X-CSRF-Token') || \n request.headers.get('X-Requested-With');\n \n return submittedToken === sessionToken;\n}\n\n/**\n * Rate limiting - simple in-memory implementation\n * For production, use Redis or a proper rate limiting service\n */\nconst rateLimitStore = new Map<string, { count: number; lastReset: number }>();\n\nexport function checkRateLimit(\n identifier: string, \n maxRequests: number = 10, \n windowMs: number = 60000\n): boolean {\n const now = Date.now();\n const windowStart = now - windowMs;\n \n let entry = rateLimitStore.get(identifier);\n \n if (!entry || entry.lastReset < windowStart) {\n entry = { count: 0, lastReset: now };\n rateLimitStore.set(identifier, entry);\n }\n \n entry.count++;\n \n // Clean up old entries periodically\n if (Math.random() < 0.01) { // 1% chance\n cleanupRateLimit(windowStart);\n }\n \n const isAllowed = entry.count <= maxRequests;\n \n // Log rate limit violations\n if (!isAllowed) {\n logSecurityEvent({\n type: 'rate_limit',\n ipAddress: identifier.includes(':') ? identifier.split(':')[1] : identifier,\n severity: 'medium',\n details: { \n maxRequests, \n windowMs, \n currentCount: entry.count,\n identifier\n }\n });\n }\n \n return isAllowed;\n}\n\nfunction cleanupRateLimit(cutoff: number) {\n for (const [key, entry] of rateLimitStore.entries()) {\n if (entry.lastReset < cutoff) {\n rateLimitStore.delete(key);\n }\n }\n}\n\n/**\n * Parse cookies from cookie header\n */\nfunction parseCookies(cookieHeader: string): Record<string, string> {\n const cookies: Record<string, string> = {};\n \n cookieHeader.split(';').forEach(cookie => {\n const [name, ...rest] = cookie.trim().split('=');\n if (name && rest.length > 0) {\n cookies[name] = rest.join('=');\n }\n });\n \n return cookies;\n}\n\n/**\n * Create secure response with auth headers\n */\nexport function createAuthResponse(\n body: string | object, \n status: number = 200,\n additionalHeaders: Record<string, string> = {}\n): Response {\n const headers = {\n 'Content-Type': typeof body === 'string' ? 'text/plain' : 'application/json',\n 'X-Content-Type-Options': 'nosniff',\n 'X-Frame-Options': 'DENY',\n 'X-XSS-Protection': '1; mode=block',\n ...additionalHeaders\n };\n \n return new Response(\n typeof body === 'string' ? body : JSON.stringify(body),\n { status, headers }\n );\n}\n\n/**\n * Get client IP address for rate limiting\n */\nexport function getClientIP(request: Request): string {\n return getClientIPFromHeaders(request);\n}\n\n/**\n * Helper function to extract IP from headers\n */\nfunction getClientIPFromHeaders(request: Request): string {\n // Try various headers that might contain the real IP\n const forwardedFor = request.headers.get('X-Forwarded-For');\n const realIP = request.headers.get('X-Real-IP');\n const cfConnectingIP = request.headers.get('CF-Connecting-IP');\n \n if (cfConnectingIP) return cfConnectingIP;\n if (realIP) return realIP;\n if (forwardedFor) return forwardedFor.split(',')[0].trim();\n \n // Fallback to connection IP (may not be available in all environments)\n return request.headers.get('X-Client-IP') || 'unknown';\n}","usedDeprecatedRules":[]},{"filePath":"/home/tyler/apps/bct-whitelabel/src/lib/availabilityDisplay.ts","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/home/tyler/apps/bct-whitelabel/src/lib/backup.ts","messages":[{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":98,"column":40,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":98,"endColumn":43,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[2211,2214],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[2211,2214],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"no-useless-catch","severity":2,"message":"Unnecessary try/catch wrapper.","line":102,"column":9,"nodeType":"TryStatement","messageId":"unnecessaryCatch","endLine":116,"endColumn":10},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'totalSize' is assigned a value but never used. Allowed unused vars must match /^_/u.","line":112,"column":11,"nodeType":null,"messageId":"unusedVar","endLine":112,"endColumn":20},{"ruleId":"no-useless-catch","severity":2,"message":"Unnecessary try/catch wrapper.","line":249,"column":9,"nodeType":"TryStatement","messageId":"unnecessaryCatch","endLine":272,"endColumn":10},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":327,"column":20,"nodeType":null,"messageId":"unusedVar","endLine":327,"endColumn":25},{"ruleId":"no-empty","severity":1,"message":"Empty block statement.","line":327,"column":27,"nodeType":"BlockStatement","messageId":"unexpected","endLine":329,"endColumn":12,"suggestions":[{"messageId":"suggestComment","data":{"type":"block"},"fix":{"range":[8030,8042],"text":" /* empty */ "},"desc":"Add comment inside empty block statement."}]},{"ruleId":"no-empty","severity":1,"message":"Empty block statement.","line":385,"column":22,"nodeType":"BlockStatement","messageId":"unexpected","endLine":387,"endColumn":12,"suggestions":[{"messageId":"suggestComment","data":{"type":"block"},"fix":{"range":[9604,9616],"text":" /* empty */ "},"desc":"Add comment inside empty block statement."}]},{"ruleId":"no-empty","severity":1,"message":"Empty block statement.","line":387,"column":18,"nodeType":"BlockStatement","messageId":"unexpected","endLine":389,"endColumn":12,"suggestions":[{"messageId":"suggestComment","data":{"type":"block"},"fix":{"range":[9624,9636],"text":" /* empty */ "},"desc":"Add comment inside empty block statement."}]},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":390,"column":18,"nodeType":null,"messageId":"unusedVar","endLine":390,"endColumn":23},{"ruleId":"no-empty","severity":1,"message":"Empty block statement.","line":390,"column":25,"nodeType":"BlockStatement","messageId":"unexpected","endLine":392,"endColumn":10,"suggestions":[{"messageId":"suggestComment","data":{"type":"block"},"fix":{"range":[9663,9673],"text":" /* empty */ "},"desc":"Add comment inside empty block statement."}]},{"ruleId":"no-undef","severity":2,"message":"'TextEncoder' is not defined.","line":419,"column":25,"nodeType":"Identifier","messageId":"undef","endLine":419,"endColumn":36},{"ruleId":"no-undef","severity":2,"message":"'crypto' is not defined.","line":422,"column":42,"nodeType":"Identifier","messageId":"undef","endLine":422,"endColumn":48},{"ruleId":"no-undef","severity":2,"message":"'crypto' is not defined.","line":423,"column":32,"nodeType":"Identifier","messageId":"undef","endLine":423,"endColumn":38},{"ruleId":"no-empty","severity":1,"message":"Empty block statement.","line":448,"column":16,"nodeType":"BlockStatement","messageId":"unexpected","endLine":450,"endColumn":6,"suggestions":[{"messageId":"suggestComment","data":{"type":"block"},"fix":{"range":[11302,11308],"text":" /* empty */ "},"desc":"Add comment inside empty block statement."}]},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":470,"column":14,"nodeType":null,"messageId":"unusedVar","endLine":470,"endColumn":19},{"ruleId":"no-undef","severity":2,"message":"'NodeJS' is not defined.","line":481,"column":34,"nodeType":"Identifier","messageId":"undef","endLine":481,"endColumn":40},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'name' is assigned a value but never used. Allowed unused vars must match /^_/u.","line":506,"column":17,"nodeType":null,"messageId":"unusedVar","endLine":506,"endColumn":21},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":527,"column":16,"nodeType":null,"messageId":"unusedVar","endLine":527,"endColumn":21},{"ruleId":"no-empty","severity":1,"message":"Empty block statement.","line":527,"column":23,"nodeType":"BlockStatement","messageId":"unexpected","endLine":529,"endColumn":8,"suggestions":[{"messageId":"suggestComment","data":{"type":"block"},"fix":{"range":[13212,13220],"text":" /* empty */ "},"desc":"Add comment inside empty block statement."}]},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'data' is assigned a value but never used. Allowed unused vars must match /^_/u.","line":590,"column":15,"nodeType":null,"messageId":"unusedVar","endLine":590,"endColumn":19},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'data' is assigned a value but never used. Allowed unused vars must match /^_/u.","line":612,"column":17,"nodeType":null,"messageId":"unusedVar","endLine":612,"endColumn":21}],"suppressedMessages":[],"errorCount":6,"fatalErrorCount":0,"warningCount":15,"fixableErrorCount":0,"fixableWarningCount":0,"source":"import { createClient } from '@supabase/supabase-js';\nimport { logError, logUserActivity } from './logger';\nimport { captureException } from './sentry';\n\n// Environment variables\nconst SUPABASE_URL = process.env.SUPABASE_URL!;\nconst SUPABASE_SERVICE_KEY = process.env.SUPABASE_SERVICE_KEY!;\n\n// Create admin client for backup operations\nconst supabaseAdmin = createClient(SUPABASE_URL, SUPABASE_SERVICE_KEY);\n\n/**\n * Backup configuration\n */\ninterface BackupConfig {\n retention: {\n daily: number; // Days to keep daily backups\n weekly: number; // Weeks to keep weekly backups\n monthly: number; // Months to keep monthly backups\n };\n tables: string[]; // Tables to backup\n storage: {\n bucket: string; // Storage bucket name\n path: string; // Path prefix for backups\n };\n}\n\nconst DEFAULT_BACKUP_CONFIG: BackupConfig = {\n retention: {\n daily: 7,\n weekly: 4,\n monthly: 12\n },\n tables: [\n 'users',\n 'organizations', \n 'events',\n 'tickets',\n 'payouts',\n 'audit_logs'\n ],\n storage: {\n bucket: 'backups',\n path: 'database'\n }\n};\n\n/**\n * Backup metadata\n */\ninterface BackupMetadata {\n id: string;\n timestamp: string;\n type: 'daily' | 'weekly' | 'monthly';\n size: number;\n tables: string[];\n checksum: string;\n status: 'in_progress' | 'completed' | 'failed';\n error?: string;\n}\n\n/**\n * Database backup manager\n */\nexport class BackupManager {\n private config: BackupConfig;\n\n constructor(config: BackupConfig = DEFAULT_BACKUP_CONFIG) {\n this.config = config;\n }\n\n /**\n * Create a full database backup\n */\n async createBackup(type: 'daily' | 'weekly' | 'monthly' = 'daily'): Promise<BackupMetadata> {\n const backupId = `${type}-${Date.now()}`;\n const timestamp = new Date().toISOString();\n \n const metadata: BackupMetadata = {\n id: backupId,\n timestamp,\n type,\n size: 0,\n tables: this.config.tables,\n checksum: '',\n status: 'in_progress'\n };\n\n try {\n logUserActivity({\n action: 'backup_started',\n userId: 'system',\n resourceType: 'database',\n resourceId: backupId\n });\n\n // Create backup data\n const backupData: Record<string, any[]> = {};\n let totalSize = 0;\n\n for (const table of this.config.tables) {\n try {\n const { data, error } = await supabaseAdmin\n .from(table)\n .select('*');\n\n if (error) {\n throw new Error(`Failed to backup table ${table}: ${error.message}`);\n }\n\n backupData[table] = data || [];\n totalSize += JSON.stringify(data).length;\n } catch (error) {\n\n throw error;\n }\n }\n\n // Create backup file\n const backupContent = JSON.stringify({\n metadata: {\n id: backupId,\n timestamp,\n type,\n tables: this.config.tables,\n version: '1.0'\n },\n data: backupData\n }, null, 2);\n\n // Calculate checksum\n const checksum = await this.calculateChecksum(backupContent);\n metadata.checksum = checksum;\n metadata.size = backupContent.length;\n\n // Upload to storage\n const fileName = `${this.config.storage.path}/${backupId}.json`;\n \n const { error: uploadError } = await supabaseAdmin.storage\n .from(this.config.storage.bucket)\n .upload(fileName, backupContent, {\n contentType: 'application/json',\n cacheControl: '3600'\n });\n\n if (uploadError) {\n throw new Error(`Failed to upload backup: ${uploadError.message}`);\n }\n\n // Save metadata\n await this.saveBackupMetadata(metadata);\n\n metadata.status = 'completed';\n\n logUserActivity({\n action: 'backup_completed',\n userId: 'system',\n resourceType: 'database',\n resourceId: backupId,\n details: {\n size: metadata.size,\n tables: metadata.tables.length,\n checksum: metadata.checksum\n }\n });\n\n return metadata;\n\n } catch (error) {\n metadata.status = 'failed';\n metadata.error = error.message;\n\n logError(error, {\n requestId: backupId,\n additionalContext: {\n operation: 'database_backup',\n type,\n tables: this.config.tables\n }\n });\n\n captureException(error, {\n additionalData: {\n backupId,\n type,\n tables: this.config.tables\n }\n });\n\n throw error;\n }\n }\n\n /**\n * Restore database from backup\n */\n async restoreBackup(backupId: string, options: {\n tables?: string[];\n dryRun?: boolean;\n confirmRestore?: boolean;\n } = {}): Promise<void> {\n if (!options.confirmRestore) {\n throw new Error('Restore confirmation required. Set confirmRestore: true');\n }\n\n try {\n logUserActivity({\n action: 'restore_started',\n userId: 'system',\n resourceType: 'database',\n resourceId: backupId\n });\n\n // Download backup file\n const fileName = `${this.config.storage.path}/${backupId}.json`;\n \n const { data: backupFile, error: downloadError } = await supabaseAdmin.storage\n .from(this.config.storage.bucket)\n .download(fileName);\n\n if (downloadError) {\n throw new Error(`Failed to download backup: ${downloadError.message}`);\n }\n\n // Parse backup data\n const backupContent = await backupFile.text();\n const backup = JSON.parse(backupContent);\n\n // Verify checksum\n const expectedChecksum = await this.calculateChecksum(backupContent);\n if (backup.metadata.checksum !== expectedChecksum) {\n throw new Error('Backup file integrity check failed');\n }\n\n const tablesToRestore = options.tables || backup.metadata.tables;\n\n if (options.dryRun) {\n\n return;\n }\n\n // Restore each table\n for (const table of tablesToRestore) {\n if (!backup.data[table]) {\n\n continue;\n }\n\n try {\n // Clear existing data (be very careful here!)\n const { error: deleteError } = await supabaseAdmin\n .from(table)\n .delete()\n .neq('id', '00000000-0000-0000-0000-000000000000'); // Delete all rows\n\n if (deleteError) {\n throw new Error(`Failed to clear table ${table}: ${deleteError.message}`);\n }\n\n // Insert backup data\n const { error: insertError } = await supabaseAdmin\n .from(table)\n .insert(backup.data[table]);\n\n if (insertError) {\n throw new Error(`Failed to restore table ${table}: ${insertError.message}`);\n }\n\n } catch (error) {\n\n throw error;\n }\n }\n\n logUserActivity({\n action: 'restore_completed',\n userId: 'system',\n resourceType: 'database',\n resourceId: backupId,\n details: {\n tables: tablesToRestore\n }\n });\n\n } catch (error) {\n logError(error, {\n requestId: backupId,\n additionalContext: {\n operation: 'database_restore',\n tables: options.tables\n }\n });\n\n captureException(error, {\n additionalData: {\n backupId,\n tables: options.tables\n }\n });\n\n throw error;\n }\n }\n\n /**\n * List available backups\n */\n async listBackups(): Promise<BackupMetadata[]> {\n try {\n const { data: files, error } = await supabaseAdmin.storage\n .from(this.config.storage.bucket)\n .list(this.config.storage.path);\n\n if (error) {\n throw new Error(`Failed to list backups: ${error.message}`);\n }\n\n const backups: BackupMetadata[] = [];\n\n for (const file of files) {\n if (file.name.endsWith('.json')) {\n try {\n const metadata = await this.getBackupMetadata(file.name.replace('.json', ''));\n if (metadata) {\n backups.push(metadata);\n }\n } catch (error) {\n\n }\n }\n }\n\n return backups.sort((a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime());\n } catch (error) {\n logError(error, {\n additionalContext: {\n operation: 'list_backups'\n }\n });\n throw error;\n }\n }\n\n /**\n * Clean up old backups based on retention policy\n */\n async cleanupBackups(): Promise<void> {\n try {\n const backups = await this.listBackups();\n const now = new Date();\n const backupsToDelete: string[] = [];\n\n for (const backup of backups) {\n const backupDate = new Date(backup.timestamp);\n const ageInDays = (now.getTime() - backupDate.getTime()) / (1000 * 60 * 60 * 24);\n\n let shouldDelete = false;\n\n switch (backup.type) {\n case 'daily':\n shouldDelete = ageInDays > this.config.retention.daily;\n break;\n case 'weekly':\n shouldDelete = ageInDays > (this.config.retention.weekly * 7);\n break;\n case 'monthly':\n shouldDelete = ageInDays > (this.config.retention.monthly * 30);\n break;\n }\n\n if (shouldDelete) {\n backupsToDelete.push(backup.id);\n }\n }\n\n // Delete old backups\n for (const backupId of backupsToDelete) {\n try {\n const fileName = `${this.config.storage.path}/${backupId}.json`;\n \n const { error } = await supabaseAdmin.storage\n .from(this.config.storage.bucket)\n .remove([fileName]);\n\n if (error) {\n\n } else {\n\n }\n } catch (error) {\n\n }\n }\n\n logUserActivity({\n action: 'backup_cleanup',\n userId: 'system',\n resourceType: 'database',\n details: {\n deletedCount: backupsToDelete.length,\n backupIds: backupsToDelete\n }\n });\n\n } catch (error) {\n logError(error, {\n additionalContext: {\n operation: 'cleanup_backups'\n }\n });\n throw error;\n }\n }\n\n /**\n * Calculate file checksum\n */\n private async calculateChecksum(content: string): Promise<string> {\n const encoder = new TextEncoder();\n const data = encoder.encode(content);\n \n if (typeof crypto !== 'undefined' && crypto.subtle) {\n const hashBuffer = await crypto.subtle.digest('SHA-256', data);\n const hashArray = Array.from(new Uint8Array(hashBuffer));\n return hashArray.map(b => b.toString(16).padStart(2, '0')).join('');\n } else {\n // Fallback for Node.js environment\n const crypto = require('crypto');\n return crypto.createHash('sha256').update(content).digest('hex');\n }\n }\n\n /**\n * Save backup metadata\n */\n private async saveBackupMetadata(metadata: BackupMetadata): Promise<void> {\n // This would typically save to a metadata table\n // For now, we'll store it as a separate file\n const metadataFileName = `${this.config.storage.path}/metadata/${metadata.id}.json`;\n \n const { error } = await supabaseAdmin.storage\n .from(this.config.storage.bucket)\n .upload(metadataFileName, JSON.stringify(metadata, null, 2), {\n contentType: 'application/json',\n cacheControl: '3600'\n });\n\n if (error) {\n\n }\n }\n\n /**\n * Get backup metadata\n */\n private async getBackupMetadata(backupId: string): Promise<BackupMetadata | null> {\n try {\n const metadataFileName = `${this.config.storage.path}/metadata/${backupId}.json`;\n \n const { data, error } = await supabaseAdmin.storage\n .from(this.config.storage.bucket)\n .download(metadataFileName);\n\n if (error) {\n return null;\n }\n\n const content = await data.text();\n return JSON.parse(content);\n } catch (error) {\n return null;\n }\n }\n}\n\n/**\n * Scheduled backup runner\n */\nexport class BackupScheduler {\n private backupManager: BackupManager;\n private intervals: Map<string, NodeJS.Timeout> = new Map();\n\n constructor(backupManager: BackupManager) {\n this.backupManager = backupManager;\n }\n\n /**\n * Start automated backups\n */\n startScheduledBackups() {\n // Daily backups at 2 AM\n this.scheduleBackup('daily', '0 2 * * *', 'daily');\n \n // Weekly backups on Sunday at 3 AM\n this.scheduleBackup('weekly', '0 3 * * 0', 'weekly');\n \n // Monthly backups on the 1st at 4 AM\n this.scheduleBackup('monthly', '0 4 1 * *', 'monthly');\n\n }\n\n /**\n * Stop all scheduled backups\n */\n stopScheduledBackups() {\n for (const [name, interval] of this.intervals) {\n clearInterval(interval);\n\n }\n this.intervals.clear();\n }\n\n /**\n * Schedule a backup with cron-like syntax (simplified)\n */\n private scheduleBackup(name: string, cronExpression: string, type: 'daily' | 'weekly' | 'monthly') {\n // For production, use a proper cron library like node-cron\n // This is a simplified version for demonstration\n \n const runBackup = async () => {\n try {\n\n await this.backupManager.createBackup(type);\n\n // Cleanup old backups after successful backup\n await this.backupManager.cleanupBackups();\n } catch (error) {\n\n }\n };\n\n // For demonstration, we'll run backups based on simple intervals\n // In production, replace with proper cron scheduling\n let intervalMs: number;\n \n switch (type) {\n case 'daily':\n intervalMs = 24 * 60 * 60 * 1000; // 24 hours\n break;\n case 'weekly':\n intervalMs = 7 * 24 * 60 * 60 * 1000; // 7 days\n break;\n case 'monthly':\n intervalMs = 30 * 24 * 60 * 60 * 1000; // 30 days\n break;\n }\n\n const interval = setInterval(runBackup, intervalMs);\n this.intervals.set(name, interval);\n }\n}\n\n// Export singleton instances\nexport const backupManager = new BackupManager();\nexport const backupScheduler = new BackupScheduler(backupManager);\n\n// Disaster recovery utilities\nexport const DisasterRecovery = {\n /**\n * Create a point-in-time recovery backup\n */\n async createPointInTimeBackup(label: string): Promise<BackupMetadata> {\n const customConfig = {\n ...DEFAULT_BACKUP_CONFIG,\n storage: {\n bucket: 'backups',\n path: `disaster-recovery/${label}`\n }\n };\n\n const manager = new BackupManager(customConfig);\n return await manager.createBackup('daily');\n },\n\n /**\n * Verify system integrity after recovery\n */\n async verifySystemIntegrity(): Promise<{\n status: 'healthy' | 'degraded' | 'critical';\n checks: Array<{\n name: string;\n status: 'pass' | 'fail';\n message: string;\n }>;\n }> {\n const checks = [];\n\n // Check database connectivity\n try {\n const { data, error } = await supabaseAdmin\n .from('users')\n .select('count')\n .limit(1);\n \n checks.push({\n name: 'Database Connectivity',\n status: error ? 'fail' : 'pass',\n message: error ? error.message : 'Database is accessible'\n });\n } catch (error) {\n checks.push({\n name: 'Database Connectivity',\n status: 'fail',\n message: error.message\n });\n }\n\n // Check critical tables exist\n const criticalTables = ['users', 'organizations', 'events', 'tickets'];\n for (const table of criticalTables) {\n try {\n const { data, error } = await supabaseAdmin\n .from(table)\n .select('count')\n .limit(1);\n \n checks.push({\n name: `Table ${table}`,\n status: error ? 'fail' : 'pass',\n message: error ? error.message : `Table ${table} is accessible`\n });\n } catch (error) {\n checks.push({\n name: `Table ${table}`,\n status: 'fail',\n message: error.message\n });\n }\n }\n\n // Determine overall status\n const failedChecks = checks.filter(check => check.status === 'fail').length;\n const status = failedChecks === 0 ? 'healthy' : \n failedChecks <= 2 ? 'degraded' : 'critical';\n\n return { status, checks };\n }\n};","usedDeprecatedRules":[]},{"filePath":"/home/tyler/apps/bct-whitelabel/src/lib/canvas-image-generator.ts","messages":[{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":9,"column":10,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":9,"endColumn":13,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[277,280],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[277,280],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":20,"column":10,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":20,"endColumn":13,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[497,500],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[497,500],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"no-undef","severity":2,"message":"'HTMLCanvasElement' is not defined.","line":28,"column":19,"nodeType":"Identifier","messageId":"undef","endLine":28,"endColumn":36},{"ruleId":"no-undef","severity":2,"message":"'CanvasRenderingContext2D' is not defined.","line":29,"column":16,"nodeType":"Identifier","messageId":"undef","endLine":29,"endColumn":40},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":243,"column":14,"nodeType":null,"messageId":"unusedVar","endLine":243,"endColumn":19},{"ruleId":"no-empty","severity":1,"message":"Empty block statement.","line":243,"column":21,"nodeType":"BlockStatement","messageId":"unexpected","endLine":245,"endColumn":6,"suggestions":[{"messageId":"suggestComment","data":{"type":"block"},"fix":{"range":[6802,6808],"text":" /* empty */ "},"desc":"Add comment inside empty block statement."}]},{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":377,"column":44,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":377,"endColumn":47,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[10376,10379],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[10376,10379],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]}],"suppressedMessages":[],"errorCount":2,"fatalErrorCount":0,"warningCount":5,"fixableErrorCount":0,"fixableWarningCount":0,"source":"// Canvas-based image generation for marketing assets\n// Note: This would typically run server-side with node-canvas or similar\n// For browser-based generation, we'd use HTML5 Canvas API\n\ninterface ImageConfig {\n width: number;\n height: number;\n platform?: string;\n event: any;\n qrCode?: string;\n backgroundColor: string | string[];\n textColor: string;\n accentColor: string;\n}\n\ninterface FlyerConfig {\n width: number;\n height: number;\n style: 'modern' | 'classic' | 'minimal';\n event: any;\n qrCode?: string;\n backgroundColor: string | string[];\n textColor: string;\n accentColor: string;\n}\n\nclass CanvasImageGenerator {\n private canvas: HTMLCanvasElement | null = null;\n private ctx: CanvasRenderingContext2D | null = null;\n\n constructor() {\n // Initialize canvas if in browser environment\n if (typeof window !== 'undefined') {\n this.canvas = document.createElement('canvas');\n this.ctx = this.canvas.getContext('2d');\n }\n }\n\n /**\n * Generate social media image\n */\n async generateSocialImage(config: ImageConfig): Promise<string> {\n if (!this.canvas || !this.ctx) {\n // Return placeholder URL for SSR or fallback\n return this.generatePlaceholderImage(config);\n }\n\n this.canvas.width = config.width;\n this.canvas.height = config.height;\n\n // Clear canvas\n this.ctx.clearRect(0, 0, config.width, config.height);\n\n // Draw background\n await this.drawBackground(config);\n\n // Draw event title\n this.drawEventTitle(config);\n\n // Draw event details\n this.drawEventDetails(config);\n\n // Draw QR code if provided\n if (config.qrCode) {\n await this.drawQRCode(config);\n }\n\n // Draw organization logo if available\n if (config.event.organizations?.logo) {\n await this.drawLogo(config);\n }\n\n // Draw platform-specific elements\n this.drawPlatformElements(config);\n\n return this.canvas.toDataURL('image/png');\n }\n\n /**\n * Generate flyer/poster image\n */\n async generateFlyer(config: FlyerConfig): Promise<string> {\n if (!this.canvas || !this.ctx) {\n return this.generatePlaceholderImage(config);\n }\n\n this.canvas.width = config.width;\n this.canvas.height = config.height;\n\n this.ctx.clearRect(0, 0, config.width, config.height);\n\n // Draw flyer-specific layout based on style\n switch (config.style) {\n case 'modern':\n await this.drawModernFlyer(config);\n break;\n case 'classic':\n await this.drawClassicFlyer(config);\n break;\n case 'minimal':\n await this.drawMinimalFlyer(config);\n break;\n default:\n await this.drawModernFlyer(config);\n }\n\n return this.canvas.toDataURL('image/png');\n }\n\n /**\n * Draw gradient or solid background\n */\n private async drawBackground(config: ImageConfig) {\n if (!this.ctx) return;\n\n if (Array.isArray(config.backgroundColor)) {\n // Create gradient\n const gradient = this.ctx.createLinearGradient(0, 0, config.width, config.height);\n config.backgroundColor.forEach((color, index) => {\n gradient.addColorStop(index / (config.backgroundColor.length - 1), color);\n });\n this.ctx.fillStyle = gradient;\n } else {\n this.ctx.fillStyle = config.backgroundColor;\n }\n\n this.ctx.fillRect(0, 0, config.width, config.height);\n }\n\n /**\n * Draw event title with proper sizing\n */\n private drawEventTitle(config: ImageConfig) {\n if (!this.ctx) return;\n\n const title = config.event.title;\n const maxWidth = config.width * 0.8;\n \n // Calculate font size based on canvas size and text length\n let fontSize = Math.min(config.width / 15, 48);\n this.ctx.font = `bold ${fontSize}px Arial, sans-serif`;\n \n // Adjust font size if text is too wide\n while (this.ctx.measureText(title).width > maxWidth && fontSize > 20) {\n fontSize -= 2;\n this.ctx.font = `bold ${fontSize}px Arial, sans-serif`;\n }\n\n this.ctx.fillStyle = config.textColor;\n this.ctx.textAlign = 'center';\n this.ctx.textBaseline = 'middle';\n\n // Draw title with multiple lines if needed\n this.wrapText(title, config.width / 2, config.height * 0.25, maxWidth, fontSize * 1.2);\n }\n\n /**\n * Draw event details (date, time, venue)\n */\n private drawEventDetails(config: ImageConfig) {\n if (!this.ctx) return;\n\n const event = config.event;\n const eventDate = new Date(event.start_time);\n const formattedDate = eventDate.toLocaleDateString('en-US', {\n weekday: 'long',\n year: 'numeric',\n month: 'long',\n day: 'numeric'\n });\n const formattedTime = eventDate.toLocaleTimeString('en-US', {\n hour: 'numeric',\n minute: '2-digit',\n hour12: true\n });\n\n const fontSize = Math.min(config.width / 25, 24);\n this.ctx.font = `${fontSize}px Arial, sans-serif`;\n this.ctx.fillStyle = config.textColor;\n this.ctx.textAlign = 'center';\n\n const y = config.height * 0.5;\n const lineHeight = fontSize * 1.5;\n\n // Draw date\n this.ctx.fillText(`📅 ${formattedDate}`, config.width / 2, y);\n // Draw time\n this.ctx.fillText(`⏰ ${formattedTime}`, config.width / 2, y + lineHeight);\n // Draw venue\n this.ctx.fillText(`📍 ${event.venue}`, config.width / 2, y + lineHeight * 2);\n }\n\n /**\n * Draw QR code\n */\n private async drawQRCode(config: ImageConfig) {\n if (!this.ctx || !config.qrCode) return;\n\n const qrSize = Math.min(config.width * 0.2, 150);\n const qrX = config.width - qrSize - 20;\n const qrY = config.height - qrSize - 20;\n\n // Create image from QR code data URL\n const qrImage = new Image();\n await new Promise((resolve) => {\n qrImage.onload = resolve;\n qrImage.src = config.qrCode!;\n });\n\n // Draw white background for QR code\n this.ctx.fillStyle = '#FFFFFF';\n this.ctx.fillRect(qrX - 10, qrY - 10, qrSize + 20, qrSize + 20);\n\n // Draw QR code\n this.ctx.drawImage(qrImage, qrX, qrY, qrSize, qrSize);\n\n // Add \"Scan for Tickets\" text\n this.ctx.fillStyle = config.textColor;\n this.ctx.font = `${Math.min(config.width / 40, 14)}px Arial, sans-serif`;\n this.ctx.textAlign = 'center';\n this.ctx.fillText('Scan for Tickets', qrX + qrSize / 2, qrY + qrSize + 25);\n }\n\n /**\n * Draw organization logo\n */\n private async drawLogo(config: ImageConfig) {\n if (!this.ctx || !config.event.organizations?.logo) return;\n\n const logoSize = Math.min(config.width * 0.15, 80);\n const logoX = 20;\n const logoY = 20;\n\n try {\n const logoImage = new Image();\n await new Promise((resolve, reject) => {\n logoImage.onload = resolve;\n logoImage.onerror = reject;\n logoImage.src = config.event.organizations.logo;\n });\n\n this.ctx.drawImage(logoImage, logoX, logoY, logoSize, logoSize);\n } catch (error) {\n\n }\n }\n\n /**\n * Draw platform-specific elements\n */\n private drawPlatformElements(config: ImageConfig) {\n if (!this.ctx) return;\n\n // Add platform-specific call-to-action\n const cta = this.getPlatformCTA(config.platform);\n if (cta) {\n const fontSize = Math.min(config.width / 30, 20);\n this.ctx.font = `bold ${fontSize}px Arial, sans-serif`;\n this.ctx.fillStyle = config.accentColor;\n this.ctx.textAlign = 'center';\n this.ctx.fillText(cta, config.width / 2, config.height * 0.85);\n }\n }\n\n /**\n * Draw modern style flyer\n */\n private async drawModernFlyer(config: FlyerConfig) {\n // Modern flyer with geometric shapes and bold typography\n await this.drawBackground(config);\n \n // Add geometric accent shapes\n if (this.ctx) {\n this.ctx.fillStyle = config.accentColor + '20'; // Semi-transparent\n this.ctx.beginPath();\n this.ctx.arc(config.width * 0.1, config.height * 0.1, 100, 0, 2 * Math.PI);\n this.ctx.fill();\n \n this.ctx.beginPath();\n this.ctx.arc(config.width * 0.9, config.height * 0.9, 150, 0, 2 * Math.PI);\n this.ctx.fill();\n }\n\n this.drawEventTitle(config);\n this.drawEventDetails(config);\n \n if (config.qrCode) {\n await this.drawQRCode(config);\n }\n }\n\n /**\n * Draw classic style flyer\n */\n private async drawClassicFlyer(config: FlyerConfig) {\n // Classic flyer with elegant borders and traditional layout\n await this.drawBackground(config);\n \n // Add decorative border\n if (this.ctx) {\n this.ctx.strokeStyle = config.textColor;\n this.ctx.lineWidth = 3;\n this.ctx.strokeRect(20, 20, config.width - 40, config.height - 40);\n }\n\n this.drawEventTitle(config);\n this.drawEventDetails(config);\n \n if (config.qrCode) {\n await this.drawQRCode(config);\n }\n }\n\n /**\n * Draw minimal style flyer\n */\n private async drawMinimalFlyer(config: FlyerConfig) {\n // Minimal flyer with lots of whitespace and clean typography\n if (this.ctx) {\n this.ctx.fillStyle = '#FFFFFF';\n this.ctx.fillRect(0, 0, config.width, config.height);\n }\n\n // Override text color for minimal style\n const minimalConfig = { ...config, textColor: '#333333' };\n this.drawEventTitle(minimalConfig);\n this.drawEventDetails(minimalConfig);\n \n if (config.qrCode) {\n await this.drawQRCode(minimalConfig);\n }\n }\n\n /**\n * Wrap text to multiple lines\n */\n private wrapText(text: string, x: number, y: number, maxWidth: number, lineHeight: number) {\n if (!this.ctx) return;\n\n const words = text.split(' ');\n let line = '';\n let currentY = y;\n\n for (let n = 0; n < words.length; n++) {\n const testLine = line + words[n] + ' ';\n const metrics = this.ctx.measureText(testLine);\n const testWidth = metrics.width;\n\n if (testWidth > maxWidth && n > 0) {\n this.ctx.fillText(line, x, currentY);\n line = words[n] + ' ';\n currentY += lineHeight;\n } else {\n line = testLine;\n }\n }\n this.ctx.fillText(line, x, currentY);\n }\n\n /**\n * Get platform-specific call-to-action text\n */\n private getPlatformCTA(platform?: string): string {\n const ctas = {\n facebook: 'Get Your Tickets Now!',\n instagram: 'Link in Bio for Tickets',\n twitter: 'Click Link for Tickets',\n linkedin: 'Register Today'\n };\n\n return ctas[platform || 'facebook'] || 'Get Tickets';\n }\n\n /**\n * Generate placeholder image URL for SSR/fallback\n */\n private generatePlaceholderImage(config: any): string {\n // Return a placeholder service URL or data URI\n const width = config.width || 1200;\n const height = config.height || 630;\n const title = encodeURIComponent(config.event?.title || 'Event');\n \n // Using a placeholder service (you could replace with your own)\n return `https://via.placeholder.com/${width}x${height}/1877F2/FFFFFF?text=${title}`;\n }\n}\n\nexport const canvasImageGenerator = new CanvasImageGenerator();","usedDeprecatedRules":[]},{"filePath":"/home/tyler/apps/bct-whitelabel/src/lib/codereadr-api.ts","messages":[{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":68,"column":86,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":68,"endColumn":89,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[1342,1345],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[1342,1345],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":68,"column":100,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":68,"endColumn":103,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[1356,1359],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[1356,1359],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"no-undef","severity":2,"message":"'RequestInit' is not defined.","line":84,"column":20,"nodeType":"Identifier","messageId":"undef","endLine":84,"endColumn":31},{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":105,"column":61,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":105,"endColumn":64,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[2386,2389],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[2386,2389],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":114,"column":14,"nodeType":null,"messageId":"unusedVar","endLine":114,"endColumn":19},{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":180,"column":89,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":180,"endColumn":92,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[4679,4682],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[4679,4682],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":189,"column":92,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":189,"endColumn":95,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[4936,4939],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[4936,4939],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":238,"column":19,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":238,"endColumn":22,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[6489,6492],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[6489,6492],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":264,"column":69,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":264,"endColumn":72,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[7321,7324],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[7321,7324],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":283,"column":49,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":283,"endColumn":52,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[7878,7881],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[7878,7881],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":283,"column":63,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":283,"endColumn":66,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[7892,7895],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[7892,7895],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":292,"column":31,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":292,"endColumn":34,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[8140,8143],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[8140,8143],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":298,"column":105,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":298,"endColumn":108,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[8399,8402],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[8399,8402],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":306,"column":33,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":306,"endColumn":36,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[8569,8572],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[8569,8572],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":311,"column":81,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":311,"endColumn":84,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[8742,8745],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[8742,8745],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]}],"suppressedMessages":[],"errorCount":1,"fatalErrorCount":0,"warningCount":14,"fixableErrorCount":0,"fixableWarningCount":0,"source":"/**\n * CodeREADr API Client\n * https://secure.codereadr.com/apidocs/README.md\n */\n\nconst API_KEY = '3bcb2250e2c9cf4adf4e807f912f907e';\nconst API_BASE_URL = 'https://secure.codereadr.com/api/';\n\nexport interface CodereadrUser {\n id: string;\n username: string;\n email: string;\n status: 'active' | 'inactive';\n}\n\nexport interface CodereadrDatabase {\n id: string;\n name: string;\n status: 'active' | 'inactive';\n records_count: number;\n}\n\nexport interface CodereadrService {\n id: string;\n name: string;\n database_id: string;\n user_id: string;\n status: 'active' | 'inactive';\n created_at: string;\n}\n\nexport interface CodereadrScan {\n id: string;\n service_id: string;\n user_id: string;\n value: string;\n timestamp: string;\n response: string;\n device_id: string;\n location?: {\n latitude: number;\n longitude: number;\n };\n}\n\nexport interface CodereadrScanResponse {\n valid: boolean;\n message: string;\n response_id: string;\n scan_id: string;\n}\n\nexport interface CodereadrExportTemplate {\n id: string;\n name: string;\n format: 'csv' | 'xml' | 'json';\n}\n\nclass CodereadrApi {\n private apiKey: string;\n private baseUrl: string;\n\n constructor(apiKey: string = API_KEY) {\n this.apiKey = apiKey;\n this.baseUrl = API_BASE_URL;\n }\n\n private async makeRequest(endpoint: string, method: 'GET' | 'POST' = 'GET', data?: any): Promise<any> {\n const url = `${this.baseUrl}${endpoint}`;\n const params = new URLSearchParams();\n \n // Add API key to all requests\n params.append('api_key', this.apiKey);\n \n // Add data for POST requests\n if (data) {\n Object.keys(data).forEach(key => {\n if (data[key] !== undefined && data[key] !== null) {\n params.append(key, data[key].toString());\n }\n });\n }\n\n const options: RequestInit = {\n method,\n headers: {\n 'Content-Type': 'application/x-www-form-urlencoded',\n 'User-Agent': 'BCT-WhiteLabel/1.0'\n }\n };\n\n if (method === 'POST') {\n options.body = params;\n } else {\n // GET request - append params to URL\n const finalUrl = `${url}?${params.toString()}`;\n const response = await fetch(finalUrl, options);\n return this.handleResponse(response);\n }\n\n const response = await fetch(url, options);\n return this.handleResponse(response);\n }\n\n private async handleResponse(response: Response): Promise<any> {\n if (!response.ok) {\n throw new Error(`API Error: ${response.status} ${response.statusText}`);\n }\n\n const text = await response.text();\n \n try {\n return JSON.parse(text);\n } catch (error) {\n // Some endpoints return CSV or plain text\n return text;\n }\n }\n\n // Users API\n async createUser(username: string, email: string, password: string): Promise<CodereadrUser> {\n const response = await this.makeRequest('users/create', 'POST', {\n username,\n email,\n password\n });\n return response;\n }\n\n async getUsers(): Promise<CodereadrUser[]> {\n const response = await this.makeRequest('users');\n return response;\n }\n\n async getUserById(userId: string): Promise<CodereadrUser> {\n const response = await this.makeRequest(`users/${userId}`);\n return response;\n }\n\n async updateUser(userId: string, data: Partial<CodereadrUser>): Promise<CodereadrUser> {\n const response = await this.makeRequest(`users/${userId}/update`, 'POST', data);\n return response;\n }\n\n async deleteUser(userId: string): Promise<boolean> {\n const response = await this.makeRequest(`users/${userId}/delete`, 'POST');\n return response.success;\n }\n\n // Databases API\n async createDatabase(name: string, description?: string): Promise<CodereadrDatabase> {\n const response = await this.makeRequest('databases/create', 'POST', {\n name,\n description\n });\n return response;\n }\n\n async getDatabases(): Promise<CodereadrDatabase[]> {\n const response = await this.makeRequest('databases');\n return response;\n }\n\n async getDatabaseById(databaseId: string): Promise<CodereadrDatabase> {\n const response = await this.makeRequest(`databases/${databaseId}`);\n return response;\n }\n\n async updateDatabase(databaseId: string, data: Partial<CodereadrDatabase>): Promise<CodereadrDatabase> {\n const response = await this.makeRequest(`databases/${databaseId}/update`, 'POST', data);\n return response;\n }\n\n async deleteDatabase(databaseId: string): Promise<boolean> {\n const response = await this.makeRequest(`databases/${databaseId}/delete`, 'POST');\n return response.success;\n }\n\n // Database Records API\n async addDatabaseRecord(databaseId: string, value: string, response: string): Promise<any> {\n const result = await this.makeRequest('database/add', 'POST', {\n database_id: databaseId,\n value,\n response\n });\n return result;\n }\n\n async updateDatabaseRecord(databaseId: string, value: string, response: string): Promise<any> {\n const result = await this.makeRequest('database/update', 'POST', {\n database_id: databaseId,\n value,\n response\n });\n return result;\n }\n\n async deleteDatabaseRecord(databaseId: string, value: string): Promise<boolean> {\n const response = await this.makeRequest('database/delete', 'POST', {\n database_id: databaseId,\n value\n });\n return response.success;\n }\n\n // Services API\n async createService(name: string, databaseId: string, userId: string): Promise<CodereadrService> {\n const response = await this.makeRequest('services/create', 'POST', {\n name,\n database_id: databaseId,\n user_id: userId\n });\n return response;\n }\n\n async getServices(): Promise<CodereadrService[]> {\n const response = await this.makeRequest('services');\n return response;\n }\n\n async getServiceById(serviceId: string): Promise<CodereadrService> {\n const response = await this.makeRequest(`services/${serviceId}`);\n return response;\n }\n\n async updateService(serviceId: string, data: Partial<CodereadrService>): Promise<CodereadrService> {\n const response = await this.makeRequest(`services/${serviceId}/update`, 'POST', data);\n return response;\n }\n\n async deleteService(serviceId: string): Promise<boolean> {\n const response = await this.makeRequest(`services/${serviceId}/delete`, 'POST');\n return response.success;\n }\n\n // Scans API\n async getScans(serviceId?: string, userId?: string, limit?: number, offset?: number): Promise<CodereadrScan[]> {\n const params: any = {};\n \n if (serviceId) params.service_id = serviceId;\n if (userId) params.user_id = userId;\n if (limit) params.limit = limit;\n if (offset) params.offset = offset;\n\n const response = await this.makeRequest('scans', 'GET', params);\n return response;\n }\n\n async getScanById(scanId: string): Promise<CodereadrScan> {\n const response = await this.makeRequest(`scans/${scanId}`);\n return response;\n }\n\n // Export scans to CSV/XML/JSON\n async exportScans(templateId: string, format: 'csv' | 'xml' | 'json' = 'csv'): Promise<string> {\n const response = await this.makeRequest('scans/export', 'GET', {\n template: templateId,\n return_type: format\n });\n return response;\n }\n\n // Validate a barcode against a database\n async validateBarcode(databaseId: string, value: string): Promise<any> {\n const response = await this.makeRequest('database/lookup', 'POST', {\n database_id: databaseId,\n value\n });\n return response;\n }\n\n // Simulate a scan (for testing/integration)\n async simulateScan(serviceId: string, userId: string, value: string): Promise<CodereadrScanResponse> {\n const response = await this.makeRequest('scans/simulate', 'POST', {\n service_id: serviceId,\n user_id: userId,\n value\n });\n return response;\n }\n\n // Kiosk mode configuration\n async setKioskMode(serviceId: string, config: any): Promise<any> {\n const response = await this.makeRequest('services/kiosk', 'POST', {\n service_id: serviceId,\n kiosk_mode_config: JSON.stringify(config)\n });\n return response;\n }\n\n // Get device information\n async getDevices(): Promise<any[]> {\n const response = await this.makeRequest('devices');\n return response;\n }\n\n // Questions API (for collecting additional data after scans)\n async createQuestion(text: string, type: 'text' | 'number' | 'select' | 'checkbox' | 'date'): Promise<any> {\n const response = await this.makeRequest('questions/create', 'POST', {\n text,\n type\n });\n return response;\n }\n\n async getQuestions(): Promise<any[]> {\n const response = await this.makeRequest('questions');\n return response;\n }\n\n async assignQuestionToService(serviceId: string, questionId: string): Promise<any> {\n const response = await this.makeRequest('services/questions/assign', 'POST', {\n service_id: serviceId,\n question_id: questionId\n });\n return response;\n }\n}\n\nexport default CodereadrApi;\nexport { CodereadrApi };","usedDeprecatedRules":[]},{"filePath":"/home/tyler/apps/bct-whitelabel/src/lib/database.types.ts","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/home/tyler/apps/bct-whitelabel/src/lib/email-template-generator.ts","messages":[{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":21,"column":18,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":21,"endColumn":21,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[376,379],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[376,379],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":27,"column":20,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":27,"endColumn":23,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[506,509],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[506,509],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":213,"column":37,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":213,"endColumn":40,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[5163,5166],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[5163,5166],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":400,"column":37,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":400,"endColumn":40,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[11913,11916],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[11913,11916],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":434,"column":48,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":434,"endColumn":51,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[12942,12945],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[12942,12945],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":5,"fixableErrorCount":0,"fixableWarningCount":0,"source":"import { qrGenerator } from './qr-generator';\n\ninterface EmailTemplate {\n title: string;\n subject: string;\n previewText: string;\n html: string;\n text: string;\n ctaText: string;\n}\n\ninterface EventData {\n id: string;\n title: string;\n description: string;\n venue: string;\n start_time: string;\n end_time: string;\n slug: string;\n image_url?: string;\n social_links?: any;\n website_url?: string;\n contact_email?: string;\n organizations: {\n name: string;\n logo?: string;\n social_links?: any;\n website_url?: string;\n contact_email?: string;\n };\n}\n\nclass EmailTemplateGenerator {\n /**\n * Generate email templates for the event\n */\n async generateTemplates(event: EventData): Promise<EmailTemplate[]> {\n const ticketUrl = `${import.meta.env.PUBLIC_SITE_URL || 'https://portal.blackcanyontickets.com'}/e/${event.slug}`;\n \n // Generate QR code for email\n const qrCode = await qrGenerator.generateEventQR(event.slug, {\n size: 200,\n color: { dark: '#000000', light: '#FFFFFF' }\n });\n\n const templates: EmailTemplate[] = [];\n\n // Primary invitation template\n templates.push(await this.generateInvitationTemplate(event, ticketUrl, qrCode.dataUrl));\n \n // Reminder template\n templates.push(await this.generateReminderTemplate(event, ticketUrl, qrCode.dataUrl));\n \n // Last chance template\n templates.push(await this.generateLastChanceTemplate(event, ticketUrl, qrCode.dataUrl));\n\n return templates;\n }\n\n /**\n * Generate primary invitation email template\n */\n private async generateInvitationTemplate(event: EventData, ticketUrl: string, qrCodeDataUrl: string): Promise<EmailTemplate> {\n const eventDate = new Date(event.start_time);\n const formattedDate = eventDate.toLocaleDateString('en-US', {\n weekday: 'long',\n year: 'numeric',\n month: 'long',\n day: 'numeric'\n });\n const formattedTime = eventDate.toLocaleTimeString('en-US', {\n hour: 'numeric',\n minute: '2-digit',\n hour12: true\n });\n\n const subject = `You're Invited: ${event.title}`;\n const previewText = `Join us on ${formattedDate} at ${event.venue}`;\n const ctaText = 'Get Your Tickets';\n\n const html = this.generateEmailHTML({\n event,\n ticketUrl,\n qrCodeDataUrl,\n formattedDate,\n formattedTime,\n subject,\n ctaText,\n template: 'invitation'\n });\n\n const text = this.generateEmailText({\n event,\n ticketUrl,\n formattedDate,\n formattedTime,\n template: 'invitation'\n });\n\n return {\n title: 'Event Invitation Email',\n subject,\n previewText,\n html,\n text,\n ctaText\n };\n }\n\n /**\n * Generate reminder email template\n */\n private async generateReminderTemplate(event: EventData, ticketUrl: string, qrCodeDataUrl: string): Promise<EmailTemplate> {\n const eventDate = new Date(event.start_time);\n const formattedDate = eventDate.toLocaleDateString('en-US', {\n weekday: 'long',\n year: 'numeric',\n month: 'long',\n day: 'numeric'\n });\n const formattedTime = eventDate.toLocaleTimeString('en-US', {\n hour: 'numeric',\n minute: '2-digit',\n hour12: true\n });\n\n const subject = `Don't Forget: ${event.title} is Coming Up!`;\n const previewText = `Event reminder for ${formattedDate}`;\n const ctaText = 'Secure Your Spot';\n\n const html = this.generateEmailHTML({\n event,\n ticketUrl,\n qrCodeDataUrl,\n formattedDate,\n formattedTime,\n subject,\n ctaText,\n template: 'reminder'\n });\n\n const text = this.generateEmailText({\n event,\n ticketUrl,\n formattedDate,\n formattedTime,\n template: 'reminder'\n });\n\n return {\n title: 'Event Reminder Email',\n subject,\n previewText,\n html,\n text,\n ctaText\n };\n }\n\n /**\n * Generate last chance email template\n */\n private async generateLastChanceTemplate(event: EventData, ticketUrl: string, qrCodeDataUrl: string): Promise<EmailTemplate> {\n const eventDate = new Date(event.start_time);\n const formattedDate = eventDate.toLocaleDateString('en-US', {\n weekday: 'long',\n year: 'numeric',\n month: 'long',\n day: 'numeric'\n });\n const formattedTime = eventDate.toLocaleTimeString('en-US', {\n hour: 'numeric',\n minute: '2-digit',\n hour12: true\n });\n\n const subject = `⏰ Last Chance: ${event.title} - Limited Tickets Remaining`;\n const previewText = `Final opportunity to secure your tickets`;\n const ctaText = 'Get Tickets Now';\n\n const html = this.generateEmailHTML({\n event,\n ticketUrl,\n qrCodeDataUrl,\n formattedDate,\n formattedTime,\n subject,\n ctaText,\n template: 'last_chance'\n });\n\n const text = this.generateEmailText({\n event,\n ticketUrl,\n formattedDate,\n formattedTime,\n template: 'last_chance'\n });\n\n return {\n title: 'Last Chance Email',\n subject,\n previewText,\n html,\n text,\n ctaText\n };\n }\n\n /**\n * Generate HTML email content\n */\n private generateEmailHTML(params: any): string {\n const { event, ticketUrl, qrCodeDataUrl, formattedDate, formattedTime, subject, ctaText, template } = params;\n \n const logoImg = event.organizations.logo ? \n `<img src=\"${event.organizations.logo}\" alt=\"${event.organizations.name}\" style=\"height: 60px; width: auto;\">` : \n `<h2 style=\"margin: 0; color: #1877F2;\">${event.organizations.name}</h2>`;\n\n const eventImg = event.image_url ? \n `<img src=\"${event.image_url}\" alt=\"${event.title}\" style=\"width: 100%; max-width: 600px; height: auto; border-radius: 12px; margin: 20px 0;\">` : \n '';\n\n const urgencyText = template === 'last_chance' ? \n `<div style=\"background: #FF6B35; color: white; padding: 15px; border-radius: 8px; margin: 20px 0; text-align: center; font-weight: bold;\">\n ⏰ Limited Tickets Available - Don't Miss Out!\n </div>` : '';\n\n const socialLinks = this.generateSocialLinksHTML(event.social_links || event.organizations.social_links);\n\n return `\n<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <title>${subject}</title>\n <style>\n @import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;600;700&display=swap');\n \n body {\n font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;\n line-height: 1.6;\n color: #333333;\n margin: 0;\n padding: 0;\n background-color: #f8fafc;\n }\n \n .email-container {\n max-width: 600px;\n margin: 0 auto;\n background: white;\n border-radius: 16px;\n overflow: hidden;\n box-shadow: 0 10px 25px rgba(0,0,0,0.1);\n }\n \n .header {\n background: linear-gradient(135deg, #1877F2 0%, #4267B2 100%);\n color: white;\n padding: 30px;\n text-align: center;\n }\n \n .content {\n padding: 40px 30px;\n }\n \n .event-details {\n background: #f8fafc;\n border-radius: 12px;\n padding: 25px;\n margin: 25px 0;\n border-left: 4px solid #1877F2;\n }\n \n .cta-button {\n display: inline-block;\n background: linear-gradient(135deg, #1877F2 0%, #4267B2 100%);\n color: white;\n padding: 15px 30px;\n text-decoration: none;\n border-radius: 8px;\n font-weight: 600;\n font-size: 16px;\n margin: 20px 0;\n transition: transform 0.2s;\n }\n \n .cta-button:hover {\n transform: translateY(-2px);\n }\n \n .qr-section {\n text-align: center;\n margin: 30px 0;\n padding: 20px;\n background: #f8fafc;\n border-radius: 12px;\n }\n \n .footer {\n background: #f8fafc;\n padding: 30px;\n text-align: center;\n font-size: 14px;\n color: #666;\n }\n \n .social-links {\n margin: 20px 0;\n }\n \n .social-links a {\n color: #1877F2;\n text-decoration: none;\n margin: 0 10px;\n }\n \n @media (max-width: 600px) {\n .email-container {\n margin: 10px;\n border-radius: 12px;\n }\n \n .content {\n padding: 25px 20px;\n }\n \n .header {\n padding: 25px 20px;\n }\n }\n </style>\n</head>\n<body>\n <div class=\"email-container\">\n <div class=\"header\">\n ${logoImg}\n <h1 style=\"margin: 15px 0 0 0; font-weight: 300; font-size: 28px;\">${event.title}</h1>\n </div>\n \n <div class=\"content\">\n ${urgencyText}\n \n <p style=\"font-size: 18px; color: #1877F2; font-weight: 600;\">\n ${template === 'invitation' ? \"You're cordially invited to join us for an unforgettable experience!\" : \n template === 'reminder' ? \"Just a friendly reminder about your upcoming event!\" :\n \"This is your final opportunity to secure your tickets!\"}\n </p>\n \n ${eventImg}\n \n ${event.description ? `<p style=\"font-size: 16px; line-height: 1.7; margin: 20px 0;\">${event.description}</p>` : ''}\n \n <div class=\"event-details\">\n <h3 style=\"margin: 0 0 15px 0; color: #1877F2;\">Event Details</h3>\n <p style=\"margin: 8px 0; font-size: 16px;\"><strong>📅 Date:</strong> ${formattedDate}</p>\n <p style=\"margin: 8px 0; font-size: 16px;\"><strong>⏰ Time:</strong> ${formattedTime}</p>\n <p style=\"margin: 8px 0; font-size: 16px;\"><strong>📍 Venue:</strong> ${event.venue}</p>\n </div>\n \n <div style=\"text-align: center; margin: 30px 0;\">\n <a href=\"${ticketUrl}\" class=\"cta-button\">${ctaText}</a>\n </div>\n \n <div class=\"qr-section\">\n <h4 style=\"margin: 0 0 15px 0; color: #333;\">Quick Access</h4>\n <img src=\"${qrCodeDataUrl}\" alt=\"QR Code for ${event.title}\" style=\"width: 150px; height: 150px;\">\n <p style=\"margin: 10px 0 0 0; font-size: 14px; color: #666;\">Scan with your phone to get tickets instantly</p>\n </div>\n \n ${template === 'last_chance' ? `\n <div style=\"background: #fff3cd; border: 1px solid #ffeaa7; border-radius: 8px; padding: 20px; margin: 20px 0;\">\n <h4 style=\"margin: 0 0 10px 0; color: #856404;\">⚠️ Limited Time Offer</h4>\n <p style=\"margin: 0; color: #856404;\">Tickets are selling fast! Don't wait - secure your spot today.</p>\n </div>\n ` : ''}\n </div>\n \n <div class=\"footer\">\n <p>Questions? Contact us at ${event.contact_email || event.organizations.contact_email || 'hello@blackcanyontickets.com'}</p>\n \n ${socialLinks}\n \n <p style=\"margin: 20px 0 0 0; font-size: 12px; color: #999;\">\n This email was sent by ${event.organizations.name}. \n ${event.organizations.website_url ? `Visit our website: <a href=\"${event.organizations.website_url}\" style=\"color: #1877F2;\">${event.organizations.website_url}</a>` : ''}\n </p>\n </div>\n </div>\n</body>\n</html>`;\n }\n\n /**\n * Generate plain text email content\n */\n private generateEmailText(params: any): string {\n const { event, ticketUrl, formattedDate, formattedTime, template } = params;\n \n const urgencyText = template === 'last_chance' ? \n '⏰ LIMITED TICKETS AVAILABLE - DON\\'T MISS OUT!\\n\\n' : '';\n\n return `\n${event.title}\n${event.organizations.name}\n\n${urgencyText}${template === 'invitation' ? \"You're cordially invited to join us for an unforgettable experience!\" : \n template === 'reminder' ? \"Just a friendly reminder about your upcoming event!\" :\n \"This is your final opportunity to secure your tickets!\"}\n\nEVENT DETAILS:\n📅 Date: ${formattedDate}\n⏰ Time: ${formattedTime}\n📍 Venue: ${event.venue}\n\n${event.description ? `${event.description}\\n\\n` : ''}\n\nGet your tickets now: ${ticketUrl}\n\nQuestions? Contact us at ${event.contact_email || event.organizations.contact_email || 'hello@blackcanyontickets.com'}\n\n--\n${event.organizations.name}\n${event.organizations.website_url || ''}\n`.trim();\n }\n\n /**\n * Generate social media links HTML\n */\n private generateSocialLinksHTML(socialLinks: any): string {\n if (!socialLinks) return '';\n\n const links: string[] = [];\n \n if (socialLinks.facebook) {\n links.push(`<a href=\"${socialLinks.facebook}\">Facebook</a>`);\n }\n if (socialLinks.instagram) {\n links.push(`<a href=\"${socialLinks.instagram}\">Instagram</a>`);\n }\n if (socialLinks.twitter) {\n links.push(`<a href=\"${socialLinks.twitter}\">Twitter</a>`);\n }\n if (socialLinks.linkedin) {\n links.push(`<a href=\"${socialLinks.linkedin}\">LinkedIn</a>`);\n }\n\n if (links.length === 0) return '';\n\n return `\n <div class=\"social-links\">\n <p style=\"margin: 0 0 10px 0;\">Follow us:</p>\n ${links.join(' | ')}\n </div>`;\n }\n\n /**\n * Generate email subject line variations\n */\n generateSubjectVariations(event: EventData): string[] {\n return [\n `You're Invited: ${event.title}`,\n `🎉 Join us for ${event.title}`,\n `Exclusive Event: ${event.title}`,\n `Save the Date: ${event.title}`,\n `${event.title} - Tickets Available Now`,\n `Experience ${event.title} at ${event.venue}`,\n `Don't Miss: ${event.title}`\n ];\n }\n}\n\nexport const emailTemplateGenerator = new EmailTemplateGenerator();","usedDeprecatedRules":[]},{"filePath":"/home/tyler/apps/bct-whitelabel/src/lib/email.ts","messages":[{"ruleId":"no-empty","severity":1,"message":"Empty block statement.","line":17,"column":34,"nodeType":"BlockStatement","messageId":"unexpected","endLine":19,"endColumn":2,"suggestions":[{"messageId":"suggestComment","data":{"type":"block"},"fix":{"range":[546,548],"text":" /* empty */ "},"desc":"Add comment inside empty block statement."}]},{"ruleId":"no-useless-catch","severity":2,"message":"Unnecessary try/catch wrapper.","line":96,"column":3,"nodeType":"TryStatement","messageId":"unnecessaryCatch","endLine":113,"endColumn":4},{"ruleId":"no-useless-catch","severity":2,"message":"Unnecessary try/catch wrapper.","line":448,"column":3,"nodeType":"TryStatement","messageId":"unnecessaryCatch","endLine":486,"endColumn":4},{"ruleId":"no-useless-catch","severity":2,"message":"Unnecessary try/catch wrapper.","line":498,"column":3,"nodeType":"TryStatement","messageId":"unnecessaryCatch","endLine":526,"endColumn":4},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'emailData' is assigned a value but never used. Allowed unused vars must match /^_/u.","line":547,"column":19,"nodeType":null,"messageId":"unusedVar","endLine":547,"endColumn":28},{"ruleId":"no-empty","severity":1,"message":"Empty block statement.","line":565,"column":16,"nodeType":"BlockStatement","messageId":"unexpected","endLine":567,"endColumn":6,"suggestions":[{"messageId":"suggestComment","data":{"type":"block"},"fix":{"range":[18649,18655],"text":" /* empty */ "},"desc":"Add comment inside empty block statement."}]},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":568,"column":12,"nodeType":null,"messageId":"unusedVar","endLine":568,"endColumn":17},{"ruleId":"no-empty","severity":1,"message":"Empty block statement.","line":568,"column":19,"nodeType":"BlockStatement","messageId":"unexpected","endLine":570,"endColumn":4,"suggestions":[{"messageId":"suggestComment","data":{"type":"block"},"fix":{"range":[18676,18680],"text":" /* empty */ "},"desc":"Add comment inside empty block statement."}]},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":591,"column":12,"nodeType":null,"messageId":"unusedVar","endLine":591,"endColumn":17},{"ruleId":"no-useless-catch","severity":2,"message":"Unnecessary try/catch wrapper.","line":827,"column":3,"nodeType":"TryStatement","messageId":"unnecessaryCatch","endLine":842,"endColumn":4},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'emailData' is assigned a value but never used. Allowed unused vars must match /^_/u.","line":828,"column":19,"nodeType":null,"messageId":"unusedVar","endLine":828,"endColumn":28},{"ruleId":"no-useless-catch","severity":2,"message":"Unnecessary try/catch wrapper.","line":854,"column":3,"nodeType":"TryStatement","messageId":"unnecessaryCatch","endLine":869,"endColumn":4},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'emailData' is assigned a value but never used. Allowed unused vars must match /^_/u.","line":855,"column":19,"nodeType":null,"messageId":"unusedVar","endLine":855,"endColumn":28},{"ruleId":"no-useless-catch","severity":2,"message":"Unnecessary try/catch wrapper.","line":881,"column":3,"nodeType":"TryStatement","messageId":"unnecessaryCatch","endLine":896,"endColumn":4},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'emailData' is assigned a value but never used. Allowed unused vars must match /^_/u.","line":882,"column":19,"nodeType":null,"messageId":"unusedVar","endLine":882,"endColumn":28}],"suppressedMessages":[],"errorCount":6,"fatalErrorCount":0,"warningCount":9,"fixableErrorCount":0,"fixableWarningCount":0,"source":"import { Resend } from 'resend';\nimport QRCode from 'qrcode';\nimport { logUserActivity } from './logger';\n\n// Initialize Resend\nconst resend = new Resend(process.env.RESEND_API_KEY);\n\n// Email configuration\nexport const EMAIL_CONFIG = {\n FROM_EMAIL: 'Black Canyon Tickets <tickets@blackcanyontickets.com>',\n FROM_NAME: 'Black Canyon Tickets',\n SUPPORT_EMAIL: 'support@blackcanyontickets.com',\n DOMAIN: process.env.PUBLIC_APP_URL || 'https://portal.blackcanyontickets.com'\n};\n\n// Validate email configuration\nif (!process.env.RESEND_API_KEY) {\n\n}\n\nexport interface TicketEmailData {\n ticketId: string;\n ticketUuid: string;\n eventTitle: string;\n eventVenue: string;\n eventDate: string;\n eventTime: string;\n ticketType: string;\n seatInfo?: string;\n price: number;\n purchaserName: string;\n purchaserEmail: string;\n organizerName: string;\n organizerEmail: string;\n qrCodeUrl: string;\n orderNumber: string;\n totalAmount: number;\n platformFee: number;\n eventDescription?: string;\n eventAddress?: string;\n additionalInfo?: string;\n}\n\nexport interface OrderConfirmationData {\n orderNumber: string;\n purchaserName: string;\n purchaserEmail: string;\n eventTitle: string;\n eventVenue: string;\n eventDate: string;\n totalAmount: number;\n platformFee: number;\n tickets: Array<{\n type: string;\n quantity: number;\n price: number;\n seatInfo?: string;\n }>;\n organizerName: string;\n refundPolicy?: string;\n}\n\nexport interface ApplicationReceivedData {\n organizationName: string;\n userEmail: string;\n userName: string;\n expectedApprovalTime: string;\n referenceNumber: string;\n nextSteps: string[];\n}\n\nexport interface AdminNotificationData {\n organizationName: string;\n userEmail: string;\n userName: string;\n businessType: string;\n approvalScore: number;\n requiresReview: boolean;\n adminDashboardUrl: string;\n}\n\nexport interface ApprovalNotificationData {\n organizationName: string;\n userEmail: string;\n userName: string;\n approvedBy: string;\n stripeOnboardingUrl: string;\n nextSteps: string[];\n welcomeMessage?: string;\n}\n\n/**\n * Generate QR code data URL for email\n */\nasync function generateQRCodeDataURL(ticketUuid: string): Promise<string> {\n try {\n const qrData = `${EMAIL_CONFIG.DOMAIN}/verify/${ticketUuid}`;\n const qrCodeDataURL = await QRCode.toDataURL(qrData, {\n errorCorrectionLevel: 'M',\n type: 'image/png',\n quality: 0.92,\n margin: 1,\n color: {\n dark: '#000000',\n light: '#FFFFFF'\n },\n width: 200\n });\n return qrCodeDataURL;\n } catch (error) {\n\n throw error;\n }\n}\n\n/**\n * Create ticket confirmation email HTML\n */\nfunction createTicketEmailHTML(data: TicketEmailData): string {\n return `\n<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <title>Your Ticket for ${data.eventTitle}</title>\n <style>\n body {\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;\n line-height: 1.6;\n color: #333333;\n max-width: 600px;\n margin: 0 auto;\n padding: 20px;\n background-color: #f8fafc;\n }\n .container {\n background-color: #ffffff;\n border-radius: 12px;\n box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);\n overflow: hidden;\n }\n .header {\n background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);\n color: white;\n padding: 30px 20px;\n text-align: center;\n }\n .content {\n padding: 30px 20px;\n }\n .ticket-section {\n background-color: #f1f5f9;\n border-radius: 8px;\n padding: 20px;\n margin: 20px 0;\n border-left: 4px solid #3b82f6;\n }\n .qr-section {\n text-align: center;\n background-color: #ffffff;\n border: 2px dashed #d1d5db;\n border-radius: 8px;\n padding: 20px;\n margin: 20px 0;\n }\n .event-details {\n display: grid;\n grid-template-columns: 1fr 1fr;\n gap: 15px;\n margin: 20px 0;\n }\n .detail-item {\n background-color: #f8fafc;\n padding: 12px;\n border-radius: 6px;\n }\n .detail-label {\n font-weight: 600;\n color: #64748b;\n font-size: 12px;\n text-transform: uppercase;\n letter-spacing: 0.5px;\n margin-bottom: 4px;\n }\n .detail-value {\n color: #1e293b;\n font-size: 14px;\n font-weight: 500;\n }\n .footer {\n background-color: #f1f5f9;\n padding: 20px;\n text-align: center;\n border-top: 1px solid #e2e8f0;\n }\n .button {\n display: inline-block;\n background-color: #3b82f6;\n color: white;\n padding: 12px 24px;\n text-decoration: none;\n border-radius: 6px;\n font-weight: 600;\n margin: 10px 0;\n }\n .important-note {\n background-color: #fef3c7;\n border: 1px solid #f59e0b;\n border-radius: 6px;\n padding: 15px;\n margin: 20px 0;\n }\n @media (max-width: 600px) {\n .event-details {\n grid-template-columns: 1fr;\n }\n }\n </style>\n</head>\n<body>\n <div class=\"container\">\n <div class=\"header\">\n <h1 style=\"margin: 0; font-size: 24px;\">🎫 Your Ticket is Ready!</h1>\n <p style=\"margin: 10px 0 0; opacity: 0.9;\">You're all set for ${data.eventTitle}</p>\n </div>\n \n <div class=\"content\">\n <p>Hi ${data.purchaserName},</p>\n \n <p>Thanks for your purchase! Your ticket for <strong>${data.eventTitle}</strong> is confirmed and ready to use.</p>\n \n <div class=\"ticket-section\">\n <h2 style=\"margin-top: 0; color: #1e293b; font-size: 18px;\">📍 Event Details</h2>\n <div class=\"event-details\">\n <div class=\"detail-item\">\n <div class=\"detail-label\">Event</div>\n <div class=\"detail-value\">${data.eventTitle}</div>\n </div>\n <div class=\"detail-item\">\n <div class=\"detail-label\">Date & Time</div>\n <div class=\"detail-value\">${data.eventDate} at ${data.eventTime}</div>\n </div>\n <div class=\"detail-item\">\n <div class=\"detail-label\">Venue</div>\n <div class=\"detail-value\">${data.eventVenue}</div>\n </div>\n <div class=\"detail-item\">\n <div class=\"detail-label\">Ticket Type</div>\n <div class=\"detail-value\">${data.ticketType}${data.seatInfo ? ` - ${data.seatInfo}` : ''}</div>\n </div>\n <div class=\"detail-item\">\n <div class=\"detail-label\">Order Number</div>\n <div class=\"detail-value\">${data.orderNumber}</div>\n </div>\n <div class=\"detail-item\">\n <div class=\"detail-label\">Amount Paid</div>\n <div class=\"detail-value\">$${(data.totalAmount / 100).toFixed(2)}</div>\n </div>\n </div>\n </div>\n \n <div class=\"qr-section\">\n <h3 style=\"color: #1e293b; margin-top: 0;\">📱 Your Digital Ticket</h3>\n <p style=\"color: #64748b; margin-bottom: 20px;\">Present this QR code at the venue for entry</p>\n <img src=\"${data.qrCodeUrl}\" alt=\"Ticket QR Code\" style=\"max-width: 200px; height: auto;\" />\n <p style=\"font-size: 12px; color: #64748b; margin-top: 15px;\">\n Ticket ID: ${data.ticketUuid}\n </p>\n </div>\n \n <div class=\"important-note\">\n <strong>📋 Important Information:</strong>\n <ul style=\"margin: 10px 0; padding-left: 20px;\">\n <li>Save this email or screenshot the QR code</li>\n <li>Arrive 15-30 minutes early for entry</li>\n <li>Present a valid ID if required</li>\n <li>This ticket is non-transferable unless specified</li>\n </ul>\n </div>\n \n ${data.additionalInfo ? `\n <div style=\"background-color: #e0f2fe; border-radius: 6px; padding: 15px; margin: 20px 0;\">\n <strong>Additional Information:</strong>\n <p style=\"margin: 10px 0 0;\">${data.additionalInfo}</p>\n </div>\n ` : ''}\n \n <div style=\"text-align: center; margin: 30px 0;\">\n <a href=\"${EMAIL_CONFIG.DOMAIN}/e/${data.eventTitle.toLowerCase().replace(/\\s+/g, '-')}\" class=\"button\">\n View Event Details\n </a>\n </div>\n \n <p>Questions? Contact the event organizer at <a href=\"mailto:${data.organizerEmail}\">${data.organizerEmail}</a> or our support team at <a href=\"mailto:${EMAIL_CONFIG.SUPPORT_EMAIL}\">${EMAIL_CONFIG.SUPPORT_EMAIL}</a>.</p>\n \n <p>We hope you have a great time at the event!</p>\n \n <p style=\"color: #64748b; font-size: 14px;\">\n Best regards,<br>\n The Black Canyon Tickets Team\n </p>\n </div>\n \n <div class=\"footer\">\n <p style=\"margin: 0; font-size: 12px; color: #64748b;\">\n This email was sent by Black Canyon Tickets.<br>\n <a href=\"${EMAIL_CONFIG.DOMAIN}/privacy\" style=\"color: #3b82f6;\">Privacy Policy</a> | \n <a href=\"${EMAIL_CONFIG.DOMAIN}/terms\" style=\"color: #3b82f6;\">Terms of Service</a>\n </p>\n </div>\n </div>\n</body>\n</html>`;\n}\n\n/**\n * Create order confirmation email HTML\n */\nfunction createOrderConfirmationHTML(data: OrderConfirmationData): string {\n const ticketList = data.tickets.map(ticket => \n `<li>${ticket.quantity}x ${ticket.type}${ticket.seatInfo ? ` (${ticket.seatInfo})` : ''} - $${(ticket.price / 100).toFixed(2)} each</li>`\n ).join('');\n\n return `\n<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <title>Order Confirmation - ${data.eventTitle}</title>\n <style>\n body {\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;\n line-height: 1.6;\n color: #333333;\n max-width: 600px;\n margin: 0 auto;\n padding: 20px;\n background-color: #f8fafc;\n }\n .container {\n background-color: #ffffff;\n border-radius: 12px;\n box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);\n overflow: hidden;\n }\n .header {\n background: linear-gradient(135deg, #10b981 0%, #059669 100%);\n color: white;\n padding: 30px 20px;\n text-align: center;\n }\n .content {\n padding: 30px 20px;\n }\n .order-summary {\n background-color: #f1f5f9;\n border-radius: 8px;\n padding: 20px;\n margin: 20px 0;\n }\n .footer {\n background-color: #f1f5f9;\n padding: 20px;\n text-align: center;\n border-top: 1px solid #e2e8f0;\n }\n </style>\n</head>\n<body>\n <div class=\"container\">\n <div class=\"header\">\n <h1 style=\"margin: 0; font-size: 24px;\">✅ Order Confirmed!</h1>\n <p style=\"margin: 10px 0 0; opacity: 0.9;\">Order #${data.orderNumber}</p>\n </div>\n \n <div class=\"content\">\n <p>Hi ${data.purchaserName},</p>\n \n <p>Your order for <strong>${data.eventTitle}</strong> has been confirmed! You'll receive individual ticket emails shortly with QR codes for entry.</p>\n \n <div class=\"order-summary\">\n <h3 style=\"margin-top: 0; color: #1e293b;\">📋 Order Summary</h3>\n <p><strong>Event:</strong> ${data.eventTitle}<br>\n <strong>Venue:</strong> ${data.eventVenue}<br>\n <strong>Date:</strong> ${data.eventDate}</p>\n \n <h4 style=\"color: #1e293b;\">Tickets Purchased:</h4>\n <ul>\n ${ticketList}\n </ul>\n \n <hr style=\"border: none; border-top: 1px solid #e2e8f0; margin: 20px 0;\">\n \n <div style=\"display: flex; justify-content: space-between; margin: 10px 0;\">\n <span>Subtotal:</span>\n <span>$${((data.totalAmount - data.platformFee) / 100).toFixed(2)}</span>\n </div>\n <div style=\"display: flex; justify-content: space-between; margin: 10px 0;\">\n <span>Platform Fee:</span>\n <span>$${(data.platformFee / 100).toFixed(2)}</span>\n </div>\n <div style=\"display: flex; justify-content: space-between; margin: 10px 0; font-weight: bold; font-size: 18px; border-top: 1px solid #e2e8f0; padding-top: 10px;\">\n <span>Total:</span>\n <span>$${(data.totalAmount / 100).toFixed(2)}</span>\n </div>\n </div>\n \n <p>Your individual ticket emails with QR codes will arrive within the next few minutes. If you don't receive them, please check your spam folder.</p>\n \n ${data.refundPolicy ? `\n <div style=\"background-color: #fef3c7; border: 1px solid #f59e0b; border-radius: 6px; padding: 15px; margin: 20px 0;\">\n <strong>Refund Policy:</strong>\n <p style=\"margin: 10px 0 0;\">${data.refundPolicy}</p>\n </div>\n ` : ''}\n \n <p>Questions about your order? Contact ${data.organizerName} at <a href=\"mailto:${data.purchaserEmail}\">${data.purchaserEmail}</a> or our support team at <a href=\"mailto:${EMAIL_CONFIG.SUPPORT_EMAIL}\">${EMAIL_CONFIG.SUPPORT_EMAIL}</a>.</p>\n \n <p style=\"color: #64748b; font-size: 14px;\">\n Best regards,<br>\n The Black Canyon Tickets Team\n </p>\n </div>\n \n <div class=\"footer\">\n <p style=\"margin: 0; font-size: 12px; color: #64748b;\">\n This email was sent by Black Canyon Tickets.<br>\n <a href=\"${EMAIL_CONFIG.DOMAIN}/privacy\" style=\"color: #3b82f6;\">Privacy Policy</a> | \n <a href=\"${EMAIL_CONFIG.DOMAIN}/terms\" style=\"color: #3b82f6;\">Terms of Service</a>\n </p>\n </div>\n </div>\n</body>\n</html>`;\n}\n\n/**\n * Send ticket confirmation email\n */\nexport async function sendTicketConfirmationEmail(ticketData: TicketEmailData): Promise<void> {\n if (!process.env.RESEND_API_KEY) {\n\n return;\n }\n\n try {\n // Generate QR code\n const qrCodeDataURL = await generateQRCodeDataURL(ticketData.ticketUuid);\n const emailData = { ...ticketData, qrCodeUrl: qrCodeDataURL };\n\n const { data, error } = await resend.emails.send({\n from: EMAIL_CONFIG.FROM_EMAIL,\n to: [ticketData.purchaserEmail],\n subject: `Your ticket for ${ticketData.eventTitle}`,\n html: createTicketEmailHTML(emailData),\n attachments: [\n {\n filename: `ticket-${ticketData.ticketUuid}.png`,\n content: qrCodeDataURL.split(',')[1], // Remove data URL prefix\n contentType: 'image/png'\n }\n ]\n });\n\n if (error) {\n throw error;\n }\n\n // Log successful email send\n logUserActivity({\n action: 'ticket_email_sent',\n userId: '', // No user context for email\n details: {\n ticketId: ticketData.ticketId,\n recipientEmail: ticketData.purchaserEmail,\n eventTitle: ticketData.eventTitle,\n emailId: data?.id\n }\n });\n\n } catch (error) {\n\n throw error;\n }\n}\n\n/**\n * Send order confirmation email\n */\nexport async function sendOrderConfirmationEmail(orderData: OrderConfirmationData): Promise<void> {\n if (!process.env.RESEND_API_KEY) {\n\n return;\n }\n\n try {\n const { data, error } = await resend.emails.send({\n from: EMAIL_CONFIG.FROM_EMAIL,\n to: [orderData.purchaserEmail],\n subject: `Order confirmed for ${orderData.eventTitle} - #${orderData.orderNumber}`,\n html: createOrderConfirmationHTML(orderData)\n });\n\n if (error) {\n throw error;\n }\n\n // Log successful email send\n logUserActivity({\n action: 'order_confirmation_email_sent',\n userId: '', // No user context for email\n details: {\n orderNumber: orderData.orderNumber,\n recipientEmail: orderData.purchaserEmail,\n eventTitle: orderData.eventTitle,\n totalAmount: orderData.totalAmount,\n emailId: data?.id\n }\n });\n\n } catch (error) {\n\n throw error;\n }\n}\n\n/**\n * Send organizer notification email\n */\nexport async function sendOrganizerNotificationEmail(data: {\n organizerEmail: string;\n organizerName: string;\n eventTitle: string;\n purchaserName: string;\n purchaserEmail: string;\n ticketType: string;\n amount: number;\n orderNumber: string;\n}): Promise<void> {\n if (!process.env.RESEND_API_KEY) {\n return;\n }\n\n try {\n const { data: emailData, error } = await resend.emails.send({\n from: EMAIL_CONFIG.FROM_EMAIL,\n to: [data.organizerEmail],\n subject: `New ticket sale for ${data.eventTitle}`,\n html: `\n <h2>New Ticket Sale</h2>\n <p>Hi ${data.organizerName},</p>\n <p>You have a new ticket sale for <strong>${data.eventTitle}</strong>!</p>\n <ul>\n <li><strong>Customer:</strong> ${data.purchaserName} (${data.purchaserEmail})</li>\n <li><strong>Ticket Type:</strong> ${data.ticketType}</li>\n <li><strong>Amount:</strong> $${(data.amount / 100).toFixed(2)}</li>\n <li><strong>Order:</strong> #${data.orderNumber}</li>\n </ul>\n <p>View your full sales report at <a href=\"${EMAIL_CONFIG.DOMAIN}/dashboard\">your dashboard</a>.</p>\n `\n });\n\n if (error) {\n\n }\n } catch (error) {\n\n }\n}\n\n/**\n * Test email configuration\n */\nexport async function testEmailConfiguration(): Promise<boolean> {\n if (!process.env.RESEND_API_KEY) {\n return false;\n }\n\n try {\n const { error } = await resend.emails.send({\n from: EMAIL_CONFIG.FROM_EMAIL,\n to: ['test@example.com'], // This will fail but tests the connection\n subject: 'Test email configuration',\n html: '<p>This is a test email.</p>'\n });\n\n // We expect this to fail with invalid email, but connection should work\n return error?.message?.includes('Invalid') || false;\n } catch (error) {\n\n return false;\n }\n}\n\n/**\n * Create application received email HTML\n */\nfunction createApplicationReceivedHTML(data: ApplicationReceivedData): string {\n const nextStepsList = data.nextSteps.map(step => `<li>${step}</li>`).join('');\n \n return `\n<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <title>Application Received</title>\n <style>\n body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; line-height: 1.6; margin: 0; padding: 0; background-color: #f8fafc; }\n .container { max-width: 600px; margin: 0 auto; background-color: white; }\n .header { background: linear-gradient(135deg, #1f2937 0%, #374151 100%); color: white; padding: 32px; text-align: center; }\n .content { padding: 32px; }\n .status-badge { background: #fef3c7; color: #92400e; padding: 8px 16px; border-radius: 20px; font-size: 14px; font-weight: 600; display: inline-block; }\n .info-box { background: #f0f9ff; border: 1px solid #bae6fd; border-radius: 8px; padding: 20px; margin: 20px 0; }\n .next-steps { background: #f0fdf4; border: 1px solid #bbf7d0; border-radius: 8px; padding: 20px; margin: 20px 0; }\n .footer { background: #f8fafc; padding: 24px; text-align: center; font-size: 12px; color: #6b7280; }\n </style>\n</head>\n<body>\n <div class=\"container\">\n <div class=\"header\">\n <h1 style=\"margin: 0; font-size: 28px;\">Application Received</h1>\n <p style=\"margin: 16px 0 0; font-size: 16px; opacity: 0.9;\">Your Black Canyon Tickets application is being reviewed</p>\n </div>\n \n <div class=\"content\">\n <div class=\"status-badge\">⏳ Pending Review</div>\n \n <p style=\"font-size: 18px; margin: 24px 0 16px;\">Hi ${data.userName},</p>\n \n <p>Thank you for your interest in Black Canyon Tickets! We've received your application for <strong>${data.organizationName}</strong> and our team is reviewing it.</p>\n \n <div class=\"info-box\">\n <h3 style=\"margin: 0 0 12px; color: #1f2937;\">📋 Application Details</h3>\n <ul style=\"margin: 0; padding-left: 20px;\">\n <li><strong>Organization:</strong> ${data.organizationName}</li>\n <li><strong>Reference Number:</strong> ${data.referenceNumber}</li>\n <li><strong>Expected Review Time:</strong> ${data.expectedApprovalTime}</li>\n </ul>\n </div>\n \n <div class=\"next-steps\">\n <h3 style=\"margin: 0 0 12px; color: #15803d;\">🚀 What happens next?</h3>\n <ol style=\"margin: 0; padding-left: 20px;\">\n ${nextStepsList}\n </ol>\n </div>\n \n <p>We'll notify you as soon as your application is reviewed. In the meantime, you can explore our platform features and documentation.</p>\n \n <p style=\"margin: 24px 0;\">\n Questions? Contact our support team at <a href=\"mailto:${EMAIL_CONFIG.SUPPORT_EMAIL}\" style=\"color: #3b82f6;\">${EMAIL_CONFIG.SUPPORT_EMAIL}</a>\n </p>\n </div>\n \n <div class=\"footer\">\n <p style=\"margin: 0;\">\n This email was sent by Black Canyon Tickets<br>\n <a href=\"${EMAIL_CONFIG.DOMAIN}/privacy\" style=\"color: #6b7280;\">Privacy Policy</a> | \n <a href=\"${EMAIL_CONFIG.DOMAIN}/terms\" style=\"color: #6b7280;\">Terms of Service</a>\n </p>\n </div>\n </div>\n</body>\n</html>`;\n}\n\n/**\n * Create admin notification email HTML\n */\nfunction createAdminNotificationHTML(data: AdminNotificationData): string {\n const scoreColor = data.approvalScore >= 80 ? '#10b981' : data.approvalScore >= 60 ? '#f59e0b' : '#ef4444';\n const reviewStatus = data.requiresReview ? 'Manual Review Required' : 'Auto-Approval Eligible';\n \n return `\n<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <title>New Application - Admin Review</title>\n <style>\n body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; line-height: 1.6; margin: 0; padding: 0; background-color: #f8fafc; }\n .container { max-width: 600px; margin: 0 auto; background-color: white; }\n .header { background: linear-gradient(135deg, #dc2626 0%, #ef4444 100%); color: white; padding: 32px; text-align: center; }\n .content { padding: 32px; }\n .review-box { background: #fef2f2; border: 1px solid #fecaca; border-radius: 8px; padding: 20px; margin: 20px 0; }\n .score-badge { padding: 8px 16px; border-radius: 20px; font-size: 14px; font-weight: 600; display: inline-block; margin-bottom: 12px; }\n .action-button { background: #dc2626; color: white; padding: 12px 24px; border-radius: 8px; text-decoration: none; font-weight: 600; display: inline-block; margin: 16px 0; }\n .footer { background: #f8fafc; padding: 24px; text-align: center; font-size: 12px; color: #6b7280; }\n </style>\n</head>\n<body>\n <div class=\"container\">\n <div class=\"header\">\n <h1 style=\"margin: 0; font-size: 28px;\">🔔 New Application</h1>\n <p style=\"margin: 16px 0 0; font-size: 16px; opacity: 0.9;\">Admin Review Required</p>\n </div>\n \n <div class=\"content\">\n <div class=\"score-badge\" style=\"background: ${scoreColor}; color: white;\">\n Score: ${data.approvalScore}/100\n </div>\n \n <h2 style=\"margin: 0 0 16px; color: #1f2937;\">${data.organizationName}</h2>\n \n <div class=\"review-box\">\n <h3 style=\"margin: 0 0 12px; color: #dc2626;\">📊 Application Summary</h3>\n <ul style=\"margin: 0; padding-left: 20px;\">\n <li><strong>Applicant:</strong> ${data.userName} (${data.userEmail})</li>\n <li><strong>Business Type:</strong> ${data.businessType}</li>\n <li><strong>Approval Score:</strong> ${data.approvalScore}/100</li>\n <li><strong>Review Status:</strong> ${reviewStatus}</li>\n </ul>\n </div>\n \n <p>${data.requiresReview ? 'This application requires manual review based on our approval criteria.' : 'This application meets auto-approval criteria but may need final verification.'}</p>\n \n <div style=\"text-align: center; margin: 32px 0;\">\n <a href=\"${data.adminDashboardUrl}\" class=\"action-button\">\n Review Application →\n </a>\n </div>\n \n <p style=\"font-size: 14px; color: #6b7280;\">\n You can approve, reject, or request more information from the admin dashboard.\n </p>\n </div>\n \n <div class=\"footer\">\n <p style=\"margin: 0;\">\n Black Canyon Tickets Admin System<br>\n <a href=\"${EMAIL_CONFIG.DOMAIN}/admin\" style=\"color: #6b7280;\">Admin Dashboard</a>\n </p>\n </div>\n </div>\n</body>\n</html>`;\n}\n\n/**\n * Create approval notification email HTML\n */\nfunction createApprovalNotificationHTML(data: ApprovalNotificationData): string {\n const nextStepsList = data.nextSteps.map(step => `<li>${step}</li>`).join('');\n \n return `\n<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <title>Account Approved - Welcome!</title>\n <style>\n body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; line-height: 1.6; margin: 0; padding: 0; background-color: #f8fafc; }\n .container { max-width: 600px; margin: 0 auto; background-color: white; }\n .header { background: linear-gradient(135deg, #10b981 0%, #14b8a6 100%); color: white; padding: 32px; text-align: center; }\n .content { padding: 32px; }\n .approval-badge { background: #d1fae5; color: #065f46; padding: 8px 16px; border-radius: 20px; font-size: 14px; font-weight: 600; display: inline-block; }\n .welcome-box { background: #f0fdf4; border: 1px solid #bbf7d0; border-radius: 8px; padding: 20px; margin: 20px 0; }\n .next-steps { background: #eff6ff; border: 1px solid #bfdbfe; border-radius: 8px; padding: 20px; margin: 20px 0; }\n .action-button { background: #1f2937; color: white; padding: 16px 32px; border-radius: 8px; text-decoration: none; font-weight: 600; display: inline-block; margin: 16px 0; }\n .footer { background: #f8fafc; padding: 24px; text-align: center; font-size: 12px; color: #6b7280; }\n </style>\n</head>\n<body>\n <div class=\"container\">\n <div class=\"header\">\n <h1 style=\"margin: 0; font-size: 28px;\">🎉 Account Approved!</h1>\n <p style=\"margin: 16px 0 0; font-size: 16px; opacity: 0.9;\">Welcome to Black Canyon Tickets</p>\n </div>\n \n <div class=\"content\">\n <div class=\"approval-badge\">✅ Approved by ${data.approvedBy}</div>\n \n <p style=\"font-size: 18px; margin: 24px 0 16px;\">Congratulations ${data.userName}!</p>\n \n <div class=\"welcome-box\">\n <h3 style=\"margin: 0 0 12px; color: #065f46;\">🏢 ${data.organizationName}</h3>\n <p style=\"margin: 0;\">Your account has been approved and you're ready to complete the secure payment setup process.</p>\n ${data.welcomeMessage ? `<p style=\"margin: 16px 0 0; font-style: italic;\">${data.welcomeMessage}</p>` : ''}\n </div>\n \n <div class=\"next-steps\">\n <h3 style=\"margin: 0 0 12px; color: #1e40af;\">🔒 Complete Your Setup</h3>\n <ol style=\"margin: 0; padding-left: 20px;\">\n ${nextStepsList}\n </ol>\n </div>\n \n <div style=\"text-align: center; margin: 32px 0;\">\n <a href=\"${data.stripeOnboardingUrl}\" class=\"action-button\">\n Complete Secure Setup →\n </a>\n </div>\n \n <p style=\"background: #f0f9ff; border: 1px solid #bae6fd; border-radius: 8px; padding: 16px; margin: 20px 0;\">\n <strong>🔒 Security Note:</strong> This setup process uses bank-level encryption through Stripe Connect. Your sensitive information is never stored on our servers.\n </p>\n \n <p>Questions about the setup process? Contact our support team at <a href=\"mailto:${EMAIL_CONFIG.SUPPORT_EMAIL}\" style=\"color: #3b82f6;\">${EMAIL_CONFIG.SUPPORT_EMAIL}</a></p>\n </div>\n \n <div class=\"footer\">\n <p style=\"margin: 0;\">\n This email was sent by Black Canyon Tickets<br>\n <a href=\"${EMAIL_CONFIG.DOMAIN}/privacy\" style=\"color: #6b7280;\">Privacy Policy</a> | \n <a href=\"${EMAIL_CONFIG.DOMAIN}/terms\" style=\"color: #6b7280;\">Terms of Service</a>\n </p>\n </div>\n </div>\n</body>\n</html>`;\n}\n\n/**\n * Send application received email\n */\nexport async function sendApplicationReceivedEmail(data: ApplicationReceivedData): Promise<void> {\n if (!process.env.RESEND_API_KEY) {\n\n return;\n }\n\n try {\n const { data: emailData, error } = await resend.emails.send({\n from: EMAIL_CONFIG.FROM_EMAIL,\n to: [data.userEmail],\n subject: `Application received for ${data.organizationName} - BCT#${data.referenceNumber}`,\n html: createApplicationReceivedHTML(data)\n });\n\n if (error) {\n throw error;\n }\n\n } catch (error) {\n\n throw error;\n }\n}\n\n/**\n * Send admin notification email\n */\nexport async function sendAdminNotificationEmail(data: AdminNotificationData): Promise<void> {\n if (!process.env.RESEND_API_KEY) {\n\n return;\n }\n\n try {\n const { data: emailData, error } = await resend.emails.send({\n from: EMAIL_CONFIG.FROM_EMAIL,\n to: [EMAIL_CONFIG.SUPPORT_EMAIL], // Send to support team\n subject: `New application requires review: ${data.organizationName}`,\n html: createAdminNotificationHTML(data)\n });\n\n if (error) {\n throw error;\n }\n\n } catch (error) {\n\n throw error;\n }\n}\n\n/**\n * Send approval notification email\n */\nexport async function sendApprovalNotificationEmail(data: ApprovalNotificationData): Promise<void> {\n if (!process.env.RESEND_API_KEY) {\n\n return;\n }\n\n try {\n const { data: emailData, error } = await resend.emails.send({\n from: EMAIL_CONFIG.FROM_EMAIL,\n to: [data.userEmail],\n subject: `🎉 ${data.organizationName} approved - Complete your setup`,\n html: createApprovalNotificationHTML(data)\n });\n\n if (error) {\n throw error;\n }\n\n } catch (error) {\n\n throw error;\n }\n}","usedDeprecatedRules":[]},{"filePath":"/home/tyler/apps/bct-whitelabel/src/lib/event-management.ts","messages":[{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":24,"column":17,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":24,"endColumn":20,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[588,591],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[588,591],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'organizationId' is defined but never used. Allowed unused args must match /^_/u.","line":35,"column":54,"nodeType":null,"messageId":"unusedVar","endLine":35,"endColumn":68},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":87,"column":12,"nodeType":null,"messageId":"unusedVar","endLine":87,"endColumn":17},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":143,"column":12,"nodeType":null,"messageId":"unusedVar","endLine":143,"endColumn":17},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":162,"column":12,"nodeType":null,"messageId":"unusedVar","endLine":162,"endColumn":17}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":5,"fixableErrorCount":0,"fixableWarningCount":0,"source":"import { createClient } from '@supabase/supabase-js';\nimport type { Database } from './database.types';\n\nconst supabase = createClient<Database>(\n import.meta.env.PUBLIC_SUPABASE_URL,\n import.meta.env.PUBLIC_SUPABASE_ANON_KEY\n);\n\nexport interface EventData {\n id: string;\n title: string;\n description: string;\n start_time: string;\n venue: string;\n slug: string;\n organization_id: string;\n seating_map_id?: string | null;\n seating_type?: string;\n created_by?: string;\n created_at?: string;\n end_time?: string;\n is_published?: boolean;\n is_public?: boolean;\n seating_map?: any;\n}\n\nexport interface EventStats {\n totalRevenue: number;\n netRevenue: number;\n ticketsSold: number;\n ticketsAvailable: number;\n checkedIn: number;\n}\n\nexport async function loadEventData(eventId: string, organizationId: string): Promise<EventData | null> {\n try {\n\n // First try to load the event by ID only\n const { data: event, error } = await supabase\n .from('events')\n .select(`\n *,\n seating_maps (\n id,\n name,\n layout_data\n )\n `)\n .eq('id', eventId)\n .single();\n\n if (error) {\n\n return null;\n }\n\n if (!event) {\n\n return null;\n }\n\n // Check if user has access to this event\n const { data: { user } } = await supabase.auth.getUser();\n const { data: userProfile } = await supabase\n .from('users')\n .select('role, organization_id')\n .eq('id', user?.id || '')\n .single();\n \n const isAdmin = userProfile?.role === 'admin';\n \n // If not admin and event doesn't belong to user's organization, deny access\n if (!isAdmin && event.organization_id !== userProfile?.organization_id) {\n\n return null;\n }\n\n if (error) {\n\n return null;\n }\n\n return {\n ...event,\n seating_map: event.seating_maps\n };\n } catch (error) {\n\n return null;\n }\n}\n\nexport async function loadEventStats(eventId: string): Promise<EventStats> {\n try {\n // Get ticket sales data\n const { data: tickets, error: ticketsError } = await supabase\n .from('tickets')\n .select(`\n id,\n price,\n checked_in,\n ticket_types (\n id,\n name,\n price,\n quantity_available\n )\n `)\n .eq('event_id', eventId);\n\n if (ticketsError) {\n\n return getDefaultStats();\n }\n\n // Get ticket types for availability calculation\n const { data: ticketTypes, error: typesError } = await supabase\n .from('ticket_types')\n .select('id, quantity_available')\n .eq('event_id', eventId)\n .eq('is_active', true);\n\n if (typesError) {\n\n return getDefaultStats();\n }\n\n // Calculate stats\n const ticketsSold = tickets?.length || 0;\n const totalRevenue = tickets?.reduce((sum, ticket) => sum + (ticket.price || 0), 0) || 0;\n const netRevenue = totalRevenue * 0.97; // Assuming 3% platform fee\n const checkedIn = tickets?.filter(ticket => ticket.checked_in).length || 0;\n const totalCapacity = ticketTypes?.reduce((sum, type) => sum + (type.quantity_available || 0), 0) || 0;\n const ticketsAvailable = totalCapacity - ticketsSold;\n\n return {\n totalRevenue,\n netRevenue,\n ticketsSold,\n ticketsAvailable,\n checkedIn\n };\n } catch (error) {\n\n return getDefaultStats();\n }\n}\n\nexport async function updateEventData(eventId: string, updates: Partial<EventData>): Promise<boolean> {\n try {\n const { error } = await supabase\n .from('events')\n .update(updates)\n .eq('id', eventId);\n\n if (error) {\n\n return false;\n }\n\n return true;\n } catch (error) {\n\n return false;\n }\n}\n\nexport function formatEventDate(dateString: string): string {\n const date = new Date(dateString);\n return date.toLocaleDateString('en-US', {\n weekday: 'long',\n year: 'numeric',\n month: 'long',\n day: 'numeric',\n hour: 'numeric',\n minute: '2-digit'\n });\n}\n\nexport function formatCurrency(cents: number): string {\n return new Intl.NumberFormat('en-US', {\n style: 'currency',\n currency: 'USD'\n }).format(cents / 100);\n}\n\nfunction getDefaultStats(): EventStats {\n return {\n totalRevenue: 0,\n netRevenue: 0,\n ticketsSold: 0,\n ticketsAvailable: 0,\n checkedIn: 0\n };\n}","usedDeprecatedRules":[]},{"filePath":"/home/tyler/apps/bct-whitelabel/src/lib/eventScraper.ts","messages":[{"ruleId":"no-undef","severity":2,"message":"'URL' is not defined.","line":56,"column":25,"nodeType":"Identifier","messageId":"undef","endLine":56,"endColumn":28},{"ruleId":"no-undef","severity":2,"message":"'URL' is not defined.","line":127,"column":32,"nodeType":"Identifier","messageId":"undef","endLine":127,"endColumn":35},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'$' is defined but never used. Allowed unused args must match /^_/u.","line":171,"column":57,"nodeType":null,"messageId":"unusedVar","endLine":171,"endColumn":58},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":190,"column":12,"nodeType":null,"messageId":"unusedVar","endLine":190,"endColumn":17},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":208,"column":12,"nodeType":null,"messageId":"unusedVar","endLine":208,"endColumn":17},{"ruleId":"no-constant-binary-expression","severity":2,"message":"Unexpected constant truthiness on the left-hand side of a `||` expression.","line":258,"column":15,"nodeType":"TemplateLiteral","messageId":"constantShortCircuit","endLine":258,"endColumn":63}],"suppressedMessages":[],"errorCount":3,"fatalErrorCount":0,"warningCount":3,"fixableErrorCount":0,"fixableWarningCount":0,"source":"import * as cheerio from 'cheerio';\nimport { createClient } from '@supabase/supabase-js';\nimport type { Database } from './database.types';\nimport { logSecurityEvent, logError } from './logger';\nimport fs from 'fs/promises';\nimport path from 'path';\n\n// Environment variables\nconst supabaseUrl = process.env.SUPABASE_URL || import.meta.env.SUPABASE_URL || 'https://zctjaivtfyfxokfaemek.supabase.co';\nconst supabaseServiceKey = process.env.SUPABASE_SERVICE_KEY || import.meta.env.SUPABASE_SERVICE_KEY || '';\n\n// Configuration\nconst REDIRECT_URL = 'https://blackcanyontickets.com/events';\nconst BASE_URL = 'https://blackcanyontickets.com';\nconst LAST_SLUG_FILE = path.join(process.cwd(), 'logs', 'last_scraped_slug.txt');\nconst SCRAPER_ORGANIZATION_ID = process.env.SCRAPER_ORGANIZATION_ID || 'scraped-events-org';\n\n// Create Supabase client with proper types\nlet supabase: ReturnType<typeof createClient<Database>> | null = null;\n\ntry {\n if (supabaseUrl && supabaseServiceKey) {\n supabase = createClient<Database>(supabaseUrl, supabaseServiceKey);\n }\n} catch (error) {\n logError('Failed to initialize Supabase client for scraper', error);\n}\n\ninterface ScrapedEventDetails {\n slug: string;\n title: string;\n description?: string;\n venue?: string;\n startTime?: string;\n endTime?: string;\n imageUrl?: string;\n category?: string;\n}\n\n/**\n * Get the current event slug by following the redirect from /events\n */\nasync function getCurrentEventSlug(): Promise<string | null> {\n try {\n const response = await fetch(REDIRECT_URL, { \n redirect: 'manual',\n headers: {\n 'User-Agent': 'Mozilla/5.0 (compatible; BCT-Event-Scraper/1.0)'\n }\n });\n \n if (response.status === 302 || response.status === 301) {\n const location = response.headers.get('location');\n if (location) {\n // Extract slug from the redirect URL\n const url = new URL(location, BASE_URL);\n return url.pathname;\n }\n }\n \n return null;\n } catch (error) {\n logError('Failed to get current event slug', error);\n return null;\n }\n}\n\n/**\n * Fetch and parse event details from the event page\n */\nasync function fetchEventDetails(slug: string): Promise<ScrapedEventDetails | null> {\n try {\n const eventUrl = `${BASE_URL}${slug}`;\n const response = await fetch(eventUrl, {\n headers: {\n 'User-Agent': 'Mozilla/5.0 (compatible; BCT-Event-Scraper/1.0)'\n }\n });\n \n if (!response.ok) {\n throw new Error(`HTTP ${response.status}: ${response.statusText}`);\n }\n \n const html = await response.text();\n const $ = cheerio.load(html);\n \n // Extract event details - these selectors may need adjustment based on actual HTML structure\n const title = $('h1').first().text().trim() || \n $('[data-event-title]').text().trim() ||\n $('title').text().trim().split(' - ')[0];\n \n const description = $('[data-event-description]').text().trim() ||\n $('.event-description').text().trim() ||\n $('meta[name=\"description\"]').attr('content') ||\n '';\n \n const venue = $('[data-event-venue]').text().trim() ||\n $('.venue-name').text().trim() ||\n $('.event-venue').text().trim() ||\n 'Black Canyon Tickets Venue';\n \n // Try to extract date/time information\n const dateTimeText = $('[data-event-date]').text().trim() ||\n $('[data-event-time]').text().trim() ||\n $('.event-date').text().trim() ||\n $('.event-time').text().trim();\n \n // Try to extract image\n const imageUrl = $('[data-event-image]').attr('src') ||\n $('.event-image img').attr('src') ||\n $('meta[property=\"og:image\"]').attr('content') ||\n $('img[alt*=\"event\" i]').first().attr('src');\n \n // Determine category based on content\n const category = determineCategoryFromContent($, title, description);\n \n // Parse dates if available\n const { startTime, endTime } = parseDateTimeFromContent(dateTimeText, $);\n \n return {\n slug,\n title: title || 'Featured Event',\n description: description.length > 0 ? description.substring(0, 500) : undefined,\n venue,\n startTime,\n endTime,\n imageUrl: imageUrl ? new URL(imageUrl, BASE_URL).toString() : undefined,\n category\n };\n \n } catch (error) {\n logError(`Failed to fetch event details for ${slug}`, error);\n return null;\n }\n}\n\n/**\n * Determine event category based on content analysis\n */\nfunction determineCategoryFromContent($: cheerio.CheerioAPI, title: string, description: string): string {\n const content = (title + ' ' + description).toLowerCase();\n \n // Define category keywords\n const categoryKeywords = {\n music: ['concert', 'music', 'band', 'performance', 'singer', 'acoustic', 'jazz', 'classical', 'rock', 'pop'],\n arts: ['art', 'gallery', 'exhibition', 'theater', 'theatre', 'play', 'drama', 'dance', 'ballet'],\n community: ['community', 'festival', 'fair', 'celebration', 'parade', 'market', 'fundraiser', 'charity'],\n business: ['business', 'networking', 'conference', 'seminar', 'workshop', 'meetup', 'corporate'],\n food: ['food', 'wine', 'tasting', 'dinner', 'restaurant', 'culinary', 'chef', 'cooking'],\n sports: ['sports', 'race', 'marathon', 'golf', 'tournament', 'athletic', 'competition', 'game']\n };\n \n // Find the category with the most matches\n let bestCategory = 'community';\n let maxMatches = 0;\n \n for (const [category, keywords] of Object.entries(categoryKeywords)) {\n const matches = keywords.filter(keyword => content.includes(keyword)).length;\n if (matches > maxMatches) {\n maxMatches = matches;\n bestCategory = category;\n }\n }\n \n return bestCategory;\n}\n\n/**\n * Parse date/time information from content\n */\nfunction parseDateTimeFromContent(dateTimeText: string, $: cheerio.CheerioAPI): { startTime?: string; endTime?: string } {\n if (!dateTimeText) {\n // Default to a future date if no date found\n const futureDate = new Date();\n futureDate.setDate(futureDate.getDate() + 30); // 30 days from now\n return {\n startTime: futureDate.toISOString()\n };\n }\n \n try {\n // Try to parse the date/time\n // This is a simplified parser - could be enhanced based on actual format\n const date = new Date(dateTimeText);\n if (!isNaN(date.getTime())) {\n return {\n startTime: date.toISOString()\n };\n }\n } catch (error) {\n // Ignore parsing errors\n }\n \n // Fallback to future date\n const futureDate = new Date();\n futureDate.setDate(futureDate.getDate() + 30);\n return {\n startTime: futureDate.toISOString()\n };\n}\n\n/**\n * Load the last seen slug from file\n */\nasync function loadLastSeenSlug(): Promise<string | null> {\n try {\n return await fs.readFile(LAST_SLUG_FILE, 'utf-8');\n } catch (error) {\n // File doesn't exist or can't be read\n return null;\n }\n}\n\n/**\n * Save the last seen slug to file\n */\nasync function saveLastSeenSlug(slug: string): Promise<void> {\n try {\n // Ensure logs directory exists\n await fs.mkdir(path.dirname(LAST_SLUG_FILE), { recursive: true });\n await fs.writeFile(LAST_SLUG_FILE, slug);\n } catch (error) {\n logError('Failed to save last seen slug', error);\n }\n}\n\n/**\n * Add scraped event to the database as a featured event\n */\nasync function addScrapedEventToDatabase(eventDetails: ScrapedEventDetails): Promise<boolean> {\n if (!supabase) {\n logError('Supabase client not available for adding scraped event');\n return false;\n }\n \n try {\n // Create a deterministic ID based on the slug to avoid duplicates\n const eventId = `scraped-${eventDetails.slug.replace(/[^a-zA-Z0-9]/g, '-')}`;\n \n // Check if event already exists\n const { data: existingEvent } = await supabase\n .from('events')\n .select('id')\n .eq('id', eventId)\n .single();\n \n if (existingEvent) {\n\n return true;\n }\n \n // Insert the new event as featured and public\n const { error } = await supabase\n .from('events')\n .insert({\n id: eventId,\n title: eventDetails.title,\n slug: `external-${eventDetails.slug.split('/').pop()}` || eventId,\n description: eventDetails.description,\n venue: eventDetails.venue || 'Black Canyon Tickets Venue',\n start_time: eventDetails.startTime || new Date(Date.now() + 30 * 24 * 60 * 60 * 1000).toISOString(),\n end_time: eventDetails.endTime,\n image_url: eventDetails.imageUrl,\n category: eventDetails.category,\n is_featured: true,\n is_public: true,\n is_published: true,\n external_source: 'scraper',\n organization_id: SCRAPER_ORGANIZATION_ID,\n created_by: SCRAPER_ORGANIZATION_ID // This will need to be a valid user ID\n });\n \n if (error) {\n logError('Failed to insert scraped event into database', error);\n return false;\n }\n\n return true;\n \n } catch (error) {\n logError('Error adding scraped event to database', error);\n return false;\n }\n}\n\n/**\n * Main scraper function - detects new events and adds them as featured\n */\nexport async function runEventScraper(): Promise<{ success: boolean; message: string; newEvent?: ScrapedEventDetails }> {\n try {\n\n // Get current event slug\n const currentSlug = await getCurrentEventSlug();\n if (!currentSlug) {\n return {\n success: true,\n message: 'No event redirect found on blackcanyontickets.com/events'\n };\n }\n\n // Check if this is a new event\n const lastSeenSlug = await loadLastSeenSlug();\n if (currentSlug === lastSeenSlug) {\n return {\n success: true,\n message: 'No new event detected (same as last seen)'\n };\n }\n \n // Fetch event details\n const eventDetails = await fetchEventDetails(currentSlug);\n if (!eventDetails) {\n return {\n success: false,\n message: `Failed to extract event details from ${currentSlug}`\n };\n }\n\n // Add to database as featured event\n const added = await addScrapedEventToDatabase(eventDetails);\n if (!added) {\n return {\n success: false,\n message: 'Failed to add event to database'\n };\n }\n \n // Save the current slug as last seen\n await saveLastSeenSlug(currentSlug);\n \n // Log the successful scraping\n logSecurityEvent({\n type: 'scraper_success',\n severity: 'info',\n details: {\n slug: currentSlug,\n title: eventDetails.title,\n venue: eventDetails.venue,\n category: eventDetails.category\n }\n });\n \n return {\n success: true,\n message: `Successfully scraped and added featured event: ${eventDetails.title}`,\n newEvent: eventDetails\n };\n \n } catch (error) {\n logError('Event scraper failed', error);\n \n logSecurityEvent({\n type: 'scraper_error',\n severity: 'high',\n details: { error: error instanceof Error ? error.message : 'Unknown error' }\n });\n \n return {\n success: false,\n message: 'Event scraper encountered an error'\n };\n }\n}\n\n/**\n * Initialize scraper organization if it doesn't exist\n */\nexport async function initializeScraperOrganization(): Promise<boolean> {\n if (!supabase) {\n return false;\n }\n \n try {\n // Check if scraper organization exists\n const { data: existingOrg } = await supabase\n .from('organizations')\n .select('id')\n .eq('id', SCRAPER_ORGANIZATION_ID)\n .single();\n \n if (existingOrg) {\n return true;\n }\n \n // Create scraper organization\n const { error: orgError } = await supabase\n .from('organizations')\n .insert({\n id: SCRAPER_ORGANIZATION_ID,\n name: 'Black Canyon Tickets - Scraped Events',\n logo: null,\n stripe_account_id: null\n });\n \n if (orgError) {\n logError('Failed to create scraper organization', orgError);\n return false;\n }\n \n // Create scraper user\n const { error: userError } = await supabase\n .from('users')\n .insert({\n id: SCRAPER_ORGANIZATION_ID,\n email: 'scraper@blackcanyontickets.com',\n name: 'Event Scraper',\n organization_id: SCRAPER_ORGANIZATION_ID\n });\n \n if (userError) {\n logError('Failed to create scraper user', userError);\n return false;\n }\n\n return true;\n \n } catch (error) {\n logError('Failed to initialize scraper organization', error);\n return false;\n }\n}","usedDeprecatedRules":[]},{"filePath":"/home/tyler/apps/bct-whitelabel/src/lib/file-storage-service.ts","messages":[{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'FileUploadResult' is defined but never used. Allowed unused vars must match /^_/u.","line":4,"column":11,"nodeType":null,"messageId":"unusedVar","endLine":4,"endColumn":27},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'fileName' is defined but never used. Allowed unused args must match /^_/u.","line":41,"column":20,"nodeType":null,"messageId":"unusedVar","endLine":41,"endColumn":28},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'fileName' is defined but never used. Allowed unused args must match /^_/u.","line":49,"column":21,"nodeType":null,"messageId":"unusedVar","endLine":49,"endColumn":29}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":3,"fixableErrorCount":0,"fixableWarningCount":0,"source":"// File storage service for marketing kit assets\n// This is a placeholder implementation\n\ninterface FileUploadResult {\n url: string;\n success: boolean;\n error?: string;\n}\n\nclass FileStorageService {\n /**\n * Upload a file buffer and return the URL\n * In production, this would integrate with AWS S3, Google Cloud Storage, etc.\n */\n async uploadFile(buffer: Buffer, fileName: string): Promise<string> {\n // TODO: Implement actual file upload to cloud storage\n // For now, return a placeholder URL\n return `/api/files/marketing-kit/${fileName}`;\n }\n\n /**\n * Upload a data URL (base64) and return the URL\n */\n async uploadDataUrl(dataUrl: string, fileName: string): Promise<string> {\n // TODO: Convert data URL to buffer and upload\n // For now, return a placeholder URL\n return `/api/files/marketing-kit/${fileName}`;\n }\n\n /**\n * Create a temporary URL for file download\n */\n async createTemporaryUrl(fileName: string, expiresInMinutes: number = 60): Promise<string> {\n // TODO: Create signed URL with expiration\n return `/api/files/marketing-kit/temp/${fileName}?expires=${Date.now() + (expiresInMinutes * 60 * 1000)}`;\n }\n\n /**\n * Delete a file from storage\n */\n async deleteFile(fileName: string): Promise<boolean> {\n // TODO: Implement file deletion\n return true;\n }\n\n /**\n * Get file metadata\n */\n async getFileInfo(fileName: string): Promise<{\n size: number;\n lastModified: Date;\n contentType: string;\n } | null> {\n // TODO: Get actual file metadata\n return null;\n }\n}\n\nexport const fileStorageService = new FileStorageService();","usedDeprecatedRules":[]},{"filePath":"/home/tyler/apps/bct-whitelabel/src/lib/firebaseEventScraper.ts","messages":[{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":112,"column":37,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":112,"endColumn":40,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[3413,3416],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[3413,3416],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":122,"column":60,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":122,"endColumn":63,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[3827,3830],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[3827,3830],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":123,"column":62,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":123,"endColumn":65,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[3918,3921],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[3918,3921],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'year' is assigned a value but never used. Allowed unused vars must match /^_/u.","line":176,"column":13,"nodeType":null,"messageId":"unusedVar","endLine":176,"endColumn":17},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":216,"column":12,"nodeType":null,"messageId":"unusedVar","endLine":216,"endColumn":17},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'loadLastSyncTime' is defined but never used. Allowed unused vars must match /^_/u.","line":275,"column":16,"nodeType":null,"messageId":"unusedVar","endLine":275,"endColumn":32},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":278,"column":12,"nodeType":null,"messageId":"unusedVar","endLine":278,"endColumn":17},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":325,"column":12,"nodeType":null,"messageId":"unusedVar","endLine":325,"endColumn":17},{"ruleId":"no-undef","severity":2,"message":"'crypto' is not defined.","line":343,"column":21,"nodeType":"Identifier","messageId":"undef","endLine":343,"endColumn":27},{"ruleId":"no-empty","severity":1,"message":"Empty block statement.","line":440,"column":16,"nodeType":"BlockStatement","messageId":"unexpected","endLine":442,"endColumn":10,"suggestions":[{"messageId":"suggestComment","data":{"type":"block"},"fix":{"range":[12736,12746],"text":" /* empty */ "},"desc":"Add comment inside empty block statement."}]},{"ruleId":"no-empty","severity":1,"message":"Empty block statement.","line":443,"column":14,"nodeType":"BlockStatement","messageId":"unexpected","endLine":445,"endColumn":8,"suggestions":[{"messageId":"suggestComment","data":{"type":"block"},"fix":{"range":[12762,12770],"text":" /* empty */ "},"desc":"Add comment inside empty block statement."}]},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'checkError' is assigned a value but never used. Allowed unused vars must match /^_/u.","line":509,"column":39,"nodeType":null,"messageId":"unusedVar","endLine":509,"endColumn":49}],"suppressedMessages":[],"errorCount":1,"fatalErrorCount":0,"warningCount":11,"fixableErrorCount":0,"fixableWarningCount":0,"source":"import { createClient } from '@supabase/supabase-js';\nimport type { Database } from './database.types';\nimport { logSecurityEvent, logError } from './logger';\nimport fs from 'fs/promises';\nimport path from 'path';\n\n// Environment variables\nconst supabaseUrl = process.env.SUPABASE_URL || import.meta.env.SUPABASE_URL || 'https://zctjaivtfyfxokfaemek.supabase.co';\nconst supabaseServiceKey = process.env.SUPABASE_SERVICE_KEY || import.meta.env.SUPABASE_SERVICE_KEY || '';\n\n// Firebase configuration\nconst FIREBASE_PROJECT_ID = process.env.FIREBASE_PROJECT_ID || 'black-canyon-tickets-bct';\nconst FIREBASE_API_KEY = process.env.FIREBASE_API_KEY || 'AIzaSyDpXpjfQcNO_Lz7OuzINzZJG6pQXFOOLxI';\nconst FIREBASE_ADMIN_EMAIL = process.env.FIREBASE_ADMIN_EMAIL || 'Tyler@touchofcarepcp.com';\nconst FIREBASE_ADMIN_PASSWORD = process.env.FIREBASE_ADMIN_PASSWORD || '^A@6qDIOah*qNf)^i)1tbqtY';\n\nconst LAST_SYNC_FILE = path.join(process.cwd(), 'logs', 'last_firebase_sync.txt');\nconst SCRAPER_ORGANIZATION_ID = process.env.SCRAPER_ORGANIZATION_ID || 'f47ac10b-58cc-4372-a567-0e02b2c3d479';\nconst BCT_VENUE_ID = 'b47ac10b-58cc-4372-a567-0e02b2c3d479'; // Black Canyon Tickets venue\n\n// Create Supabase client with proper types\nlet supabase: ReturnType<typeof createClient<Database>> | null = null;\n\ntry {\n if (supabaseUrl && supabaseServiceKey) {\n supabase = createClient<Database>(supabaseUrl, supabaseServiceKey);\n }\n} catch (error) {\n logError('Failed to initialize Supabase client for scraper', error);\n}\n\ninterface FirebaseEvent {\n id: string;\n name: string;\n description: string;\n location: string;\n datetime: string;\n images?: string[];\n tickets: Array<{\n type: string;\n price: string;\n }>;\n createdAt: string;\n updateTime: string;\n}\n\ninterface ProcessedEvent {\n firebaseId: string;\n title: string;\n description: string;\n venue: string;\n startTime: string;\n endTime?: string;\n imageUrl?: string;\n category: string;\n priceRange: string;\n}\n\n/**\n * Authenticate with Firebase and get an ID token\n */\nasync function authenticateFirebase(): Promise<string | null> {\n try {\n const response = await fetch(`https://identitytoolkit.googleapis.com/v1/accounts:signInWithPassword?key=${FIREBASE_API_KEY}`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({\n email: FIREBASE_ADMIN_EMAIL,\n password: FIREBASE_ADMIN_PASSWORD,\n returnSecureToken: true,\n }),\n });\n\n if (!response.ok) {\n throw new Error(`Firebase auth failed: ${response.status} ${response.statusText}`);\n }\n\n const data = await response.json();\n return data.idToken;\n } catch (error) {\n logError('Firebase authentication failed', error);\n return null;\n }\n}\n\n/**\n * Fetch all events from Firebase Firestore\n */\nasync function fetchFirebaseEvents(idToken: string): Promise<FirebaseEvent[]> {\n try {\n const response = await fetch(\n `https://firestore.googleapis.com/v1/projects/${FIREBASE_PROJECT_ID}/databases/(default)/documents/events`,\n {\n headers: {\n 'Authorization': `Bearer ${idToken}`,\n },\n }\n );\n\n if (!response.ok) {\n throw new Error(`Firebase events fetch failed: ${response.status} ${response.statusText}`);\n }\n\n const data = await response.json();\n \n if (!data.documents) {\n return [];\n }\n\n return data.documents.map((doc: any) => {\n const fields = doc.fields;\n const documentId = doc.name.split('/').pop();\n \n return {\n id: documentId,\n name: fields.name?.stringValue || '',\n description: fields.description?.stringValue || '',\n location: fields.location?.stringValue || '',\n datetime: fields.datetime?.stringValue || '',\n images: fields.images?.arrayValue?.values?.map((v: any) => v.stringValue) || [],\n tickets: fields.tickets?.arrayValue?.values?.map((v: any) => ({\n type: v.mapValue.fields.type?.stringValue || '',\n price: v.mapValue.fields.price?.stringValue || '0',\n })) || [],\n createdAt: fields.createdAt?.timestampValue || doc.createTime,\n updateTime: doc.updateTime,\n };\n });\n } catch (error) {\n logError('Failed to fetch Firebase events', error);\n return [];\n }\n}\n\n/**\n * Determine event category based on content\n */\nfunction categorizeEvent(name: string, description: string): string {\n const content = (name + ' ' + description).toLowerCase();\n \n const categoryKeywords = {\n music: ['concert', 'band', 'music', 'guitar', 'song', 'album', 'tour', 'performance'],\n community: ['fair', 'festival', 'county', 'community', 'celebration', 'rodeo', 'carnival'],\n sports: ['rodeo', 'bull', 'riding', 'horse', 'competition', 'race', 'athletic'],\n arts: ['theater', 'theatre', 'art', 'dance', 'performance', 'show'],\n food: ['food', 'wine', 'tasting', 'dinner', 'culinary'],\n business: ['conference', 'meeting', 'workshop', 'seminar', 'networking'],\n };\n\n let bestCategory = 'community';\n let maxMatches = 0;\n\n for (const [category, keywords] of Object.entries(categoryKeywords)) {\n const matches = keywords.filter(keyword => content.includes(keyword)).length;\n if (matches > maxMatches) {\n maxMatches = matches;\n bestCategory = category;\n }\n }\n\n return bestCategory;\n}\n\n/**\n * Parse date from Firebase datetime string\n */\nfunction parseEventDate(datetime: string): { startTime: string; endTime?: string } {\n try {\n // Handle various date formats\n let date: Date;\n \n if (datetime.includes('August')) {\n // Parse formats like \"August 8, 2025\" or \"August 6-9, 2025\"\n const year = datetime.match(/202\\d/)?.[0] || new Date().getFullYear().toString();\n \n if (datetime.includes('-')) {\n // Range format like \"August 6-9, 2025\"\n const match = datetime.match(/(\\w+)\\s+(\\d+)-(\\d+),\\s*(\\d+)/);\n if (match) {\n const [, month, startDay, endDay, yr] = match;\n const startDate = new Date(`${month} ${startDay}, ${yr}`);\n const endDate = new Date(`${month} ${endDay}, ${yr}`);\n \n return {\n startTime: startDate.toISOString(),\n endTime: endDate.toISOString(),\n };\n }\n } else {\n // Single date format like \"August 8, 2025\"\n date = new Date(datetime);\n if (!isNaN(date.getTime())) {\n return {\n startTime: date.toISOString(),\n };\n }\n }\n }\n \n // Try direct date parsing\n date = new Date(datetime);\n if (!isNaN(date.getTime())) {\n return {\n startTime: date.toISOString(),\n };\n }\n \n // Default to future date if parsing fails\n const futureDate = new Date();\n futureDate.setDate(futureDate.getDate() + 30);\n return {\n startTime: futureDate.toISOString(),\n };\n } catch (error) {\n // Fallback to future date\n const futureDate = new Date();\n futureDate.setDate(futureDate.getDate() + 30);\n return {\n startTime: futureDate.toISOString(),\n };\n }\n}\n\n/**\n * Calculate price range from tickets\n */\nfunction calculatePriceRange(tickets: Array<{ type: string; price: string }>): string {\n if (tickets.length === 0) {\n return 'Price TBA';\n }\n \n const prices = tickets\n .map(ticket => parseFloat(ticket.price))\n .filter(price => !isNaN(price))\n .sort((a, b) => a - b);\n \n if (prices.length === 0) {\n return 'Price TBA';\n }\n \n const min = prices[0];\n const max = prices[prices.length - 1];\n \n if (min === max) {\n return `$${min.toFixed(2)}`;\n }\n \n return `$${min.toFixed(2)} - $${max.toFixed(2)}`;\n}\n\n/**\n * Process Firebase event into our format\n */\nfunction processFirebaseEvent(firebaseEvent: FirebaseEvent): ProcessedEvent {\n const { startTime, endTime } = parseEventDate(firebaseEvent.datetime);\n \n return {\n firebaseId: firebaseEvent.id,\n title: firebaseEvent.name,\n description: firebaseEvent.description.substring(0, 500), // Limit description length\n venue: firebaseEvent.location,\n startTime,\n endTime,\n imageUrl: firebaseEvent.images && firebaseEvent.images.length > 0 ? firebaseEvent.images[0] : undefined,\n category: categorizeEvent(firebaseEvent.name, firebaseEvent.description),\n priceRange: calculatePriceRange(firebaseEvent.tickets),\n };\n}\n\n/**\n * Load last sync timestamp\n */\nasync function loadLastSyncTime(): Promise<string | null> {\n try {\n return await fs.readFile(LAST_SYNC_FILE, 'utf-8');\n } catch (error) {\n return null;\n }\n}\n\n/**\n * Save last sync timestamp\n */\nasync function saveLastSyncTime(timestamp: string): Promise<void> {\n try {\n await fs.mkdir(path.dirname(LAST_SYNC_FILE), { recursive: true });\n await fs.writeFile(LAST_SYNC_FILE, timestamp);\n } catch (error) {\n logError('Failed to save last sync time', error);\n }\n}\n\n/**\n * Check if event already exists in our database\n */\nasync function eventExistsInDatabase(firebaseId: string): Promise<boolean> {\n if (!supabase) {\n\n return false;\n }\n \n try {\n // Check for events with this Firebase ID in the description\n const { data, error } = await supabase\n .from('events')\n .select('id, title, external_source')\n .eq('external_source', 'firebase')\n .eq('organization_id', SCRAPER_ORGANIZATION_ID)\n .ilike('description', `%firebase_id:${firebaseId}%`)\n .single();\n \n if (error) {\n\n return false;\n }\n \n if (data) {\n\n return true;\n }\n \n return false;\n } catch (error) {\n\n return false;\n }\n}\n\n/**\n * Add Firebase event to our database\n */\nasync function addEventToDatabase(processedEvent: ProcessedEvent): Promise<boolean> {\n if (!supabase) {\n\n logError('Supabase client not available for adding Firebase event');\n return false;\n }\n \n try {\n // Generate a proper UUID for the event ID (can't use string concatenation)\n const eventId = crypto.randomUUID();\n\n // Insert the new event as featured and public\n const { error } = await supabase\n .from('events')\n .insert({\n id: eventId,\n title: processedEvent.title,\n slug: `firebase-event-${processedEvent.firebaseId.toLowerCase()}`,\n description: `${processedEvent.description}\\n\\n[firebase_id:${processedEvent.firebaseId}]`, // Hidden identifier\n venue: processedEvent.venue,\n venue_id: BCT_VENUE_ID,\n start_time: processedEvent.startTime,\n end_time: processedEvent.endTime,\n image_url: processedEvent.imageUrl,\n category: processedEvent.category,\n is_featured: true,\n is_public: true,\n is_published: true,\n external_source: 'firebase',\n organization_id: SCRAPER_ORGANIZATION_ID,\n created_by: SCRAPER_ORGANIZATION_ID,\n });\n \n if (error) {\n\n logError('Failed to insert Firebase event into database', error);\n return false;\n }\n\n return true;\n \n } catch (error) {\n\n logError('Error adding Firebase event to database', error);\n return false;\n }\n}\n\n/**\n * Main Firebase scraper function\n */\nexport async function runFirebaseEventScraper(): Promise<{ success: boolean; message: string; newEvents?: ProcessedEvent[] }> {\n try {\n\n // Authenticate with Firebase\n const idToken = await authenticateFirebase();\n if (!idToken) {\n return {\n success: false,\n message: 'Failed to authenticate with Firebase',\n };\n }\n\n // Ensure scraper organization exists\n try {\n const orgInitialized = await initializeScraperOrganization();\n if (!orgInitialized) {\n return {\n success: false,\n message: 'Failed to initialize Black Canyon Tickets organization',\n debug: { step: 'organization_init_failed' },\n };\n }\n } catch (orgError) {\n return {\n success: false,\n message: `Organization initialization error: ${orgError instanceof Error ? orgError.message : 'Unknown error'}`,\n debug: { step: 'organization_init_exception', error: orgError },\n };\n }\n\n // Fetch events from Firebase\n const firebaseEvents = await fetchFirebaseEvents(idToken);\n\n if (firebaseEvents.length === 0) {\n return {\n success: true,\n message: 'No events found in Firebase',\n };\n }\n \n // Process and filter new events\n const newEvents: ProcessedEvent[] = [];\n\n for (const firebaseEvent of firebaseEvents) {\n\n const exists = await eventExistsInDatabase(firebaseEvent.id);\n \n if (!exists) {\n\n const processedEvent = processFirebaseEvent(firebaseEvent);\n const added = await addEventToDatabase(processedEvent);\n \n if (added) {\n newEvents.push(processedEvent);\n\n } else {\n\n }\n } else {\n\n }\n }\n \n // Save sync timestamp\n await saveLastSyncTime(new Date().toISOString());\n \n // Log successful sync\n logSecurityEvent({\n type: 'firebase_scraper_success',\n severity: 'info',\n details: {\n totalEvents: firebaseEvents.length,\n newEvents: newEvents.length,\n syncTime: new Date().toISOString(),\n },\n });\n \n const message = newEvents.length > 0\n ? `Successfully synced ${newEvents.length} new events from Firebase`\n : `All Firebase events are already synchronized (found ${firebaseEvents.length} events in Firebase)`;\n \n return {\n success: true,\n message,\n newEvents: newEvents.length > 0 ? newEvents : undefined,\n debug: {\n firebaseEventsCount: firebaseEvents.length,\n firebaseEventTitles: firebaseEvents.map(e => e.name),\n newEventsCount: newEvents.length,\n processedEvents: firebaseEvents.map(e => ({\n name: e.name,\n id: e.id,\n processed: true\n })),\n },\n };\n \n } catch (error) {\n logError('Firebase event scraper failed', error);\n \n logSecurityEvent({\n type: 'firebase_scraper_error',\n severity: 'high',\n details: { error: error instanceof Error ? error.message : 'Unknown error' },\n });\n \n return {\n success: false,\n message: 'Firebase event scraper encountered an error',\n };\n }\n}\n\n/**\n * Initialize scraper organization if it doesn't exist\n */\nexport async function initializeScraperOrganization(): Promise<boolean> {\n if (!supabase) {\n return false;\n }\n \n try {\n // Check if scraper organization exists\n\n const { data: existingOrg, error: checkError } = await supabase\n .from('organizations')\n .select('id')\n .eq('id', SCRAPER_ORGANIZATION_ID)\n .single();\n \n if (existingOrg) {\n\n return true;\n }\n\n // Create scraper organization\n const { error: orgError } = await supabase\n .from('organizations')\n .insert({\n id: SCRAPER_ORGANIZATION_ID,\n name: 'Black Canyon Tickets',\n logo: null,\n stripe_account_id: null,\n });\n \n if (orgError) {\n\n logError('Failed to create scraper organization', orgError);\n return false;\n }\n \n // Create scraper user\n const { error: userError } = await supabase\n .from('users')\n .insert({\n id: SCRAPER_ORGANIZATION_ID,\n email: 'scraper@blackcanyontickets.com',\n name: 'Black Canyon Tickets Event Manager',\n organization_id: SCRAPER_ORGANIZATION_ID,\n });\n \n if (userError) {\n\n logError('Failed to create scraper user', userError);\n return false;\n }\n\n return true;\n \n } catch (error) {\n logError('Failed to initialize scraper organization', error);\n return false;\n }\n}","usedDeprecatedRules":[]},{"filePath":"/home/tyler/apps/bct-whitelabel/src/lib/flyer-generator.ts","messages":[{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":20,"column":18,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":20,"endColumn":21,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[432,435],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[432,435],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":25,"column":20,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":25,"endColumn":23,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[536,539],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[536,539],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'event' is defined but never used. Allowed unused args must match /^_/u.","line":187,"column":41,"nodeType":null,"messageId":"unusedVar","endLine":187,"endColumn":46},{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":365,"column":16,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":365,"endColumn":19,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[10182,10185],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[10182,10185],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":4,"fixableErrorCount":0,"fixableWarningCount":0,"source":"import { canvasImageGenerator } from './canvas-image-generator';\nimport { qrGenerator } from './qr-generator';\n\ninterface FlyerDesign {\n title: string;\n imageUrl: string;\n dimensions: { width: number; height: number };\n style: string;\n}\n\ninterface EventData {\n id: string;\n title: string;\n description: string;\n venue: string;\n start_time: string;\n end_time: string;\n slug: string;\n image_url?: string;\n social_links?: any;\n website_url?: string;\n organizations: {\n name: string;\n logo?: string;\n social_links?: any;\n website_url?: string;\n };\n}\n\nclass FlyerGenerator {\n private flyerDimensions = {\n poster: { width: 1080, height: 1350 }, // 4:5 ratio - good for printing\n social: { width: 1080, height: 1080 }, // Square - good for Instagram\n story: { width: 1080, height: 1920 }, // 9:16 ratio - good for stories\n landscape: { width: 1920, height: 1080 }, // 16:9 ratio - good for digital displays\n a4: { width: 2480, height: 3508 } // A4 size for high-quality printing\n };\n\n /**\n * Generate multiple flyer designs for the event\n */\n async generateFlyers(event: EventData): Promise<FlyerDesign[]> {\n const flyers: FlyerDesign[] = [];\n\n // Generate QR code for flyers\n const qrCode = await qrGenerator.generateEventQR(event.slug, {\n size: 300,\n color: { dark: '#000000', light: '#FFFFFF' }\n });\n\n // Generate different styles and formats\n const styles = ['modern', 'classic', 'minimal'];\n const formats = ['poster', 'social', 'landscape'];\n\n for (const style of styles) {\n for (const format of formats) {\n const dimensions = this.flyerDimensions[format];\n const flyer = await this.generateFlyer(event, style, format, dimensions, qrCode.dataUrl);\n flyers.push(flyer);\n }\n }\n\n // Generate high-resolution print version\n const printFlyer = await this.generateFlyer(\n event, \n 'modern', \n 'a4', \n this.flyerDimensions.a4, \n qrCode.dataUrl\n );\n flyers.push(printFlyer);\n\n return flyers;\n }\n\n /**\n * Generate a single flyer design\n */\n private async generateFlyer(\n event: EventData,\n style: string,\n format: string,\n dimensions: { width: number; height: number },\n qrCodeDataUrl: string\n ): Promise<FlyerDesign> {\n const config = {\n width: dimensions.width,\n height: dimensions.height,\n style: style as 'modern' | 'classic' | 'minimal',\n event,\n qrCode: qrCodeDataUrl,\n backgroundColor: this.getStyleColors(style).backgroundColor,\n textColor: this.getStyleColors(style).textColor,\n accentColor: this.getStyleColors(style).accentColor\n };\n\n const imageUrl = await canvasImageGenerator.generateFlyer(config);\n\n return {\n title: `${this.capitalizeFirst(style)} ${this.capitalizeFirst(format)} Flyer`,\n imageUrl,\n dimensions,\n style\n };\n }\n\n /**\n * Get color scheme for different styles\n */\n private getStyleColors(style: string) {\n const colorSchemes = {\n modern: {\n backgroundColor: ['#667eea', '#764ba2'], // Purple gradient\n textColor: '#FFFFFF',\n accentColor: '#FF6B6B'\n },\n classic: {\n backgroundColor: ['#2C3E50', '#34495E'], // Dark blue gradient\n textColor: '#FFFFFF',\n accentColor: '#E74C3C'\n },\n minimal: {\n backgroundColor: '#FFFFFF',\n textColor: '#2C3E50',\n accentColor: '#3498DB'\n },\n elegant: {\n backgroundColor: ['#232526', '#414345'], // Dark gradient\n textColor: '#F8F9FA',\n accentColor: '#FD79A8'\n },\n vibrant: {\n backgroundColor: ['#FF6B6B', '#4ECDC4'], // Coral to teal\n textColor: '#FFFFFF',\n accentColor: '#45B7D1'\n }\n };\n\n return colorSchemes[style] || colorSchemes.modern;\n }\n\n /**\n * Generate themed flyer sets\n */\n async generateThemedSet(event: EventData, theme: string): Promise<FlyerDesign[]> {\n const flyers: FlyerDesign[] = [];\n \n // Generate QR code\n const qrCode = await qrGenerator.generateEventQR(event.slug, {\n size: 300,\n color: { dark: '#000000', light: '#FFFFFF' }\n });\n\n const themeConfig = this.getThemeConfig(theme, event);\n \n // Generate different formats for the theme\n for (const [formatName, dimensions] of Object.entries(this.flyerDimensions)) {\n if (formatName === 'story') continue; // Skip story format for themed sets\n \n const config = {\n width: dimensions.width,\n height: dimensions.height,\n style: themeConfig.style,\n event,\n qrCode: qrCode.dataUrl,\n backgroundColor: themeConfig.colors.backgroundColor,\n textColor: themeConfig.colors.textColor,\n accentColor: themeConfig.colors.accentColor\n };\n\n const imageUrl = await canvasImageGenerator.generateFlyer(config);\n\n flyers.push({\n title: `${this.capitalizeFirst(theme)} ${this.capitalizeFirst(formatName)} Flyer`,\n imageUrl,\n dimensions,\n style: theme\n });\n }\n\n return flyers;\n }\n\n /**\n * Get theme-specific configuration\n */\n private getThemeConfig(theme: string, event: EventData) {\n const themes = {\n corporate: {\n style: 'minimal' as const,\n colors: {\n backgroundColor: '#FFFFFF',\n textColor: '#2C3E50',\n accentColor: '#3498DB'\n }\n },\n party: {\n style: 'modern' as const,\n colors: {\n backgroundColor: ['#FF6B6B', '#4ECDC4'],\n textColor: '#FFFFFF',\n accentColor: '#FFD93D'\n }\n },\n wedding: {\n style: 'classic' as const,\n colors: {\n backgroundColor: ['#F8BBD9', '#E8F5E8'],\n textColor: '#2C3E50',\n accentColor: '#E91E63'\n }\n },\n concert: {\n style: 'modern' as const,\n colors: {\n backgroundColor: ['#000000', '#434343'],\n textColor: '#FFFFFF',\n accentColor: '#FF0080'\n }\n },\n gala: {\n style: 'classic' as const,\n colors: {\n backgroundColor: ['#232526', '#414345'],\n textColor: '#F8F9FA',\n accentColor: '#FFD700'\n }\n }\n };\n\n return themes[theme] || themes.corporate;\n }\n\n /**\n * Generate social media story versions\n */\n async generateStoryFlyers(event: EventData): Promise<FlyerDesign[]> {\n const storyFlyers: FlyerDesign[] = [];\n \n // Generate QR code optimized for mobile\n const qrCode = await qrGenerator.generateEventQR(event.slug, {\n size: 250,\n color: { dark: '#000000', light: '#FFFFFF' }\n });\n\n const storyStyles = ['modern', 'vibrant', 'elegant'];\n \n for (const style of storyStyles) {\n const colors = this.getStyleColors(style);\n const config = {\n width: this.flyerDimensions.story.width,\n height: this.flyerDimensions.story.height,\n style: style as 'modern' | 'classic' | 'minimal',\n event,\n qrCode: qrCode.dataUrl,\n backgroundColor: colors.backgroundColor,\n textColor: colors.textColor,\n accentColor: colors.accentColor\n };\n\n const imageUrl = await canvasImageGenerator.generateFlyer(config);\n\n storyFlyers.push({\n title: `${this.capitalizeFirst(style)} Story Flyer`,\n imageUrl,\n dimensions: this.flyerDimensions.story,\n style\n });\n }\n\n return storyFlyers;\n }\n\n /**\n * Generate print-ready flyers with bleed\n */\n async generatePrintFlyers(event: EventData): Promise<FlyerDesign[]> {\n const printFlyers: FlyerDesign[] = [];\n \n // Generate high-resolution QR code for print\n const qrCode = await qrGenerator.generateEventQR(event.slug, {\n size: 600, // High resolution for print\n color: { dark: '#000000', light: '#FFFFFF' }\n });\n\n // A4 with bleed (A4 + 3mm bleed on each side)\n const a4WithBleed = { width: 2551, height: 3579 };\n \n // US Letter with bleed\n const letterWithBleed = { width: 2551, height: 3301 };\n\n const printSizes = [\n { name: 'A4 with Bleed', dimensions: a4WithBleed },\n { name: 'US Letter with Bleed', dimensions: letterWithBleed },\n { name: 'Poster 11x17', dimensions: { width: 3300, height: 5100 } },\n { name: 'Poster 18x24', dimensions: { width: 5400, height: 7200 } }\n ];\n\n for (const size of printSizes) {\n const colors = this.getStyleColors('modern');\n const config = {\n width: size.dimensions.width,\n height: size.dimensions.height,\n style: 'modern' as const,\n event,\n qrCode: qrCode.dataUrl,\n backgroundColor: colors.backgroundColor,\n textColor: colors.textColor,\n accentColor: colors.accentColor\n };\n\n const imageUrl = await canvasImageGenerator.generateFlyer(config);\n\n printFlyers.push({\n title: `Print Ready - ${size.name}`,\n imageUrl,\n dimensions: size.dimensions,\n style: 'print'\n });\n }\n\n return printFlyers;\n }\n\n /**\n * Get recommended flyer formats for event type\n */\n getRecommendedFormats(eventType: string): string[] {\n const recommendations = {\n conference: ['poster', 'landscape', 'a4'],\n wedding: ['poster', 'social', 'story'],\n concert: ['poster', 'social', 'story', 'landscape'],\n gala: ['poster', 'social', 'a4'],\n workshop: ['poster', 'landscape'],\n party: ['social', 'story', 'poster'],\n corporate: ['landscape', 'poster', 'a4']\n };\n\n return recommendations[eventType] || ['poster', 'social', 'landscape'];\n }\n\n /**\n * Capitalize first letter of a string\n */\n private capitalizeFirst(str: string): string {\n return str.charAt(0).toUpperCase() + str.slice(1);\n }\n\n /**\n * Get optimal image dimensions for different use cases\n */\n getOptimalDimensions(useCase: string): { width: number; height: number } {\n return this.flyerDimensions[useCase] || this.flyerDimensions.poster;\n }\n\n /**\n * Generate custom flyer with specific requirements\n */\n async generateCustomFlyer(\n event: EventData,\n requirements: {\n width: number;\n height: number;\n style: string;\n colors?: any;\n includeQR?: boolean;\n includeLogo?: boolean;\n }\n ): Promise<FlyerDesign> {\n let qrCodeDataUrl = '';\n \n if (requirements.includeQR !== false) {\n const qrCode = await qrGenerator.generateEventQR(event.slug, {\n size: Math.min(requirements.width / 6, 300),\n color: { dark: '#000000', light: '#FFFFFF' }\n });\n qrCodeDataUrl = qrCode.dataUrl;\n }\n\n const colors = requirements.colors || this.getStyleColors(requirements.style);\n \n const config = {\n width: requirements.width,\n height: requirements.height,\n style: requirements.style as 'modern' | 'classic' | 'minimal',\n event,\n qrCode: qrCodeDataUrl,\n backgroundColor: colors.backgroundColor,\n textColor: colors.textColor,\n accentColor: colors.accentColor\n };\n\n const imageUrl = await canvasImageGenerator.generateFlyer(config);\n\n return {\n title: `Custom ${requirements.style} Flyer`,\n imageUrl,\n dimensions: { width: requirements.width, height: requirements.height },\n style: requirements.style\n };\n }\n}\n\nexport const flyerGenerator = new FlyerGenerator();","usedDeprecatedRules":[]},{"filePath":"/home/tyler/apps/bct-whitelabel/src/lib/geolocation.ts","messages":[{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used. Allowed unused args must match /^_/u.","line":71,"column":10,"nodeType":null,"messageId":"unusedVar","endLine":71,"endColumn":15},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":100,"column":14,"nodeType":null,"messageId":"unusedVar","endLine":100,"endColumn":19},{"ruleId":"no-empty","severity":1,"message":"Empty block statement.","line":100,"column":21,"nodeType":"BlockStatement","messageId":"unexpected","endLine":102,"endColumn":6,"suggestions":[{"messageId":"suggestComment","data":{"type":"block"},"fix":{"range":[2593,2599],"text":" /* empty */ "},"desc":"Add comment inside empty block statement."}]},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":127,"column":14,"nodeType":null,"messageId":"unusedVar","endLine":127,"endColumn":19},{"ruleId":"no-empty","severity":1,"message":"Empty block statement.","line":127,"column":21,"nodeType":"BlockStatement","messageId":"unexpected","endLine":129,"endColumn":6,"suggestions":[{"messageId":"suggestComment","data":{"type":"block"},"fix":{"range":[3591,3597],"text":" /* empty */ "},"desc":"Add comment inside empty block statement."}]},{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":133,"column":40,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":133,"endColumn":43,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[3660,3663],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[3660,3663],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"no-empty","severity":1,"message":"Empty block statement.","line":157,"column":18,"nodeType":"BlockStatement","messageId":"unexpected","endLine":159,"endColumn":8,"suggestions":[{"messageId":"suggestComment","data":{"type":"block"},"fix":{"range":[4662,4670],"text":" /* empty */ "},"desc":"Add comment inside empty block statement."}]},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":160,"column":14,"nodeType":null,"messageId":"unusedVar","endLine":160,"endColumn":19},{"ruleId":"no-empty","severity":1,"message":"Empty block statement.","line":160,"column":21,"nodeType":"BlockStatement","messageId":"unexpected","endLine":162,"endColumn":6,"suggestions":[{"messageId":"suggestComment","data":{"type":"block"},"fix":{"range":[4693,4699],"text":" /* empty */ "},"desc":"Add comment inside empty block statement."}]},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":195,"column":14,"nodeType":null,"messageId":"unusedVar","endLine":195,"endColumn":19},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":207,"column":14,"nodeType":null,"messageId":"unusedVar","endLine":207,"endColumn":19},{"ruleId":"no-empty","severity":1,"message":"Empty block statement.","line":207,"column":21,"nodeType":"BlockStatement","messageId":"unexpected","endLine":209,"endColumn":6,"suggestions":[{"messageId":"suggestComment","data":{"type":"block"},"fix":{"range":[5960,5966],"text":" /* empty */ "},"desc":"Add comment inside empty block statement."}]}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":12,"fixableErrorCount":0,"fixableWarningCount":0,"source":"import { supabase } from './supabase';\n\nexport interface LocationData {\n latitude: number;\n longitude: number;\n city?: string;\n state?: string;\n country?: string;\n zipCode?: string;\n accuracy?: number;\n source: 'gps' | 'ip_geolocation' | 'manual';\n}\n\nexport interface UserLocationPreference {\n userId?: string;\n sessionId: string;\n preferredLatitude: number;\n preferredLongitude: number;\n preferredCity?: string;\n preferredState?: string;\n preferredCountry?: string;\n preferredZipCode?: string;\n searchRadiusMiles: number;\n locationSource: 'gps' | 'manual' | 'ip_geolocation';\n}\n\nexport class GeolocationService {\n private static instance: GeolocationService;\n private currentLocation: LocationData | null = null;\n private locationWatchers: ((location: LocationData | null) => void)[] = [];\n\n static getInstance(): GeolocationService {\n if (!GeolocationService.instance) {\n GeolocationService.instance = new GeolocationService();\n }\n return GeolocationService.instance;\n }\n\n async getCurrentLocation(): Promise<LocationData | null> {\n return new Promise((resolve) => {\n if (this.currentLocation) {\n resolve(this.currentLocation);\n return;\n }\n\n if (!navigator.geolocation) {\n\n resolve(null);\n return;\n }\n\n const options = {\n enableHighAccuracy: true,\n timeout: 10000,\n maximumAge: 300000 // 5 minutes\n };\n\n navigator.geolocation.getCurrentPosition(\n (position) => {\n const location: LocationData = {\n latitude: position.coords.latitude,\n longitude: position.coords.longitude,\n accuracy: position.coords.accuracy,\n source: 'gps'\n };\n\n this.currentLocation = location;\n this.notifyWatchers(location);\n resolve(location);\n },\n (error) => {\n\n resolve(null);\n },\n options\n );\n });\n }\n\n async getLocationFromIP(): Promise<LocationData | null> {\n try {\n const response = await fetch('https://ipapi.co/json/');\n const data = await response.json();\n \n if (data.latitude && data.longitude) {\n const location: LocationData = {\n latitude: data.latitude,\n longitude: data.longitude,\n city: data.city,\n state: data.region,\n country: data.country_code,\n zipCode: data.postal,\n source: 'ip_geolocation'\n };\n\n this.currentLocation = location;\n this.notifyWatchers(location);\n return location;\n }\n } catch (error) {\n\n }\n return null;\n }\n\n async geocodeAddress(address: string): Promise<LocationData | null> {\n try {\n const response = await fetch(`https://api.mapbox.com/geocoding/v5/mapbox.places/${encodeURIComponent(address)}.json?access_token=${import.meta.env.PUBLIC_MAPBOX_TOKEN}&country=US&types=place,postcode,address`);\n const data = await response.json();\n \n if (data.features && data.features.length > 0) {\n const feature = data.features[0];\n const [longitude, latitude] = feature.center;\n \n const location: LocationData = {\n latitude,\n longitude,\n city: this.extractContextValue(feature.context, 'place'),\n state: this.extractContextValue(feature.context, 'region'),\n country: this.extractContextValue(feature.context, 'country'),\n zipCode: this.extractContextValue(feature.context, 'postcode'),\n source: 'manual'\n };\n\n return location;\n }\n } catch (error) {\n\n }\n return null;\n }\n\n private extractContextValue(context: any[], type: string): string | undefined {\n if (!context) return undefined;\n const item = context.find(c => c.id.startsWith(type));\n return item ? item.text : undefined;\n }\n\n async saveUserLocationPreference(preference: UserLocationPreference): Promise<void> {\n try {\n const { error } = await supabase\n .from('user_location_preferences')\n .upsert({\n user_id: preference.userId,\n session_id: preference.sessionId,\n preferred_latitude: preference.preferredLatitude,\n preferred_longitude: preference.preferredLongitude,\n preferred_city: preference.preferredCity,\n preferred_state: preference.preferredState,\n preferred_country: preference.preferredCountry,\n preferred_zip_code: preference.preferredZipCode,\n search_radius_miles: preference.searchRadiusMiles,\n location_source: preference.locationSource,\n updated_at: new Date().toISOString()\n });\n\n if (error) {\n\n }\n } catch (error) {\n\n }\n }\n\n async getUserLocationPreference(userId?: string, sessionId?: string): Promise<UserLocationPreference | null> {\n try {\n let query = supabase.from('user_location_preferences').select('*');\n \n if (userId) {\n query = query.eq('user_id', userId);\n } else if (sessionId) {\n query = query.eq('session_id', sessionId);\n } else {\n return null;\n }\n\n const { data, error } = await query.single();\n\n if (error || !data) {\n return null;\n }\n\n return {\n userId: data.user_id,\n sessionId: data.session_id,\n preferredLatitude: data.preferred_latitude,\n preferredLongitude: data.preferred_longitude,\n preferredCity: data.preferred_city,\n preferredState: data.preferred_state,\n preferredCountry: data.preferred_country,\n preferredZipCode: data.preferred_zip_code,\n searchRadiusMiles: data.search_radius_miles,\n locationSource: data.location_source\n };\n } catch (error) {\n\n return null;\n }\n }\n\n async requestLocationPermission(): Promise<LocationData | null> {\n try {\n const location = await this.getCurrentLocation();\n if (location) {\n return location;\n }\n } catch (error) {\n\n }\n\n return await this.getLocationFromIP();\n }\n\n watchLocation(callback: (location: LocationData | null) => void): () => void {\n this.locationWatchers.push(callback);\n \n // Immediately call with current location if available\n if (this.currentLocation) {\n callback(this.currentLocation);\n }\n\n // Return unsubscribe function\n return () => {\n this.locationWatchers = this.locationWatchers.filter(w => w !== callback);\n };\n }\n\n private notifyWatchers(location: LocationData | null): void {\n this.locationWatchers.forEach(callback => callback(location));\n }\n\n clearCurrentLocation(): void {\n this.currentLocation = null;\n this.notifyWatchers(null);\n }\n\n calculateDistance(lat1: number, lon1: number, lat2: number, lon2: number): number {\n const R = 3959; // Earth's radius in miles\n const dLat = this.toRad(lat2 - lat1);\n const dLon = this.toRad(lon2 - lon1);\n const a = \n Math.sin(dLat/2) * Math.sin(dLat/2) +\n Math.cos(this.toRad(lat1)) * Math.cos(this.toRad(lat2)) * \n Math.sin(dLon/2) * Math.sin(dLon/2);\n const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));\n return R * c;\n }\n\n private toRad(value: number): number {\n return value * Math.PI / 180;\n }\n}\n\nexport const geolocationService = GeolocationService.getInstance();","usedDeprecatedRules":[]},{"filePath":"/home/tyler/apps/bct-whitelabel/src/lib/inventory.ts","messages":[{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":34,"column":10,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":34,"endColumn":13,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[636,639],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[636,639],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":1,"fixableErrorCount":0,"fixableWarningCount":0,"source":"// Client-side inventory management library\n\nexport interface TicketAvailability {\n available: number;\n total: number;\n reserved: number;\n sold: number;\n is_available: boolean;\n}\n\nexport interface TicketReservation {\n id: string;\n ticket_type_id: string;\n quantity: number;\n expires_at: string;\n seat_id?: string;\n status: string;\n}\n\nexport interface PurchaseItem {\n ticket_type_id: string;\n quantity: number;\n unit_price: number;\n seat_id?: string;\n}\n\nexport interface PurchaseAttempt {\n id: string;\n session_id: string;\n total_amount: number;\n platform_fee: number;\n expires_at: string;\n status: string;\n items: any[];\n reservations: string[];\n}\n\nclass InventoryManager {\n private baseUrl: string;\n public sessionId: string;\n private reservations: Map<string, TicketReservation> = new Map();\n\n constructor() {\n this.baseUrl = '/api/inventory';\n this.sessionId = this.getOrCreateSessionId();\n }\n\n private getOrCreateSessionId(): string {\n if (typeof sessionStorage === 'undefined') {\n // Fallback for server-side rendering\n return 'session_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9);\n }\n \n let sessionId = sessionStorage.getItem('ticket_session_id');\n if (!sessionId) {\n sessionId = 'session_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9);\n sessionStorage.setItem('ticket_session_id', sessionId);\n }\n return sessionId;\n }\n\n async getAvailability(ticketTypeId: string): Promise<TicketAvailability> {\n const url = `${this.baseUrl}/availability/${encodeURIComponent(ticketTypeId)}`;\n \n const response = await fetch(url);\n const data = await response.json();\n \n if (!data.success) {\n throw new Error(data.error || 'Failed to get availability');\n }\n \n return data.availability;\n }\n\n async reserveTickets(\n ticketTypeId: string, \n quantity: number, \n holdMinutes: number = 15,\n seatIds?: string[]\n ): Promise<TicketReservation> {\n const response = await fetch(`${this.baseUrl}/reserve`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({\n ticket_type_id: ticketTypeId,\n quantity,\n session_id: this.sessionId,\n hold_minutes: holdMinutes,\n seat_ids: seatIds\n })\n });\n\n const data = await response.json();\n \n if (!data.success) {\n throw new Error(data.error || 'Failed to reserve tickets');\n }\n\n // Store reservation locally\n this.reservations.set(data.reservation.id, data.reservation);\n \n // Set up auto-release timer\n this.scheduleAutoRelease(data.reservation);\n \n return data.reservation;\n }\n\n async releaseReservation(reservationId: string): Promise<void> {\n const response = await fetch(`${this.baseUrl}/release`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({\n reservation_id: reservationId,\n session_id: this.sessionId\n })\n });\n\n const data = await response.json();\n \n if (!data.success) {\n throw new Error(data.error || 'Failed to release reservation');\n }\n\n // Remove from local storage\n this.reservations.delete(reservationId);\n }\n\n async createPurchaseAttempt(\n eventId: string,\n purchaserEmail: string,\n purchaserName: string,\n items: PurchaseItem[],\n platformFee: number = 0,\n holdMinutes: number = 30\n ): Promise<PurchaseAttempt> {\n const response = await fetch(`${this.baseUrl}/purchase-attempt`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({\n session_id: this.sessionId,\n event_id: eventId,\n purchaser_email: purchaserEmail,\n purchaser_name: purchaserName,\n items,\n platform_fee: platformFee,\n hold_minutes: holdMinutes\n })\n });\n\n const data = await response.json();\n \n if (!data.success) {\n throw new Error(data.error || 'Failed to create purchase attempt');\n }\n\n return data.purchase_attempt;\n }\n\n private scheduleAutoRelease(reservation: TicketReservation): void {\n const expiresAt = new Date(reservation.expires_at).getTime();\n const now = Date.now();\n const timeUntilExpiry = expiresAt - now;\n\n if (timeUntilExpiry > 0) {\n setTimeout(() => {\n this.reservations.delete(reservation.id);\n // Optionally notify user that reservation expired\n this.onReservationExpired?.(reservation);\n }, timeUntilExpiry);\n }\n }\n\n // Get all active reservations for this session\n getActiveReservations(): TicketReservation[] {\n return Array.from(this.reservations.values());\n }\n\n // Release all active reservations\n async releaseAllReservations(): Promise<void> {\n const promises = Array.from(this.reservations.keys()).map(id => \n this.releaseReservation(id).catch(console.error)\n );\n await Promise.all(promises);\n }\n\n // Get time remaining for a reservation in milliseconds\n getTimeRemaining(reservation: TicketReservation): number {\n const expiresAt = new Date(reservation.expires_at).getTime();\n const now = Date.now();\n return Math.max(0, expiresAt - now);\n }\n\n // Format time remaining as a readable string\n formatTimeRemaining(reservation: TicketReservation): string {\n const ms = this.getTimeRemaining(reservation);\n const minutes = Math.floor(ms / 60000);\n const seconds = Math.floor((ms % 60000) / 1000);\n return `${minutes}:${seconds.toString().padStart(2, '0')}`;\n }\n\n // Callback for when a reservation expires\n onReservationExpired?: (reservation: TicketReservation) => void;\n}\n\n// Singleton instance\nexport const inventoryManager = new InventoryManager();\n\n// Only run browser-specific code if we're in the browser\nif (typeof window !== 'undefined') {\n // Cleanup reservations when page unloads\n window.addEventListener('beforeunload', () => {\n inventoryManager.releaseAllReservations().catch(console.error);\n });\n\n // Auto-cleanup expired reservations every minute\n setInterval(() => {\n const now = Date.now();\n for (const [id, reservation] of inventoryManager['reservations']) {\n if (new Date(reservation.expires_at).getTime() <= now) {\n inventoryManager['reservations'].delete(id);\n }\n }\n }, 60000);\n}","usedDeprecatedRules":[]},{"filePath":"/home/tyler/apps/bct-whitelabel/src/lib/logger.ts","messages":[{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'captureMessage' is defined but never used. Allowed unused vars must match /^_/u.","line":2,"column":28,"nodeType":null,"messageId":"unusedVar","endLine":2,"endColumn":42},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'addBreadcrumb' is defined but never used. Allowed unused vars must match /^_/u.","line":2,"column":44,"nodeType":null,"messageId":"unusedVar","endLine":2,"endColumn":57},{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":74,"column":28,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":74,"endColumn":31,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[1642,1645],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[1642,1645],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":159,"column":28,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":159,"endColumn":31,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[3851,3854],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[3851,3854],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":181,"column":38,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":181,"endColumn":41,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[4422,4425],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[4422,4425],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":250,"column":30,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":250,"endColumn":33,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[6188,6191],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[6188,6191],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":251,"column":30,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":251,"endColumn":33,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[6223,6226],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[6223,6226],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":7,"fixableErrorCount":0,"fixableWarningCount":0,"source":"import winston from 'winston';\nimport { captureException, captureMessage, addBreadcrumb } from './sentry';\n\n// Define log levels\nconst logLevels = {\n error: 0,\n warn: 1,\n info: 2,\n http: 3,\n debug: 4,\n};\n\n// Define log colors\nconst logColors = {\n error: 'red',\n warn: 'yellow',\n info: 'green',\n http: 'magenta',\n debug: 'white',\n};\n\n// Add colors to winston\nwinston.addColors(logColors);\n\n// Define log format\nconst logFormat = winston.format.combine(\n winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss:ms' }),\n winston.format.colorize({ all: true }),\n winston.format.printf(\n (info) => `${info.timestamp} ${info.level}: ${info.message}`\n ),\n);\n\n// Define transports\nconst transports = [\n // Console transport\n new winston.transports.Console({\n format: logFormat,\n }),\n \n // Error log file\n new winston.transports.File({\n filename: 'logs/error.log',\n level: 'error',\n format: winston.format.combine(\n winston.format.timestamp(),\n winston.format.json()\n ),\n }),\n \n // Combined log file\n new winston.transports.File({\n filename: 'logs/combined.log',\n format: winston.format.combine(\n winston.format.timestamp(),\n winston.format.json()\n ),\n }),\n];\n\n// Create logger instance\nconst logger = winston.createLogger({\n level: process.env.NODE_ENV === 'development' ? 'debug' : 'info',\n levels: logLevels,\n transports,\n});\n\n// Security event logging\nexport interface SecurityEvent {\n type: 'auth_failure' | 'rate_limit' | 'suspicious_activity' | 'access_denied' | 'data_breach';\n userId?: string;\n ipAddress?: string;\n userAgent?: string;\n details?: Record<string, any>;\n severity: 'low' | 'medium' | 'high' | 'critical';\n}\n\nexport function logSecurityEvent(event: SecurityEvent) {\n logger.warn('SECURITY_EVENT', {\n type: event.type,\n userId: event.userId,\n ipAddress: event.ipAddress,\n userAgent: event.userAgent,\n severity: event.severity,\n details: event.details,\n timestamp: new Date().toISOString(),\n });\n \n // In production, you might also send this to a security monitoring service\n if (event.severity === 'critical') {\n logger.error('CRITICAL_SECURITY_EVENT', event);\n // TODO: Send alert to security team\n }\n}\n\n// API request logging\nexport interface APILogEntry {\n method: string;\n url: string;\n statusCode: number;\n responseTime: number;\n userId?: string;\n ipAddress?: string;\n userAgent?: string;\n error?: string;\n}\n\nexport function logAPIRequest(entry: APILogEntry) {\n const level = entry.statusCode >= 500 ? 'error' : \n entry.statusCode >= 400 ? 'warn' : 'info';\n \n logger.log(level, 'API_REQUEST', {\n method: entry.method,\n url: entry.url,\n statusCode: entry.statusCode,\n responseTime: entry.responseTime,\n userId: entry.userId,\n ipAddress: entry.ipAddress,\n userAgent: entry.userAgent,\n error: entry.error,\n timestamp: new Date().toISOString(),\n });\n}\n\n// Payment event logging\nexport interface PaymentEvent {\n type: 'payment_started' | 'payment_completed' | 'payment_failed' | 'refund_requested' | 'refund_completed';\n userId?: string;\n amount: number;\n currency: string;\n paymentIntentId?: string;\n eventId?: string;\n error?: string;\n}\n\nexport function logPaymentEvent(event: PaymentEvent) {\n const level = event.type.includes('failed') ? 'error' : 'info';\n \n logger.log(level, 'PAYMENT_EVENT', {\n type: event.type,\n userId: event.userId,\n amount: event.amount,\n currency: event.currency,\n paymentIntentId: event.paymentIntentId,\n eventId: event.eventId,\n error: event.error,\n timestamp: new Date().toISOString(),\n });\n}\n\n// User activity logging\nexport interface UserActivity {\n action: string;\n userId: string;\n resourceType?: string;\n resourceId?: string;\n ipAddress?: string;\n userAgent?: string;\n details?: Record<string, any>;\n}\n\nexport function logUserActivity(activity: UserActivity) {\n logger.info('USER_ACTIVITY', {\n action: activity.action,\n userId: activity.userId,\n resourceType: activity.resourceType,\n resourceId: activity.resourceId,\n ipAddress: activity.ipAddress,\n userAgent: activity.userAgent,\n details: activity.details,\n timestamp: new Date().toISOString(),\n });\n}\n\n// Error logging with context\nexport interface ErrorContext {\n userId?: string;\n ipAddress?: string;\n userAgent?: string;\n requestId?: string;\n additionalContext?: Record<string, any>;\n}\n\nexport function logError(error: Error, context?: ErrorContext) {\n logger.error('APPLICATION_ERROR', {\n message: error.message,\n stack: error.stack,\n name: error.name,\n userId: context?.userId,\n ipAddress: context?.ipAddress,\n userAgent: context?.userAgent,\n requestId: context?.requestId,\n additionalContext: context?.additionalContext,\n timestamp: new Date().toISOString(),\n });\n\n // Also send to Sentry\n captureException(error, {\n userId: context?.userId,\n userEmail: context?.userAgent, // We don't have email in context, would need to be added\n requestId: context?.requestId,\n additionalData: {\n ipAddress: context?.ipAddress,\n userAgent: context?.userAgent,\n ...context?.additionalContext\n }\n });\n}\n\n// Performance logging\nexport interface PerformanceMetrics {\n operation: string;\n duration: number;\n userId?: string;\n additionalMetrics?: Record<string, number>;\n}\n\nexport function logPerformance(metrics: PerformanceMetrics) {\n logger.info('PERFORMANCE_METRICS', {\n operation: metrics.operation,\n duration: metrics.duration,\n userId: metrics.userId,\n additionalMetrics: metrics.additionalMetrics,\n timestamp: new Date().toISOString(),\n });\n}\n\n// Business metrics logging\nexport interface BusinessMetrics {\n metric: string;\n value: number;\n tags?: Record<string, string>;\n}\n\nexport function logBusinessMetrics(metrics: BusinessMetrics) {\n logger.info('BUSINESS_METRICS', {\n metric: metrics.metric,\n value: metrics.value,\n tags: metrics.tags,\n timestamp: new Date().toISOString(),\n });\n}\n\n// Audit trail logging\nexport interface AuditEvent {\n action: string;\n userId: string;\n resourceType: string;\n resourceId: string;\n oldValues?: Record<string, any>;\n newValues?: Record<string, any>;\n ipAddress?: string;\n userAgent?: string;\n}\n\nexport function logAuditEvent(event: AuditEvent) {\n logger.info('AUDIT_TRAIL', {\n action: event.action,\n userId: event.userId,\n resourceType: event.resourceType,\n resourceId: event.resourceId,\n oldValues: event.oldValues,\n newValues: event.newValues,\n ipAddress: event.ipAddress,\n userAgent: event.userAgent,\n timestamp: new Date().toISOString(),\n });\n \n // Also log to database for compliance\n // This would integrate with your audit_logs table\n}\n\n// Export the main logger instance\nexport default logger;","usedDeprecatedRules":[]},{"filePath":"/home/tyler/apps/bct-whitelabel/src/lib/marketing-kit-enhanced.ts","messages":[{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'supabase' is assigned a value but never used. Allowed unused vars must match /^_/u.","line":4,"column":7,"nodeType":null,"messageId":"unusedVar","endLine":4,"endColumn":15},{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":24,"column":63,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":24,"endColumn":66,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[744,747],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[744,747],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'e' is defined but never used.","line":101,"column":14,"nodeType":null,"messageId":"unusedVar","endLine":101,"endColumn":15},{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":112,"column":34,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":112,"endColumn":37,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[3253,3256],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[3253,3256],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":122,"column":12,"nodeType":null,"messageId":"unusedVar","endLine":122,"endColumn":17},{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":128,"column":41,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":128,"endColumn":44,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[3688,3691],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[3688,3691],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":204,"column":59,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":204,"endColumn":62,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[6351,6354],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[6351,6354],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'e' is defined but never used.","line":278,"column":14,"nodeType":null,"messageId":"unusedVar","endLine":278,"endColumn":15},{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":286,"column":42,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":286,"endColumn":45,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[8701,8704],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[8701,8704],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":297,"column":12,"nodeType":null,"messageId":"unusedVar","endLine":297,"endColumn":17},{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":303,"column":48,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":303,"endColumn":51,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[9199,9202],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[9199,9202],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":343,"column":54,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":343,"endColumn":57,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[11483,11486],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[11483,11486],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'e' is defined but never used.","line":416,"column":14,"nodeType":null,"messageId":"unusedVar","endLine":416,"endColumn":15},{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":424,"column":36,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":424,"endColumn":39,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[13650,13653],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[13650,13653],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":434,"column":12,"nodeType":null,"messageId":"unusedVar","endLine":434,"endColumn":17},{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":440,"column":43,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":440,"endColumn":46,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[14067,14070],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[14067,14070],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":493,"column":56,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":493,"endColumn":59,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[16124,16127],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[16124,16127],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'e' is defined but never used.","line":564,"column":14,"nodeType":null,"messageId":"unusedVar","endLine":564,"endColumn":15},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":579,"column":12,"nodeType":null,"messageId":"unusedVar","endLine":579,"endColumn":17},{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":585,"column":44,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":585,"endColumn":47,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[18717,18720],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[18717,18720],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":602,"column":27,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":602,"endColumn":30,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[19590,19593],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[19590,19593],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":612,"column":54,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":612,"endColumn":57,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[19943,19946],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[19943,19946],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'e' is defined but never used.","line":682,"column":14,"nodeType":null,"messageId":"unusedVar","endLine":682,"endColumn":15},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":698,"column":12,"nodeType":null,"messageId":"unusedVar","endLine":698,"endColumn":17},{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":704,"column":47,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":704,"endColumn":50,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[22485,22488],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[22485,22488],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":25,"fixableErrorCount":0,"fixableWarningCount":0,"source":"import { createClient } from '@supabase/supabase-js';\nimport type { Database } from './database.types';\n\nconst supabase = createClient<Database>(\n import.meta.env.PUBLIC_SUPABASE_URL,\n import.meta.env.PUBLIC_SUPABASE_ANON_KEY\n);\n\n// OpenAI configuration\nconst OPENAI_API_KEY = import.meta.env.OPENAI_API_KEY;\nconst OPENAI_API_URL = 'https://api.openai.com/v1/chat/completions';\n\nexport interface SocialMediaContent {\n id?: string;\n platform: 'facebook' | 'twitter' | 'instagram' | 'linkedin';\n content: string;\n hashtags: string[];\n image_url?: string;\n tone?: 'professional' | 'casual' | 'exciting' | 'informative' | 'urgent';\n votes?: number;\n generated_at?: string;\n}\n\nexport async function generateSocialMediaContentWithAI(event: any): Promise<SocialMediaContent[]> {\n if (!OPENAI_API_KEY) {\n\n return generateFallbackContent(event);\n }\n\n try {\n const eventDate = new Date(event.start_time).toLocaleDateString('en-US', {\n weekday: 'long',\n year: 'numeric',\n month: 'long',\n day: 'numeric'\n });\n\n const prompt = `Generate 5 varied social media posts for an event with the following details:\nEvent: ${event.title}\nDate: ${eventDate}\nVenue: ${event.venue}\nDescription: ${event.description || 'A special event'}\n\nPlease create 5 different posts with these variations:\n1. Professional/formal tone for LinkedIn\n2. Casual/friendly tone for Facebook \n3. Exciting/energetic tone for Instagram\n4. Informative/educational tone\n5. Urgent/FOMO (fear of missing out) tone\n\nFor each post:\n- Keep it concise and engaging\n- Include relevant emojis\n- Suggest 3-5 relevant hashtags\n- Vary the messaging approach\n- Make each post unique and platform-appropriate\n\nFormat as JSON with an array called \"posts\", each containing: \n{\n \"platform\": \"facebook|twitter|instagram|linkedin\",\n \"content\": \"post text\",\n \"hashtags\": [\"tag1\", \"tag2\"],\n \"tone\": \"professional|casual|exciting|informative|urgent\"\n}`;\n\n const response = await fetch(OPENAI_API_URL, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${OPENAI_API_KEY}`\n },\n body: JSON.stringify({\n model: 'gpt-3.5-turbo',\n messages: [\n {\n role: 'system',\n content: 'You are a social media marketing expert specializing in event promotion. Generate engaging, varied content that drives ticket sales. Always respond with valid JSON.'\n },\n {\n role: 'user',\n content: prompt\n }\n ],\n temperature: 0.9,\n max_tokens: 2000\n })\n });\n\n if (!response.ok) {\n const error = await response.text();\n throw new Error(`OpenAI API error: ${response.status} - ${error}`);\n }\n\n const data = await response.json();\n const content = data.choices[0].message.content;\n \n // Parse the JSON response\n let parsedContent;\n try {\n parsedContent = JSON.parse(content);\n } catch (e) {\n\n return generateFallbackContent(event);\n }\n \n // Extract posts array\n const posts = parsedContent.posts || parsedContent;\n \n // Ensure it's an array\n const postsArray = Array.isArray(posts) ? posts : [posts];\n \n return postsArray.map((post: any, index: number) => ({\n id: `ai-${Date.now()}-${index}`,\n platform: post.platform || 'facebook',\n content: post.content || '',\n hashtags: Array.isArray(post.hashtags) ? post.hashtags : [],\n tone: post.tone || 'professional',\n votes: 0,\n generated_at: new Date().toISOString()\n }));\n\n } catch (error) {\n\n return generateFallbackContent(event);\n }\n}\n\nfunction generateFallbackContent(event: any): SocialMediaContent[] {\n const eventDate = new Date(event.start_time).toLocaleDateString('en-US', {\n weekday: 'long',\n year: 'numeric',\n month: 'long',\n day: 'numeric'\n });\n \n const baseHashtags = ['#event', '#tickets', '#liveentertainment'];\n \n return [\n {\n id: `fallback-${Date.now()}-1`,\n platform: 'linkedin',\n content: `We're pleased to announce ${event.title}, taking place on ${eventDate} at ${event.venue}. This exclusive event offers an exceptional opportunity for networking and entertainment. Secure your tickets today.`,\n hashtags: [...baseHashtags, '#networking', '#professional'],\n tone: 'professional',\n votes: 0\n },\n {\n id: `fallback-${Date.now()}-2`,\n platform: 'facebook',\n content: `🎉 Hey everyone! Don't miss out on ${event.title}! Join us on ${eventDate} at ${event.venue} for an unforgettable experience. Grab your tickets now - link in bio! 🎫`,\n hashtags: [...baseHashtags, '#community', '#fun'],\n tone: 'casual',\n votes: 0\n },\n {\n id: `fallback-${Date.now()}-3`,\n platform: 'instagram',\n content: `🔥 IT'S HAPPENING! ${event.title} is coming to ${event.venue}! 🎉✨ Mark your calendars for ${eventDate} - this is THE event you don't want to miss! Limited tickets available! 🎫🏃‍♀️`,\n hashtags: [...baseHashtags, '#instagood', '#eventlife', '#mustattend'],\n tone: 'exciting',\n votes: 0\n },\n {\n id: `fallback-${Date.now()}-4`,\n platform: 'twitter',\n content: `📅 Learn more: ${event.title} features world-class entertainment on ${eventDate} at ${event.venue}. An evening of culture and sophistication awaits. Details: [link]`,\n hashtags: [...baseHashtags, '#culture', '#arts'],\n tone: 'informative',\n votes: 0\n },\n {\n id: `fallback-${Date.now()}-5`,\n platform: 'facebook',\n content: `⏰ LAST CHANCE! Only a few tickets left for ${event.title}! Don't be the one who misses out on the event of the season. ${eventDate} at ${event.venue} - GET YOUR TICKETS NOW! 🎟️💨`,\n hashtags: [...baseHashtags, '#lastchance', '#limitedtickets', '#FOMO'],\n tone: 'urgent',\n votes: 0\n }\n ];\n}\n\nexport interface EmailTemplate {\n id?: string;\n subject: string;\n html_content: string;\n text_content: string;\n preview_text: string;\n tone?: string;\n votes?: number;\n generated_at?: string;\n}\n\nexport interface FlyerData {\n id?: string;\n title: string;\n content: string;\n style: string;\n theme: string;\n votes?: number;\n generated_at?: string;\n}\n\n// Generate AI-powered email templates\nexport async function generateEmailTemplatesWithAI(event: any): Promise<EmailTemplate[]> {\n if (!OPENAI_API_KEY) {\n\n return generateFallbackEmailTemplates(event);\n }\n\n try {\n const eventDate = new Date(event.start_time).toLocaleDateString('en-US', {\n weekday: 'long',\n year: 'numeric',\n month: 'long',\n day: 'numeric'\n });\n\n const prompt = `Generate 3 different email templates for an event with these details:\nEvent: ${event.title}\nDate: ${eventDate}\nVenue: ${event.venue}\nDescription: ${event.description || 'A special event'}\n\nCreate 3 variations with different tones:\n1. Professional/corporate tone\n2. Friendly/casual tone\n3. Urgent/promotional tone\n\nFor each email template:\n- Create an engaging subject line\n- Write a compelling preview text (50-80 characters)\n- Generate HTML email content with proper formatting\n- Create a text version for accessibility\n- Include a clear call-to-action\n\nFormat as JSON with an array called \"templates\", each containing:\n{\n \"tone\": \"professional|casual|urgent\",\n \"subject\": \"subject line\",\n \"preview_text\": \"preview text\",\n \"html_content\": \"HTML email content\",\n \"text_content\": \"plain text version\"\n}`;\n\n const response = await fetch(OPENAI_API_URL, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${OPENAI_API_KEY}`\n },\n body: JSON.stringify({\n model: 'gpt-3.5-turbo',\n messages: [\n {\n role: 'system',\n content: 'You are an email marketing expert specializing in event promotion. Create compelling email templates that drive ticket sales and engagement. Always respond with valid JSON.'\n },\n {\n role: 'user',\n content: prompt\n }\n ],\n temperature: 0.8,\n max_tokens: 3000\n })\n });\n\n if (!response.ok) {\n throw new Error(`OpenAI API error: ${response.status}`);\n }\n\n const data = await response.json();\n const content = data.choices[0].message.content;\n \n let parsedContent;\n try {\n parsedContent = JSON.parse(content);\n } catch (e) {\n\n return generateFallbackEmailTemplates(event);\n }\n \n const templates = parsedContent.templates || parsedContent;\n const templatesArray = Array.isArray(templates) ? templates : [templates];\n \n return templatesArray.map((template: any, index: number) => ({\n id: `ai-email-${Date.now()}-${index}`,\n subject: template.subject || '',\n html_content: template.html_content || '',\n text_content: template.text_content || '',\n preview_text: template.preview_text || '',\n tone: template.tone || 'professional',\n votes: 0,\n generated_at: new Date().toISOString()\n }));\n\n } catch (error) {\n\n return generateFallbackEmailTemplates(event);\n }\n}\n\nfunction generateFallbackEmailTemplates(event: any): EmailTemplate[] {\n const eventDate = new Date(event.start_time).toLocaleDateString('en-US', {\n weekday: 'long',\n year: 'numeric',\n month: 'long',\n day: 'numeric'\n });\n\n return [\n {\n id: `fallback-email-${Date.now()}-1`,\n subject: `${event.title} - ${eventDate}`,\n preview_text: `Join us for ${event.title} at ${event.venue}`,\n html_content: `<h1>${event.title}</h1><p>We cordially invite you to ${event.title} on ${eventDate} at ${event.venue}.</p><p>${event.description || 'An exceptional event awaits you.'}</p><a href=\"#\">Reserve Your Tickets</a>`,\n text_content: `${event.title}\\n\\nWe cordially invite you to ${event.title} on ${eventDate} at ${event.venue}.\\n\\n${event.description || 'An exceptional event awaits you.'}\\n\\nReserve Your Tickets: [LINK]`,\n tone: 'professional',\n votes: 0\n },\n {\n id: `fallback-email-${Date.now()}-2`,\n subject: `🎉 Don't miss ${event.title}!`,\n preview_text: `Hey there! ${event.title} is coming up fast`,\n html_content: `<h1>🎉 ${event.title}</h1><p>Hey there! We're so excited to invite you to ${event.title} on ${eventDate} at ${event.venue}.</p><p>${event.description || 'It\\'s going to be amazing!'}</p><a href=\"#\">Get Your Tickets Now!</a>`,\n text_content: `🎉 ${event.title}\\n\\nHey there! We're so excited to invite you to ${event.title} on ${eventDate} at ${event.venue}.\\n\\n${event.description || 'It\\'s going to be amazing!'}\\n\\nGet Your Tickets Now: [LINK]`,\n tone: 'casual',\n votes: 0\n },\n {\n id: `fallback-email-${Date.now()}-3`,\n subject: `⏰ LAST CHANCE: ${event.title} tickets selling fast!`,\n preview_text: `Limited tickets remaining for ${event.title}`,\n html_content: `<h1>⏰ LAST CHANCE!</h1><p>Limited tickets remaining for ${event.title} on ${eventDate} at ${event.venue}.</p><p>Don't miss out on this exclusive event!</p><a href=\"#\">SECURE YOUR SPOT NOW</a>`,\n text_content: `⏰ LAST CHANCE!\\n\\nLimited tickets remaining for ${event.title} on ${eventDate} at ${event.venue}.\\n\\nDon't miss out on this exclusive event!\\n\\nSECURE YOUR SPOT NOW: [LINK]`,\n tone: 'urgent',\n votes: 0\n }\n ];\n}\n\n// Generate AI-powered flyer/asset data\nexport async function generateFlyerDataWithAI(event: any): Promise<FlyerData[]> {\n if (!OPENAI_API_KEY) {\n\n return generateFallbackFlyerData(event);\n }\n\n try {\n const eventDate = new Date(event.start_time).toLocaleDateString('en-US', {\n weekday: 'long',\n year: 'numeric',\n month: 'long',\n day: 'numeric'\n });\n\n const prompt = `Generate 3 different flyer designs for an event:\nEvent: ${event.title}\nDate: ${eventDate}\nVenue: ${event.venue}\nDescription: ${event.description || 'A special event'}\n\nCreate 3 variations with different styles:\n1. Elegant/sophisticated style\n2. Modern/contemporary style\n3. Bold/energetic style\n\nFor each flyer design:\n- Create compelling headline text\n- Write engaging body content\n- Suggest design theme and colors\n- Include style guidelines\n\nFormat as JSON with an array called \"flyers\", each containing:\n{\n \"style\": \"elegant|modern|bold\",\n \"title\": \"main headline\",\n \"content\": \"body content for flyer\",\n \"theme\": \"theme description\",\n \"colors\": \"color scheme suggestions\"\n}`;\n\n const response = await fetch(OPENAI_API_URL, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${OPENAI_API_KEY}`\n },\n body: JSON.stringify({\n model: 'gpt-3.5-turbo',\n messages: [\n {\n role: 'system',\n content: 'You are a graphic design expert specializing in event flyers. Create compelling flyer concepts that attract attendees. Always respond with valid JSON.'\n },\n {\n role: 'user',\n content: prompt\n }\n ],\n temperature: 0.8,\n max_tokens: 2000\n })\n });\n\n if (!response.ok) {\n throw new Error(`OpenAI API error: ${response.status}`);\n }\n\n const data = await response.json();\n const content = data.choices[0].message.content;\n \n let parsedContent;\n try {\n parsedContent = JSON.parse(content);\n } catch (e) {\n\n return generateFallbackFlyerData(event);\n }\n \n const flyers = parsedContent.flyers || parsedContent;\n const flyersArray = Array.isArray(flyers) ? flyers : [flyers];\n \n return flyersArray.map((flyer: any, index: number) => ({\n id: `ai-flyer-${Date.now()}-${index}`,\n title: flyer.title || event.title,\n content: flyer.content || '',\n style: flyer.style || 'modern',\n theme: flyer.theme || 'Default theme',\n votes: 0,\n generated_at: new Date().toISOString()\n }));\n\n } catch (error) {\n\n return generateFallbackFlyerData(event);\n }\n}\n\nfunction generateFallbackFlyerData(event: any): FlyerData[] {\n const eventDate = new Date(event.start_time).toLocaleDateString('en-US', {\n weekday: 'long',\n year: 'numeric',\n month: 'long',\n day: 'numeric'\n });\n\n return [\n {\n id: `fallback-flyer-${Date.now()}-1`,\n title: `${event.title}`,\n content: `Join us for an elegant evening on ${eventDate} at ${event.venue}. ${event.description || 'An unforgettable experience awaits.'}`,\n style: 'elegant',\n theme: 'Sophisticated black and gold theme with elegant typography',\n votes: 0\n },\n {\n id: `fallback-flyer-${Date.now()}-2`,\n title: `${event.title}`,\n content: `Experience ${event.title} like never before. ${eventDate} • ${event.venue}. ${event.description || 'Modern entertainment for the contemporary audience.'}`,\n style: 'modern',\n theme: 'Clean minimalist design with bold blues and whites',\n votes: 0\n },\n {\n id: `fallback-flyer-${Date.now()}-3`,\n title: `${event.title.toUpperCase()}`,\n content: `🎉 THE EVENT OF THE YEAR! ${eventDate} at ${event.venue}. ${event.description || 'Get ready for an explosive experience!'}`,\n style: 'bold',\n theme: 'High-energy design with bright colors and dynamic typography',\n votes: 0\n }\n ];\n}\n\n// Function to save user vote/preference\nexport async function saveContentVote(contentId: string, vote: 'up' | 'down'): Promise<void> {\n // In a real implementation, this would save to a database\n // For now, we'll just log it\n\n // You could store this in localStorage for learning\n const votes = JSON.parse(localStorage.getItem('marketing_content_votes') || '{}');\n votes[contentId] = vote;\n localStorage.setItem('marketing_content_votes', JSON.stringify(votes));\n}\n\n// Function to get voting data for learning\nexport function getVotingData(): Record<string, 'up' | 'down'> {\n return JSON.parse(localStorage.getItem('marketing_content_votes') || '{}');\n}\n\n// Generate a single new social media post for a specific platform\nexport async function regenerateSocialMediaPost(event: any, platform: string, tone?: string): Promise<SocialMediaContent | null> {\n if (!OPENAI_API_KEY) {\n\n return generateFallbackSocialPost(event, platform, tone);\n }\n\n try {\n const eventDate = new Date(event.start_time).toLocaleDateString('en-US', {\n weekday: 'long',\n year: 'numeric',\n month: 'long',\n day: 'numeric'\n });\n\n const selectedTone = tone || 'engaging';\n \n const prompt = `Generate a new social media post for ${platform} with the following details:\nEvent: ${event.title}\nDate: ${eventDate}\nVenue: ${event.venue}\nDescription: ${event.description || 'A special event'}\n\nCreate a ${selectedTone} post that:\n- Is optimized for ${platform}\n- Uses a ${selectedTone} tone\n- Includes relevant emojis\n- Has a clear call-to-action\n- Is different from previous attempts (be creative and unique)\n- Suggests 3-5 relevant hashtags\n\nFormat as JSON with:\n{\n \"platform\": \"${platform}\",\n \"content\": \"post text with emojis\",\n \"hashtags\": [\"#tag1\", \"#tag2\", \"#tag3\"],\n \"tone\": \"${selectedTone}\"\n}`;\n\n const response = await fetch(OPENAI_API_URL, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${OPENAI_API_KEY}`\n },\n body: JSON.stringify({\n model: 'gpt-3.5-turbo',\n messages: [\n {\n role: 'system',\n content: `You are a social media expert specializing in ${platform} content. Create engaging, platform-specific posts that drive ticket sales. Always respond with valid JSON.`\n },\n {\n role: 'user',\n content: prompt\n }\n ],\n temperature: 0.9, // Higher temperature for more creativity\n max_tokens: 500\n })\n });\n\n if (!response.ok) {\n throw new Error(`OpenAI API error: ${response.status}`);\n }\n\n const data = await response.json();\n const content = data.choices[0].message.content;\n \n let parsedContent;\n try {\n parsedContent = JSON.parse(content);\n } catch (e) {\n\n return generateFallbackSocialPost(event, platform, tone);\n }\n \n return {\n id: `ai-regen-${Date.now()}`,\n platform: parsedContent.platform || platform,\n content: parsedContent.content || '',\n hashtags: Array.isArray(parsedContent.hashtags) ? parsedContent.hashtags : [],\n tone: parsedContent.tone || selectedTone,\n votes: 0,\n generated_at: new Date().toISOString()\n };\n\n } catch (error) {\n\n return generateFallbackSocialPost(event, platform, tone);\n }\n}\n\nfunction generateFallbackSocialPost(event: any, platform: string, tone?: string): SocialMediaContent {\n const eventDate = new Date(event.start_time).toLocaleDateString('en-US', {\n weekday: 'long',\n year: 'numeric',\n month: 'long',\n day: 'numeric'\n });\n\n const fallbackContent = {\n facebook: `🎉 Join us for ${event.title} on ${eventDate} at ${event.venue}! This is going to be an incredible experience. Get your tickets now!`,\n twitter: `🎫 ${event.title} - ${eventDate} at ${event.venue}. Don't miss out! Tickets available now.`,\n instagram: `✨ ${event.title} ✨\\n📅 ${eventDate}\\n📍 ${event.venue}\\n\\nThis is THE event you've been waiting for! 🎟️`,\n linkedin: `We're excited to announce ${event.title} on ${eventDate} at ${event.venue}. Join us for this professional networking and entertainment event.`\n };\n\n return {\n id: `fallback-regen-${Date.now()}`,\n platform: platform as any,\n content: fallbackContent[platform as keyof typeof fallbackContent] || fallbackContent.facebook,\n hashtags: ['#event', '#tickets', '#liveentertainment'],\n tone: tone || 'engaging',\n votes: 0,\n generated_at: new Date().toISOString()\n };\n}\n\n// Generate a single new email template\nexport async function regenerateEmailTemplate(event: any, tone: string): Promise<EmailTemplate | null> {\n if (!OPENAI_API_KEY) {\n\n return generateFallbackEmailTemplate(event, tone);\n }\n\n try {\n const eventDate = new Date(event.start_time).toLocaleDateString('en-US', {\n weekday: 'long',\n year: 'numeric',\n month: 'long',\n day: 'numeric'\n });\n\n const prompt = `Generate a new email template with a ${tone} tone for this event:\nEvent: ${event.title}\nDate: ${eventDate}\nVenue: ${event.venue}\nDescription: ${event.description || 'A special event'}\n\nCreate a ${tone} email template that:\n- Has an engaging subject line\n- Includes compelling preview text (50-80 characters)\n- Contains HTML email content with proper formatting\n- Has a plain text version for accessibility\n- Is different from previous attempts (be creative and unique)\n- Includes a clear call-to-action\n\nFormat as JSON with:\n{\n \"tone\": \"${tone}\",\n \"subject\": \"subject line\",\n \"preview_text\": \"preview text\",\n \"html_content\": \"HTML email content\",\n \"text_content\": \"plain text version\"\n}`;\n\n const response = await fetch(OPENAI_API_URL, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${OPENAI_API_KEY}`\n },\n body: JSON.stringify({\n model: 'gpt-3.5-turbo',\n messages: [\n {\n role: 'system',\n content: 'You are an email marketing expert. Create compelling email templates that drive engagement and ticket sales. Always respond with valid JSON.'\n },\n {\n role: 'user',\n content: prompt\n }\n ],\n temperature: 0.9,\n max_tokens: 2000\n })\n });\n\n if (!response.ok) {\n throw new Error(`OpenAI API error: ${response.status}`);\n }\n\n const data = await response.json();\n const content = data.choices[0].message.content;\n \n let parsedContent;\n try {\n parsedContent = JSON.parse(content);\n } catch (e) {\n\n return generateFallbackEmailTemplate(event, tone);\n }\n \n return {\n id: `ai-email-regen-${Date.now()}`,\n subject: parsedContent.subject || '',\n html_content: parsedContent.html_content || '',\n text_content: parsedContent.text_content || '',\n preview_text: parsedContent.preview_text || '',\n tone: parsedContent.tone || tone,\n votes: 0,\n generated_at: new Date().toISOString()\n };\n\n } catch (error) {\n\n return generateFallbackEmailTemplate(event, tone);\n }\n}\n\nfunction generateFallbackEmailTemplate(event: any, tone: string): EmailTemplate {\n const eventDate = new Date(event.start_time).toLocaleDateString('en-US', {\n weekday: 'long',\n year: 'numeric',\n month: 'long',\n day: 'numeric'\n });\n\n const templates = {\n professional: {\n subject: `${event.title} - ${eventDate}`,\n preview_text: `Join us for ${event.title} at ${event.venue}`,\n html_content: `<h1>${event.title}</h1><p>We cordially invite you to ${event.title} on ${eventDate} at ${event.venue}.</p><p>${event.description || 'An exceptional event awaits you.'}</p><a href=\"#\">Reserve Your Tickets</a>`,\n text_content: `${event.title}\\n\\nWe cordially invite you to ${event.title} on ${eventDate} at ${event.venue}.\\n\\n${event.description || 'An exceptional event awaits you.'}\\n\\nReserve Your Tickets: [LINK]`\n },\n casual: {\n subject: `🎉 Don't miss ${event.title}!`,\n preview_text: `Hey there! ${event.title} is coming up fast`,\n html_content: `<h1>🎉 ${event.title}</h1><p>Hey there! We're so excited to invite you to ${event.title} on ${eventDate} at ${event.venue}.</p><p>${event.description || 'It\\'s going to be amazing!'}</p><a href=\"#\">Get Your Tickets Now!</a>`,\n text_content: `🎉 ${event.title}\\n\\nHey there! We're so excited to invite you to ${event.title} on ${eventDate} at ${event.venue}.\\n\\n${event.description || 'It\\'s going to be amazing!'}\\n\\nGet Your Tickets Now: [LINK]`\n },\n urgent: {\n subject: `⏰ LAST CHANCE: ${event.title} tickets selling fast!`,\n preview_text: `Limited tickets remaining for ${event.title}`,\n html_content: `<h1>⏰ LAST CHANCE!</h1><p>Limited tickets remaining for ${event.title} on ${eventDate} at ${event.venue}.</p><p>Don't miss out on this exclusive event!</p><a href=\"#\">SECURE YOUR SPOT NOW</a>`,\n text_content: `⏰ LAST CHANCE!\\n\\nLimited tickets remaining for ${event.title} on ${eventDate} at ${event.venue}.\\n\\nDon't miss out on this exclusive event!\\n\\nSECURE YOUR SPOT NOW: [LINK]`\n }\n };\n\n const template = templates[tone as keyof typeof templates] || templates.professional;\n \n return {\n id: `fallback-email-regen-${Date.now()}`,\n subject: template.subject,\n html_content: template.html_content,\n text_content: template.text_content,\n preview_text: template.preview_text,\n tone,\n votes: 0,\n generated_at: new Date().toISOString()\n };\n}","usedDeprecatedRules":[]},{"filePath":"/home/tyler/apps/bct-whitelabel/src/lib/marketing-kit-service.ts","messages":[{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'fileStorageService' is defined but never used. Allowed unused vars must match /^_/u.","line":6,"column":10,"nodeType":null,"messageId":"unusedVar","endLine":6,"endColumn":28},{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":17,"column":18,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":17,"endColumn":21,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[523,526],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[523,526],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":24,"column":20,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":24,"endColumn":23,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[680,683],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[680,683],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":38,"column":16,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":38,"endColumn":19,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[926,929],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[926,929],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":39,"column":14,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":39,"endColumn":17,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[944,947],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[944,947],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"no-useless-catch","severity":2,"message":"Unnecessary try/catch wrapper.","line":47,"column":5,"nodeType":"TryStatement","messageId":"unnecessaryCatch","endLine":111,"endColumn":6},{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":262,"column":14,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":262,"endColumn":17,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[7521,7524],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[7521,7524],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":293,"column":43,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":293,"endColumn":46,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[8346,8349],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[8346,8349],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'zipFileName' is assigned a value but never used. Allowed unused vars must match /^_/u.","line":298,"column":11,"nodeType":null,"messageId":"unusedVar","endLine":298,"endColumn":22},{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":313,"column":37,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":313,"endColumn":40,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[9107,9110],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[9107,9110],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]}],"suppressedMessages":[],"errorCount":1,"fatalErrorCount":0,"warningCount":9,"fixableErrorCount":0,"fixableWarningCount":0,"source":"import { supabase } from './supabase';\nimport { qrGenerator } from './qr-generator';\nimport { socialMediaGenerator } from './social-media-generator';\nimport { emailTemplateGenerator } from './email-template-generator';\nimport { flyerGenerator } from './flyer-generator';\nimport { fileStorageService } from './file-storage-service';\n\ninterface EventData {\n id: string;\n title: string;\n description: string;\n venue: string;\n start_time: string;\n end_time: string;\n slug: string;\n image_url?: string;\n social_links?: any;\n website_url?: string;\n contact_email?: string;\n organization_id: string;\n organizations: {\n name: string;\n logo?: string;\n social_links?: any;\n website_url?: string;\n };\n}\n\ninterface MarketingAsset {\n id?: string;\n asset_type: string;\n platform?: string;\n title: string;\n content?: string;\n image_url?: string;\n download_url?: string;\n file_format: string;\n dimensions?: any;\n metadata?: any;\n}\n\nclass MarketingKitService {\n /**\n * Generate complete marketing kit for an event\n */\n async generateCompleteKit(event: EventData, organizationId: string, userId: string) {\n try {\n // Start kit generation record\n const { data: kitGeneration, error: kitError } = await supabase\n .from('marketing_kit_generations')\n .insert({\n event_id: event.id,\n organization_id: organizationId,\n generated_by: userId,\n generation_type: 'full_kit',\n assets_included: ['social_post', 'flyer', 'email_template', 'qr_code'],\n generation_status: 'processing'\n })\n .select()\n .single();\n\n if (kitError) {\n throw new Error('Failed to start kit generation');\n }\n\n const assets: MarketingAsset[] = [];\n\n // 1. Generate QR Code first (needed for other assets)\n const qrCodes = await this.generateQRCodes(event);\n assets.push(...qrCodes);\n\n // 2. Generate Social Media Posts\n const socialPosts = await this.generateSocialMediaPosts(event);\n assets.push(...socialPosts);\n\n // 3. Generate Flyer/Poster\n const flyers = await this.generateFlyers(event);\n assets.push(...flyers);\n\n // 4. Generate Email Templates\n const emailTemplates = await this.generateEmailTemplates(event);\n assets.push(...emailTemplates);\n\n // Save all assets to database\n const savedAssets = await this.saveAssetsToDatabase(assets, event.id, organizationId);\n\n // Create ZIP file with all assets\n const zipUrl = await this.createZipDownload(savedAssets, event);\n\n // Update kit generation with success\n await supabase\n .from('marketing_kit_generations')\n .update({\n generation_status: 'completed',\n zip_file_url: zipUrl,\n zip_expires_at: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000).toISOString() // 7 days\n })\n .eq('id', kitGeneration.id);\n\n return {\n event,\n assets: this.groupAssetsByType(savedAssets),\n zip_download_url: zipUrl,\n generated_at: new Date().toISOString(),\n generation_id: kitGeneration.id\n };\n\n } catch (error) {\n\n throw error;\n }\n }\n\n /**\n * Generate specific asset types only\n */\n async generateSpecificAssets(\n event: EventData, \n organizationId: string, \n userId: string, \n assetTypes: string[]\n ) {\n const assets: MarketingAsset[] = [];\n\n for (const assetType of assetTypes) {\n switch (assetType) {\n case 'qr_code':\n assets.push(...await this.generateQRCodes(event));\n break;\n case 'social_post':\n assets.push(...await this.generateSocialMediaPosts(event));\n break;\n case 'flyer':\n assets.push(...await this.generateFlyers(event));\n break;\n case 'email_template':\n assets.push(...await this.generateEmailTemplates(event));\n break;\n }\n }\n\n const savedAssets = await this.saveAssetsToDatabase(assets, event.id, organizationId);\n\n return {\n event,\n assets: this.groupAssetsByType(savedAssets),\n generated_at: new Date().toISOString()\n };\n }\n\n /**\n * Generate QR codes for different use cases\n */\n private async generateQRCodes(event: EventData): Promise<MarketingAsset[]> {\n const ticketUrl = `${import.meta.env.PUBLIC_SITE_URL || 'https://portal.blackcanyontickets.com'}/e/${event.slug}`;\n \n const qrCodes = await qrGenerator.generateMultiFormat(ticketUrl);\n \n return [\n {\n asset_type: 'qr_code',\n title: 'QR Code - Social Media',\n content: qrCodes.social.dataUrl,\n file_format: 'png',\n dimensions: { width: qrCodes.social.size, height: qrCodes.social.size },\n metadata: { url: ticketUrl, use_case: 'social' }\n },\n {\n asset_type: 'qr_code',\n title: 'QR Code - Print/Flyer',\n content: qrCodes.print.dataUrl,\n file_format: 'png',\n dimensions: { width: qrCodes.print.size, height: qrCodes.print.size },\n metadata: { url: ticketUrl, use_case: 'print' }\n },\n {\n asset_type: 'qr_code',\n title: 'QR Code - Email',\n content: qrCodes.email.dataUrl,\n file_format: 'png',\n dimensions: { width: qrCodes.email.size, height: qrCodes.email.size },\n metadata: { url: ticketUrl, use_case: 'email' }\n }\n ];\n }\n\n /**\n * Generate social media posts for different platforms\n */\n private async generateSocialMediaPosts(event: EventData): Promise<MarketingAsset[]> {\n const platforms = ['facebook', 'instagram', 'twitter', 'linkedin'];\n const posts: MarketingAsset[] = [];\n\n for (const platform of platforms) {\n const post = await socialMediaGenerator.generatePost(event, platform);\n posts.push({\n asset_type: 'social_post',\n platform,\n title: `${platform} Post - ${event.title}`,\n content: post.text,\n image_url: post.imageUrl,\n file_format: 'png',\n dimensions: post.dimensions,\n metadata: {\n hashtags: post.hashtags,\n social_links: event.social_links || event.organizations.social_links,\n platform_specific: post.platformSpecific\n }\n });\n }\n\n return posts;\n }\n\n /**\n * Generate flyers and posters\n */\n private async generateFlyers(event: EventData): Promise<MarketingAsset[]> {\n const flyers = await flyerGenerator.generateFlyers(event);\n \n return flyers.map(flyer => ({\n asset_type: 'flyer',\n title: flyer.title,\n image_url: flyer.imageUrl,\n file_format: 'png',\n dimensions: flyer.dimensions,\n metadata: {\n style: flyer.style,\n includes_qr: true,\n includes_logo: !!event.organizations.logo\n }\n }));\n }\n\n /**\n * Generate email campaign templates\n */\n private async generateEmailTemplates(event: EventData): Promise<MarketingAsset[]> {\n const templates = await emailTemplateGenerator.generateTemplates(event);\n \n return templates.map(template => ({\n asset_type: 'email_template',\n title: template.title,\n content: template.html,\n file_format: 'html',\n metadata: {\n subject: template.subject,\n preview_text: template.previewText,\n includes_qr: true,\n cta_text: template.ctaText\n }\n }));\n }\n\n /**\n * Save generated assets to database\n */\n private async saveAssetsToDatabase(\n assets: MarketingAsset[], \n eventId: string, \n organizationId: string\n ): Promise<any[]> {\n const assetsToInsert = assets.map(asset => ({\n event_id: eventId,\n organization_id: organizationId,\n asset_type: asset.asset_type,\n platform: asset.platform,\n title: asset.title,\n content: asset.content,\n image_url: asset.image_url,\n file_format: asset.file_format,\n dimensions: asset.dimensions,\n metadata: asset.metadata,\n generated_at: new Date().toISOString(),\n is_active: true\n }));\n\n const { data: savedAssets, error } = await supabase\n .from('marketing_kit_assets')\n .insert(assetsToInsert)\n .select();\n\n if (error) {\n throw new Error(`Failed to save assets: ${error.message}`);\n }\n\n return savedAssets || [];\n }\n\n /**\n * Create ZIP download with all assets\n */\n private async createZipDownload(assets: any[], event: EventData): Promise<string> {\n // This would typically use a file storage service to create a ZIP\n // For now, we'll create a placeholder URL\n // In production, you'd use something like AWS S3, Google Cloud Storage, etc.\n \n const zipFileName = `${event.slug}-marketing-kit-${Date.now()}.zip`;\n \n // TODO: Implement actual ZIP creation and upload\n // const zipBuffer = await this.createZipBuffer(assets);\n // const zipUrl = await fileStorageService.uploadFile(zipBuffer, zipFileName);\n \n // For now, return a placeholder\n const zipUrl = `/api/events/${event.id}/marketing-kit/download`;\n \n return zipUrl;\n }\n\n /**\n * Group assets by type for organized display\n */\n private groupAssetsByType(assets: any[]) {\n return assets.reduce((acc, asset) => {\n if (!acc[asset.asset_type]) {\n acc[asset.asset_type] = [];\n }\n acc[asset.asset_type].push(asset);\n return acc;\n }, {});\n }\n\n /**\n * Get existing marketing kit for an event\n */\n async getExistingKit(eventId: string, organizationId: string) {\n const { data: assets, error } = await supabase\n .from('marketing_kit_assets')\n .select('*')\n .eq('event_id', eventId)\n .eq('organization_id', organizationId)\n .eq('is_active', true)\n .order('generated_at', { ascending: false });\n\n if (error) {\n throw new Error(`Failed to fetch marketing kit: ${error.message}`);\n }\n\n return {\n assets: this.groupAssetsByType(assets || []),\n generated_at: assets?.[0]?.generated_at\n };\n }\n\n /**\n * Delete marketing kit assets\n */\n async deleteKit(eventId: string, organizationId: string) {\n const { error } = await supabase\n .from('marketing_kit_assets')\n .update({ is_active: false })\n .eq('event_id', eventId)\n .eq('organization_id', organizationId);\n\n if (error) {\n throw new Error(`Failed to delete marketing kit: ${error.message}`);\n }\n\n return { success: true };\n }\n}\n\nexport const marketingKitService = new MarketingKitService();","usedDeprecatedRules":[]},{"filePath":"/home/tyler/apps/bct-whitelabel/src/lib/marketing-kit.ts","messages":[{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'OPENAI_API_KEY' is assigned a value but never used. Allowed unused vars must match /^_/u.","line":10,"column":7,"nodeType":null,"messageId":"unusedVar","endLine":10,"endColumn":21},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'OPENAI_API_URL' is assigned a value but never used. Allowed unused vars must match /^_/u.","line":11,"column":7,"nodeType":null,"messageId":"unusedVar","endLine":11,"endColumn":21},{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":18,"column":15,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":18,"endColumn":18,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[568,571],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[568,571],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":84,"column":12,"nodeType":null,"messageId":"unusedVar","endLine":84,"endColumn":17},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":105,"column":12,"nodeType":null,"messageId":"unusedVar","endLine":105,"endColumn":17},{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":111,"column":89,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":111,"endColumn":92,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[2681,2684],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[2681,2684],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":119,"column":32,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":119,"endColumn":35,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[2968,2971],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[2968,2971],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":124,"column":12,"nodeType":null,"messageId":"unusedVar","endLine":124,"endColumn":17},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":143,"column":12,"nodeType":null,"messageId":"unusedVar","endLine":143,"endColumn":17},{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":271,"column":70,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":271,"endColumn":73,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[7408,7411],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[7408,7411],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":310,"column":12,"nodeType":null,"messageId":"unusedVar","endLine":310,"endColumn":17},{"ruleId":"no-empty","severity":1,"message":"Empty block statement.","line":310,"column":19,"nodeType":"BlockStatement","messageId":"unexpected","endLine":312,"endColumn":4,"suggestions":[{"messageId":"suggestComment","data":{"type":"block"},"fix":{"range":[8440,8444],"text":" /* empty */ "},"desc":"Add comment inside empty block statement."}]}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":12,"fixableErrorCount":0,"fixableWarningCount":0,"source":"import { createClient } from '@supabase/supabase-js';\nimport type { Database } from './database.types';\n\nconst supabase = createClient<Database>(\n import.meta.env.PUBLIC_SUPABASE_URL,\n import.meta.env.PUBLIC_SUPABASE_ANON_KEY\n);\n\n// OpenAI configuration\nconst OPENAI_API_KEY = import.meta.env.OPENAI_API_KEY;\nconst OPENAI_API_URL = 'https://api.openai.com/v1/chat/completions';\n\nexport interface MarketingAsset {\n id: string;\n event_id: string;\n asset_type: 'flyer' | 'social_post' | 'email_banner' | 'web_banner' | 'print_ad';\n asset_url: string;\n asset_data: any;\n created_at: string;\n}\n\nexport interface MarketingKitData {\n event: {\n id: string;\n title: string;\n description: string;\n start_time: string;\n venue: string;\n image_url?: string;\n };\n assets: MarketingAsset[];\n social_links: {\n facebook?: string;\n twitter?: string;\n instagram?: string;\n website?: string;\n };\n}\n\nexport interface SocialMediaContent {\n id?: string;\n platform: 'facebook' | 'twitter' | 'instagram' | 'linkedin';\n content: string;\n hashtags: string[];\n image_url?: string;\n tone?: 'professional' | 'casual' | 'exciting' | 'informative' | 'urgent';\n votes?: number;\n generated_at?: string;\n}\n\nexport interface EmailTemplate {\n subject: string;\n html_content: string;\n text_content: string;\n preview_text: string;\n}\n\nexport async function loadMarketingKit(eventId: string): Promise<MarketingKitData | null> {\n try {\n // Load event data\n const { data: event, error: eventError } = await supabase\n .from('events')\n .select('id, title, description, start_time, venue, image_url')\n .eq('id', eventId)\n .single();\n\n if (eventError) {\n\n return null;\n }\n\n // Since marketing_kit_assets table doesn't exist, return empty assets\n // This can be implemented later when the table is created\n \n return {\n event: {\n ...event,\n start_time: event.start_time || '',\n description: event.description || ''\n },\n assets: [], // Empty assets for now\n social_links: {}\n };\n } catch (error) {\n\n return null;\n }\n}\n\nexport async function generateMarketingKit(eventId: string): Promise<MarketingKitData | null> {\n try {\n const response = await fetch(`/api/events/${eventId}/marketing-kit`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json'\n }\n });\n\n if (!response.ok) {\n throw new Error('Failed to generate marketing kit');\n }\n\n const data = await response.json();\n return data;\n } catch (error) {\n\n return null;\n }\n}\n\nexport async function saveMarketingAsset(eventId: string, assetType: string, assetData: any): Promise<MarketingAsset | null> {\n try {\n // Since marketing_kit_assets table doesn't exist, return a mock asset\n // This can be implemented later when the table is created\n\n return {\n id: `temp-${Date.now()}`,\n event_id: eventId,\n asset_type: assetType as any,\n asset_url: assetData.url || '',\n asset_data: assetData,\n created_at: new Date().toISOString()\n };\n } catch (error) {\n\n return null;\n }\n}\n\nexport async function updateSocialLinks(eventId: string, socialLinks: Record<string, string>): Promise<boolean> {\n try {\n const { error } = await supabase\n .from('events')\n .update({ social_links: socialLinks })\n .eq('id', eventId);\n\n if (error) {\n\n return false;\n }\n\n return true;\n } catch (error) {\n\n return false;\n }\n}\n\nexport function generateSocialMediaContent(event: MarketingKitData['event']): SocialMediaContent[] {\n const eventDate = new Date(event.date).toLocaleDateString('en-US', {\n weekday: 'long',\n year: 'numeric',\n month: 'long',\n day: 'numeric'\n });\n\n const baseHashtags = ['#event', '#tickets', '#blackcanyontickets'];\n const eventHashtags = event.title.toLowerCase()\n .split(' ')\n .filter(word => word.length > 3)\n .map(word => `#${word.replace(/[^a-zA-Z0-9]/g, '')}`);\n\n const allHashtags = [...baseHashtags, ...eventHashtags.slice(0, 3)];\n\n return [\n {\n platform: 'facebook',\n content: `🎉 Don't miss ${event.title}! Join us on ${eventDate} at ${event.venue}. \n\n${event.description}\n\nGet your tickets now! Link in bio.`,\n hashtags: allHashtags,\n image_url: event.image_url\n },\n {\n platform: 'twitter',\n content: `🎫 ${event.title} - ${eventDate} at ${event.venue}. Get tickets now!`,\n hashtags: allHashtags,\n image_url: event.image_url\n },\n {\n platform: 'instagram',\n content: `✨ ${event.title} ✨\n\n📅 ${eventDate}\n📍 ${event.venue}\n\n${event.description}\n\nTickets available now! Link in bio 🎟️`,\n hashtags: allHashtags,\n image_url: event.image_url\n },\n {\n platform: 'linkedin',\n content: `We're excited to announce ${event.title}, taking place on ${eventDate} at ${event.venue}.\n\n${event.description}\n\nProfessional networking and entertainment combined. Reserve your spot today.`,\n hashtags: allHashtags.slice(0, 3), // LinkedIn prefers fewer hashtags\n image_url: event.image_url\n }\n ];\n}\n\nexport function generateEmailTemplate(event: MarketingKitData['event']): EmailTemplate {\n const eventDate = new Date(event.date).toLocaleDateString('en-US', {\n weekday: 'long',\n year: 'numeric',\n month: 'long',\n day: 'numeric',\n hour: 'numeric',\n minute: '2-digit'\n });\n\n const subject = `Don't Miss ${event.title} - ${eventDate}`;\n const previewText = `Join us for an unforgettable experience at ${event.venue}`;\n\n const htmlContent = `\n <html>\n <body style=\"font-family: Arial, sans-serif; line-height: 1.6; color: #333;\">\n <div style=\"max-width: 600px; margin: 0 auto; padding: 20px;\">\n ${event.image_url ? `<img src=\"${event.image_url}\" alt=\"${event.title}\" style=\"width: 100%; max-width: 600px; height: auto; border-radius: 8px; margin-bottom: 20px;\">` : ''}\n \n <h1 style=\"color: #2563eb; margin-bottom: 20px;\">${event.title}</h1>\n \n <div style=\"background: #f8fafc; padding: 20px; border-radius: 8px; margin-bottom: 20px;\">\n <h2 style=\"margin-top: 0; color: #1e293b;\">Event Details</h2>\n <p><strong>Date:</strong> ${eventDate}</p>\n <p><strong>Venue:</strong> ${event.venue}</p>\n </div>\n \n <p style=\"font-size: 16px; margin-bottom: 20px;\">${event.description}</p>\n \n <div style=\"text-align: center; margin: 30px 0;\">\n <a href=\"#\" style=\"background: linear-gradient(135deg, #2563eb 0%, #7c3aed 100%); color: white; padding: 15px 30px; text-decoration: none; border-radius: 8px; font-weight: bold; display: inline-block;\">Get Tickets Now</a>\n </div>\n \n <div style=\"border-top: 1px solid #e2e8f0; padding-top: 20px; margin-top: 30px; text-align: center; color: #64748b; font-size: 14px;\">\n <p>Powered by Black Canyon Tickets</p>\n </div>\n </div>\n </body>\n </html>\n `;\n\n const textContent = `\n${event.title}\n\nEvent Details:\nDate: ${eventDate}\nVenue: ${event.venue}\n\n${event.description}\n\nGet your tickets now: [TICKET_LINK]\n\nPowered by Black Canyon Tickets\n `;\n\n return {\n subject,\n html_content: htmlContent,\n text_content: textContent,\n preview_text: previewText\n };\n}\n\nexport function generateFlyerData(event: MarketingKitData['event']): any {\n return {\n title: event.title,\n date: new Date(event.date).toLocaleDateString('en-US', {\n weekday: 'long',\n year: 'numeric',\n month: 'long',\n day: 'numeric',\n hour: 'numeric',\n minute: '2-digit'\n }),\n venue: event.venue,\n description: event.description,\n image_url: event.image_url,\n qr_code_url: `https://portal.blackcanyontickets.com/e/${event.id}`,\n template: 'premium',\n colors: {\n primary: '#2563eb',\n secondary: '#7c3aed',\n accent: '#06b6d4',\n text: '#1e293b'\n }\n };\n}\n\nexport async function downloadAsset(assetUrl: string, filename: string): Promise<void> {\n try {\n const response = await fetch(assetUrl);\n const blob = await response.blob();\n const url = window.URL.createObjectURL(blob);\n \n const a = document.createElement('a');\n a.href = url;\n a.download = filename;\n document.body.appendChild(a);\n a.click();\n \n window.URL.revokeObjectURL(url);\n document.body.removeChild(a);\n } catch (error) {\n\n }\n}\n\nexport function copyToClipboard(text: string): Promise<void> {\n return navigator.clipboard.writeText(text);\n}","usedDeprecatedRules":[]},{"filePath":"/home/tyler/apps/bct-whitelabel/src/lib/performance.js","messages":[{"ruleId":"no-undef","severity":2,"message":"'performance' is not defined.","line":77,"column":18,"nodeType":"Identifier","messageId":"undef","endLine":77,"endColumn":29},{"ruleId":"no-undef","severity":2,"message":"'performance' is not defined.","line":81,"column":25,"nodeType":"Identifier","messageId":"undef","endLine":81,"endColumn":36},{"ruleId":"no-undef","severity":2,"message":"'requestAnimationFrame' is not defined.","line":97,"column":5,"nodeType":"Identifier","messageId":"undef","endLine":97,"endColumn":26},{"ruleId":"no-undef","severity":2,"message":"'requestAnimationFrame' is not defined.","line":100,"column":3,"nodeType":"Identifier","messageId":"undef","endLine":100,"endColumn":24}],"suppressedMessages":[],"errorCount":4,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"source":"/**\n * Performance detection and optimization for glassmorphism effects\n * Automatically reduces effects on low-end devices\n */\n\n// Device capability detection\nexport function detectDeviceCapabilities() {\n const capabilities = {\n isLowEnd: false,\n isMobile: false,\n hasReducedMotion: false,\n hardwareConcurrency: navigator.hardwareConcurrency || 2,\n deviceMemory: navigator.deviceMemory || 2,\n connection: navigator.connection || null\n };\n\n // Detect low-end devices\n if (capabilities.hardwareConcurrency <= 2 || capabilities.deviceMemory <= 2) {\n capabilities.isLowEnd = true;\n }\n\n // Check for slow network connection\n if (capabilities.connection && capabilities.connection.effectiveType) {\n const slowConnections = ['slow-2g', '2g', '3g'];\n if (slowConnections.includes(capabilities.connection.effectiveType)) {\n capabilities.isLowEnd = true;\n }\n }\n\n // Detect mobile devices\n capabilities.isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);\n\n // Check for reduced motion preference\n capabilities.hasReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;\n\n return capabilities;\n}\n\n// Apply performance optimizations based on device capabilities\nexport function applyPerformanceOptimizations() {\n const capabilities = detectDeviceCapabilities();\n const body = document.body;\n\n // Add CSS classes for optimization\n if (capabilities.isLowEnd) {\n body.classList.add('low-end-device');\n }\n\n if (capabilities.isMobile) {\n body.classList.add('mobile-device');\n }\n\n if (capabilities.hasReducedMotion) {\n body.classList.add('reduced-motion');\n }\n\n // Log performance optimization decisions\n console.log('Performance optimizations applied:', {\n isLowEnd: capabilities.isLowEnd,\n isMobile: capabilities.isMobile,\n hasReducedMotion: capabilities.hasReducedMotion,\n hardwareConcurrency: capabilities.hardwareConcurrency,\n deviceMemory: capabilities.deviceMemory\n });\n\n return capabilities;\n}\n\n// Monitor performance and adjust effects dynamically\nexport function monitorPerformance() {\n if (!window.performance || !window.performance.getEntriesByType) {\n return;\n }\n\n // Check for frame rate issues\n let frameCount = 0;\n let lastTime = performance.now();\n\n function checkFrameRate() {\n frameCount++;\n const currentTime = performance.now();\n \n if (currentTime - lastTime >= 1000) {\n const fps = frameCount;\n frameCount = 0;\n lastTime = currentTime;\n \n // If FPS is consistently low, reduce effects\n if (fps < 30) {\n document.body.classList.add('performance-degraded');\n console.warn('Low FPS detected, reducing glassmorphism effects');\n } else if (fps > 50) {\n document.body.classList.remove('performance-degraded');\n }\n }\n \n requestAnimationFrame(checkFrameRate);\n }\n \n requestAnimationFrame(checkFrameRate);\n}\n\n// Initialize performance optimizations\nexport function initializePerformanceOptimizations() {\n // Apply initial optimizations\n const capabilities = applyPerformanceOptimizations();\n \n // Start performance monitoring\n if (!capabilities.isLowEnd) {\n monitorPerformance();\n }\n \n // Listen for connection changes\n if (navigator.connection) {\n navigator.connection.addEventListener('change', () => {\n applyPerformanceOptimizations();\n });\n }\n \n // Listen for reduced motion changes\n const reducedMotionQuery = window.matchMedia('(prefers-reduced-motion: reduce)');\n reducedMotionQuery.addEventListener('change', () => {\n applyPerformanceOptimizations();\n });\n}","usedDeprecatedRules":[]},{"filePath":"/home/tyler/apps/bct-whitelabel/src/lib/performance.ts","messages":[{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":12,"column":30,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":12,"endColumn":33,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[293,296],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[293,296],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"no-undef","severity":2,"message":"'NodeJS' is not defined.","line":195,"column":61,"nodeType":"Identifier","messageId":"undef","endLine":195,"endColumn":67},{"ruleId":"no-undef","severity":2,"message":"'NodeJS' is not defined.","line":229,"column":14,"nodeType":"Identifier","messageId":"undef","endLine":229,"endColumn":20},{"ruleId":"no-undef","severity":2,"message":"'NodeJS' is not defined.","line":230,"column":22,"nodeType":"Identifier","messageId":"undef","endLine":230,"endColumn":28},{"ruleId":"no-undef","severity":2,"message":"'NodeJS' is not defined.","line":231,"column":19,"nodeType":"Identifier","messageId":"undef","endLine":231,"endColumn":25},{"ruleId":"no-undef","severity":2,"message":"'PerformanceObserver' is not defined.","line":285,"column":26,"nodeType":"Identifier","messageId":"undef","endLine":285,"endColumn":45},{"ruleId":"no-undef","severity":2,"message":"'PerformanceObserver' is not defined.","line":300,"column":29,"nodeType":"Identifier","messageId":"undef","endLine":300,"endColumn":48},{"ruleId":"no-undef","severity":2,"message":"'PerformanceObserver' is not defined.","line":317,"column":29,"nodeType":"Identifier","messageId":"undef","endLine":317,"endColumn":48},{"ruleId":"no-undef","severity":2,"message":"'performance' is not defined.","line":340,"column":26,"nodeType":"Identifier","messageId":"undef","endLine":340,"endColumn":37},{"ruleId":"no-undef","severity":2,"message":"'PerformanceNavigationTiming' is not defined.","line":340,"column":75,"nodeType":"Identifier","messageId":"undef","endLine":340,"endColumn":102}],"suppressedMessages":[],"errorCount":9,"fatalErrorCount":0,"warningCount":1,"fixableErrorCount":0,"fixableWarningCount":0,"source":"import { logPerformance } from './logger';\nimport { startTransaction, addBreadcrumb } from './sentry';\n\n/**\n * Performance monitoring utilities\n */\n\nexport class PerformanceMonitor {\n private startTime: number;\n private endTime?: number;\n private name: string;\n private sentryTransaction: any;\n\n constructor(name: string, operation: string = 'custom') {\n this.name = name;\n this.startTime = Date.now();\n this.sentryTransaction = startTransaction(name, operation);\n \n addBreadcrumb(`Started ${name}`, 'performance', 'info');\n }\n\n /**\n * Mark the end of the performance measurement\n */\n end(additionalMetrics?: Record<string, number>) {\n this.endTime = Date.now();\n const duration = this.endTime - this.startTime;\n\n // Log to our custom logger\n logPerformance({\n operation: this.name,\n duration,\n additionalMetrics\n });\n\n // Finish Sentry transaction\n if (this.sentryTransaction) {\n this.sentryTransaction.setTag('duration', duration.toString());\n if (additionalMetrics) {\n Object.entries(additionalMetrics).forEach(([key, value]) => {\n this.sentryTransaction.setTag(key, value.toString());\n });\n }\n this.sentryTransaction.finish();\n }\n\n addBreadcrumb(`Completed ${this.name} in ${duration}ms`, 'performance', 'info');\n\n return duration;\n }\n\n /**\n * Get current duration without ending the measurement\n */\n getCurrentDuration(): number {\n return Date.now() - this.startTime;\n }\n}\n\n/**\n * Monitor database query performance\n */\nexport class DatabaseMonitor {\n private static instance: DatabaseMonitor;\n private queryTimes: Map<string, number[]> = new Map();\n\n static getInstance(): DatabaseMonitor {\n if (!DatabaseMonitor.instance) {\n DatabaseMonitor.instance = new DatabaseMonitor();\n }\n return DatabaseMonitor.instance;\n }\n\n /**\n * Track a database query\n */\n trackQuery(query: string, duration: number, table?: string) {\n const key = table || 'unknown';\n if (!this.queryTimes.has(key)) {\n this.queryTimes.set(key, []);\n }\n \n this.queryTimes.get(key)!.push(duration);\n\n // Log slow queries\n if (duration > 1000) { // Queries over 1 second\n\n addBreadcrumb(`Slow query: ${query.substring(0, 100)}...`, 'database', 'warning', {\n duration,\n table\n });\n }\n\n // Clean up old metrics (keep only last 100 per table)\n const times = this.queryTimes.get(key)!;\n if (times.length > 100) {\n times.splice(0, times.length - 100);\n }\n }\n\n /**\n * Get average query time for a table\n */\n getAverageQueryTime(table: string): number {\n const times = this.queryTimes.get(table);\n if (!times || times.length === 0) return 0;\n \n return times.reduce((sum, time) => sum + time, 0) / times.length;\n }\n\n /**\n * Get performance metrics for all tables\n */\n getMetrics(): Record<string, { avg: number; max: number; count: number }> {\n const metrics: Record<string, { avg: number; max: number; count: number }> = {};\n \n for (const [table, times] of this.queryTimes.entries()) {\n if (times.length === 0) continue;\n \n metrics[table] = {\n avg: times.reduce((sum, time) => sum + time, 0) / times.length,\n max: Math.max(...times),\n count: times.length\n };\n }\n \n return metrics;\n }\n}\n\n/**\n * Monitor API endpoint performance\n */\nexport class APIMonitor {\n private static metrics: Map<string, { times: number[]; errors: number }> = new Map();\n\n /**\n * Track API response time\n */\n static trackEndpoint(endpoint: string, method: string, duration: number, statusCode: number) {\n const key = `${method} ${endpoint}`;\n \n if (!this.metrics.has(key)) {\n this.metrics.set(key, { times: [], errors: 0 });\n }\n \n const metric = this.metrics.get(key)!;\n metric.times.push(duration);\n \n if (statusCode >= 400) {\n metric.errors++;\n }\n\n // Clean up old metrics\n if (metric.times.length > 100) {\n metric.times.splice(0, metric.times.length - 100);\n }\n\n // Log slow API calls\n if (duration > 5000) { // API calls over 5 seconds\n\n addBreadcrumb(`Slow API call: ${key}`, 'http', 'warning', {\n duration,\n statusCode\n });\n }\n }\n\n /**\n * Get API performance metrics\n */\n static getMetrics(): Record<string, { avg: number; max: number; count: number; errorRate: number }> {\n const metrics: Record<string, { avg: number; max: number; count: number; errorRate: number }> = {};\n \n for (const [endpoint, data] of this.metrics.entries()) {\n if (data.times.length === 0) continue;\n \n metrics[endpoint] = {\n avg: data.times.reduce((sum, time) => sum + time, 0) / data.times.length,\n max: Math.max(...data.times),\n count: data.times.length,\n errorRate: data.errors / data.times.length\n };\n }\n \n return metrics;\n }\n}\n\n/**\n * Memory usage monitoring\n */\nexport class MemoryMonitor {\n private static lastCheck = Date.now();\n private static samples: Array<{ timestamp: number; usage: NodeJS.MemoryUsage }> = [];\n\n /**\n * Take a memory usage sample\n */\n static sample() {\n const now = Date.now();\n const usage = process.memoryUsage();\n \n this.samples.push({ timestamp: now, usage });\n \n // Keep only last 100 samples\n if (this.samples.length > 100) {\n this.samples.splice(0, this.samples.length - 100);\n }\n\n // Log memory warning if usage is high\n const heapUsedMB = usage.heapUsed / 1024 / 1024;\n if (heapUsedMB > 512) { // Over 512MB\n \n addBreadcrumb(`High memory usage: ${heapUsedMB.toFixed(2)}MB`, 'memory', 'warning', {\n heapUsed: usage.heapUsed,\n heapTotal: usage.heapTotal,\n external: usage.external\n });\n }\n\n this.lastCheck = now;\n }\n\n /**\n * Get memory usage trends\n */\n static getTrends(): {\n current: NodeJS.MemoryUsage;\n average: Partial<NodeJS.MemoryUsage>;\n peak: Partial<NodeJS.MemoryUsage>;\n } {\n if (this.samples.length === 0) {\n return {\n current: process.memoryUsage(),\n average: {},\n peak: {}\n };\n }\n\n const current = this.samples[this.samples.length - 1].usage;\n \n // Calculate averages\n const avgHeapUsed = this.samples.reduce((sum, s) => sum + s.usage.heapUsed, 0) / this.samples.length;\n const avgHeapTotal = this.samples.reduce((sum, s) => sum + s.usage.heapTotal, 0) / this.samples.length;\n \n // Find peaks\n const peakHeapUsed = Math.max(...this.samples.map(s => s.usage.heapUsed));\n const peakHeapTotal = Math.max(...this.samples.map(s => s.usage.heapTotal));\n\n return {\n current,\n average: {\n heapUsed: avgHeapUsed,\n heapTotal: avgHeapTotal\n },\n peak: {\n heapUsed: peakHeapUsed,\n heapTotal: peakHeapTotal\n }\n };\n }\n\n /**\n * Start automatic memory monitoring\n */\n static startMonitoring(intervalMs: number = 60000) { // Default: every minute\n setInterval(() => {\n this.sample();\n }, intervalMs);\n }\n}\n\n/**\n * Web Vitals monitoring for the frontend\n */\nexport const WebVitalsMonitor = {\n /**\n * Monitor Core Web Vitals\n */\n initWebVitals() {\n if (typeof window === 'undefined') return;\n\n // Monitor Largest Contentful Paint (LCP)\n const observer = new PerformanceObserver((list) => {\n for (const entry of list.getEntries()) {\n if (entry.entryType === 'largest-contentful-paint') {\n addBreadcrumb(`LCP: ${entry.startTime.toFixed(2)}ms`, 'performance', 'info');\n \n if (entry.startTime > 2500) { // LCP > 2.5s is poor\n \n }\n }\n }\n });\n\n observer.observe({ entryTypes: ['largest-contentful-paint'] });\n\n // Monitor First Input Delay (FID)\n const fidObserver = new PerformanceObserver((list) => {\n for (const entry of list.getEntries()) {\n if (entry.entryType === 'first-input') {\n const fid = entry.processingStart - entry.startTime;\n addBreadcrumb(`FID: ${fid.toFixed(2)}ms`, 'performance', 'info');\n \n if (fid > 100) { // FID > 100ms is poor\n \n }\n }\n }\n });\n\n fidObserver.observe({ entryTypes: ['first-input'] });\n\n // Monitor Cumulative Layout Shift (CLS)\n let clsValue = 0;\n const clsObserver = new PerformanceObserver((list) => {\n for (const entry of list.getEntries()) {\n if (!entry.hadRecentInput) {\n clsValue += entry.value;\n }\n }\n \n if (clsValue > 0.1) { // CLS > 0.1 is poor\n \n }\n });\n\n clsObserver.observe({ entryTypes: ['layout-shift'] });\n },\n\n /**\n * Monitor page load performance\n */\n trackPageLoad() {\n if (typeof window === 'undefined') return;\n\n window.addEventListener('load', () => {\n setTimeout(() => {\n const perfData = performance.getEntriesByType('navigation')[0] as PerformanceNavigationTiming;\n \n const metrics = {\n domContentLoaded: perfData.domContentLoadedEventEnd - perfData.domContentLoadedEventStart,\n domComplete: perfData.domComplete - perfData.navigationStart,\n loadComplete: perfData.loadEventEnd - perfData.navigationStart,\n firstByte: perfData.responseStart - perfData.requestStart\n };\n\n addBreadcrumb('Page load metrics', 'performance', 'info', metrics);\n\n // Log slow page loads\n if (metrics.loadComplete > 3000) { // Over 3 seconds\n\n }\n }, 0);\n });\n }\n};\n\n/**\n * Utility functions\n */\nexport function measureAsync<T>(name: string, fn: () => Promise<T>): Promise<T> {\n const monitor = new PerformanceMonitor(name, 'async');\n \n return fn()\n .then(result => {\n monitor.end();\n return result;\n })\n .catch(error => {\n monitor.end();\n throw error;\n });\n}\n\nexport function measureSync<T>(name: string, fn: () => T): T {\n const monitor = new PerformanceMonitor(name, 'sync');\n \n try {\n const result = fn();\n monitor.end();\n return result;\n } catch (error) {\n monitor.end();\n throw error;\n }\n}\n\n// Start memory monitoring automatically\nMemoryMonitor.startMonitoring();\n\n// Export all monitors\nexport { DatabaseMonitor, APIMonitor, MemoryMonitor };","usedDeprecatedRules":[]},{"filePath":"/home/tyler/apps/bct-whitelabel/src/lib/qr-generator.ts","messages":[{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":67,"column":14,"nodeType":null,"messageId":"unusedVar","endLine":67,"endColumn":19},{"ruleId":"no-undef","severity":2,"message":"'URL' is not defined.","line":101,"column":11,"nodeType":"Identifier","messageId":"undef","endLine":101,"endColumn":14}],"suppressedMessages":[],"errorCount":1,"fatalErrorCount":0,"warningCount":1,"fixableErrorCount":0,"fixableWarningCount":0,"source":"import QRCode from 'qrcode';\n\ninterface QRCodeOptions {\n size?: number;\n margin?: number;\n color?: {\n dark?: string;\n light?: string;\n };\n errorCorrectionLevel?: 'L' | 'M' | 'Q' | 'H';\n}\n\ninterface QRCodeResult {\n dataUrl: string;\n svg: string;\n size: number;\n}\n\nexport class QRCodeGenerator {\n private defaultOptions: QRCodeOptions = {\n size: 256,\n margin: 2,\n color: {\n dark: '#000000',\n light: '#FFFFFF'\n },\n errorCorrectionLevel: 'M'\n };\n\n /**\n * Generate QR code for event ticket URL\n */\n async generateEventQR(eventSlug: string, options: QRCodeOptions = {}): Promise<QRCodeResult> {\n const ticketUrl = `${import.meta.env.PUBLIC_SITE_URL || 'https://portal.blackcanyontickets.com'}/e/${eventSlug}`;\n return this.generateQRCode(ticketUrl, options);\n }\n\n /**\n * Generate QR code for any URL\n */\n async generateQRCode(url: string, options: QRCodeOptions = {}): Promise<QRCodeResult> {\n const mergedOptions = { ...this.defaultOptions, ...options };\n \n try {\n // Generate data URL (base64 PNG)\n const dataUrl = await QRCode.toDataURL(url, {\n width: mergedOptions.size,\n margin: mergedOptions.margin,\n color: mergedOptions.color,\n errorCorrectionLevel: mergedOptions.errorCorrectionLevel\n });\n\n // Generate SVG\n const svg = await QRCode.toString(url, {\n type: 'svg',\n width: mergedOptions.size,\n margin: mergedOptions.margin,\n color: mergedOptions.color,\n errorCorrectionLevel: mergedOptions.errorCorrectionLevel\n });\n\n return {\n dataUrl,\n svg,\n size: mergedOptions.size || this.defaultOptions.size!\n };\n } catch (error) {\n\n throw new Error('Failed to generate QR code');\n }\n }\n\n /**\n * Generate QR code with custom branding/logo overlay\n */\n async generateBrandedQR(\n url: string, \n logoDataUrl?: string, \n options: QRCodeOptions = {}\n ): Promise<QRCodeResult> {\n const qrResult = await this.generateQRCode(url, {\n ...options,\n errorCorrectionLevel: 'H' // Higher error correction for logo overlay\n });\n\n if (!logoDataUrl) {\n return qrResult;\n }\n\n // If logo is provided, we'll need to composite it onto the QR code\n // This would typically be done server-side with canvas or image processing\n // For now, we'll return the base QR code and handle logo overlay in the image generation\n return qrResult;\n }\n\n /**\n * Validate URL before QR generation\n */\n private validateUrl(url: string): boolean {\n try {\n new URL(url);\n return true;\n } catch {\n return false;\n }\n }\n\n /**\n * Get optimal QR code size for different use cases\n */\n getRecommendedSize(useCase: 'social' | 'flyer' | 'email' | 'print'): number {\n switch (useCase) {\n case 'social':\n return 200;\n case 'flyer':\n return 300;\n case 'email':\n return 150;\n case 'print':\n return 600;\n default:\n return 256;\n }\n }\n\n /**\n * Generate multiple QR code formats for different use cases\n */\n async generateMultiFormat(url: string): Promise<{\n social: QRCodeResult;\n flyer: QRCodeResult;\n email: QRCodeResult;\n print: QRCodeResult;\n }> {\n const [social, flyer, email, print] = await Promise.all([\n this.generateQRCode(url, { size: this.getRecommendedSize('social') }),\n this.generateQRCode(url, { size: this.getRecommendedSize('flyer') }),\n this.generateQRCode(url, { size: this.getRecommendedSize('email') }),\n this.generateQRCode(url, { size: this.getRecommendedSize('print') })\n ]);\n\n return { social, flyer, email, print };\n }\n}\n\n// Export singleton instance\nexport const qrGenerator = new QRCodeGenerator();","usedDeprecatedRules":[]},{"filePath":"/home/tyler/apps/bct-whitelabel/src/lib/qr.ts","messages":[{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":32,"column":12,"nodeType":null,"messageId":"unusedVar","endLine":32,"endColumn":17},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":198,"column":12,"nodeType":null,"messageId":"unusedVar","endLine":198,"endColumn":17}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":2,"fixableErrorCount":0,"fixableWarningCount":0,"source":"import QRCode from 'qrcode';\n\nexport interface TicketData {\n uuid: string;\n eventId: string;\n eventTitle: string;\n purchaserName: string;\n purchaserEmail: string;\n venue: string;\n startTime: string;\n}\n\nexport async function generateQRCode(ticketData: TicketData): Promise<string> {\n try {\n // Create QR code data URL\n const qrData = JSON.stringify({\n uuid: ticketData.uuid,\n eventId: ticketData.eventId,\n type: 'ticket'\n });\n \n const qrCodeDataURL = await QRCode.toDataURL(qrData, {\n width: 300,\n margin: 2,\n color: {\n dark: '#1F2937', // Dark gray\n light: '#FFFFFF' // White\n }\n });\n \n return qrCodeDataURL;\n } catch (error) {\n\n throw new Error('Failed to generate QR code');\n }\n}\n\nexport async function generateTicketHTML(ticketData: TicketData): Promise<string> {\n const qrCodeDataURL = await generateQRCode(ticketData);\n \n const ticketHTML = `\n <!DOCTYPE html>\n <html>\n <head>\n <meta charset=\"UTF-8\">\n <title>Your Ticket - ${ticketData.eventTitle}</title>\n <style>\n body {\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\n margin: 0;\n padding: 20px;\n background-color: #f9fafb;\n }\n .ticket {\n max-width: 600px;\n margin: 0 auto;\n background: white;\n border-radius: 12px;\n overflow: hidden;\n box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);\n }\n .ticket-header {\n background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);\n color: white;\n padding: 24px;\n text-align: center;\n }\n .ticket-header h1 {\n margin: 0;\n font-size: 28px;\n font-weight: 700;\n }\n .ticket-header p {\n margin: 8px 0 0 0;\n opacity: 0.9;\n font-size: 16px;\n }\n .ticket-body {\n padding: 32px 24px;\n }\n .ticket-info {\n display: grid;\n grid-template-columns: 1fr 1fr;\n gap: 24px;\n margin-bottom: 32px;\n }\n .info-item {\n text-align: center;\n }\n .info-label {\n font-size: 12px;\n text-transform: uppercase;\n font-weight: 600;\n color: #6b7280;\n margin-bottom: 4px;\n }\n .info-value {\n font-size: 18px;\n font-weight: 600;\n color: #1f2937;\n }\n .qr-section {\n text-align: center;\n border-top: 2px dashed #e5e7eb;\n padding-top: 32px;\n }\n .qr-code {\n margin: 0 auto 16px;\n display: block;\n }\n .qr-instructions {\n color: #6b7280;\n font-size: 14px;\n line-height: 1.5;\n }\n .ticket-footer {\n background: #f9fafb;\n padding: 16px 24px;\n text-align: center;\n font-size: 12px;\n color: #6b7280;\n }\n @media (max-width: 480px) {\n .ticket-info {\n grid-template-columns: 1fr;\n gap: 16px;\n }\n }\n </style>\n </head>\n <body>\n <div class=\"ticket\">\n <div class=\"ticket-header\">\n <h1>${ticketData.eventTitle}</h1>\n <p>Your ticket confirmation</p>\n </div>\n \n <div class=\"ticket-body\">\n <div class=\"ticket-info\">\n <div class=\"info-item\">\n <div class=\"info-label\">Event Date & Time</div>\n <div class=\"info-value\">${new Date(ticketData.startTime).toLocaleDateString('en-US', {\n weekday: 'long',\n year: 'numeric',\n month: 'long',\n day: 'numeric'\n })}</div>\n <div class=\"info-value\">${new Date(ticketData.startTime).toLocaleTimeString('en-US', {\n hour: 'numeric',\n minute: '2-digit',\n hour12: true\n })}</div>\n </div>\n \n <div class=\"info-item\">\n <div class=\"info-label\">Venue</div>\n <div class=\"info-value\">${ticketData.venue}</div>\n </div>\n \n <div class=\"info-item\">\n <div class=\"info-label\">Ticket Holder</div>\n <div class=\"info-value\">${ticketData.purchaserName}</div>\n </div>\n \n <div class=\"info-item\">\n <div class=\"info-label\">Ticket ID</div>\n <div class=\"info-value\">${ticketData.uuid.substring(0, 8).toUpperCase()}</div>\n </div>\n </div>\n \n <div class=\"qr-section\">\n <img src=\"${qrCodeDataURL}\" alt=\"Ticket QR Code\" class=\"qr-code\" />\n <div class=\"qr-instructions\">\n <strong>Show this QR code at the door</strong><br>\n Keep this email handy or take a screenshot for easy access.\n </div>\n </div>\n </div>\n \n <div class=\"ticket-footer\">\n Powered by Black Canyon Tickets • Questions? Contact the event organizer\n </div>\n </div>\n </body>\n </html>\n `;\n \n return ticketHTML;\n}\n\nexport function parseQRCode(qrData: string): { uuid: string; eventId: string; type: string } | null {\n try {\n const parsed = JSON.parse(qrData);\n if (parsed.type === 'ticket' && parsed.uuid && parsed.eventId) {\n return parsed;\n }\n return null;\n } catch (error) {\n\n return null;\n }\n}","usedDeprecatedRules":[]},{"filePath":"/home/tyler/apps/bct-whitelabel/src/lib/sales-analytics.ts","messages":[{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":123,"column":12,"nodeType":null,"messageId":"unusedVar","endLine":123,"endColumn":17},{"ruleId":"no-case-declarations","severity":2,"message":"Unexpected lexical declaration in case block.","line":163,"column":9,"nodeType":"VariableDeclaration","messageId":"unexpected","endLine":163,"endColumn":42,"suggestions":[{"messageId":"addBrackets","fix":{"range":[4180,4340],"text":"{ const weekStart = new Date(date);\n weekStart.setDate(date.getDate() - date.getDay());\n key = weekStart.toISOString().split('T')[0];\n break; }"},"desc":"Add {} brackets around the case block."}]},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":269,"column":12,"nodeType":null,"messageId":"unusedVar","endLine":269,"endColumn":17}],"suppressedMessages":[],"errorCount":1,"fatalErrorCount":0,"warningCount":2,"fixableErrorCount":0,"fixableWarningCount":0,"source":"import { createClient } from '@supabase/supabase-js';\nimport type { Database } from './database.types';\n\nconst supabase = createClient<Database>(\n import.meta.env.PUBLIC_SUPABASE_URL,\n import.meta.env.PUBLIC_SUPABASE_ANON_KEY\n);\n\nexport interface SalesData {\n id: string;\n event_id: string;\n ticket_type_id: string;\n price: number;\n refund_status: string | null;\n checked_in: boolean;\n purchaser_email: string;\n purchaser_name: string | null;\n created_at: string;\n uuid: string;\n ticket_types: {\n name: string;\n price: number;\n };\n}\n\nexport interface SalesMetrics {\n totalRevenue: number;\n netRevenue: number;\n ticketsSold: number;\n averageTicketPrice: number;\n conversionRate: number;\n refundRate: number;\n}\n\nexport interface SalesFilter {\n ticketTypeId?: string;\n status?: string;\n searchTerm?: string;\n dateFrom?: string;\n dateTo?: string;\n checkedIn?: boolean;\n}\n\nexport interface TimeSeries {\n date: string;\n revenue: number;\n tickets: number;\n}\n\nexport interface TicketTypeBreakdown {\n ticketTypeId: string;\n ticketTypeName: string;\n sold: number;\n revenue: number;\n refunded: number;\n percentage: number;\n}\n\nexport async function loadSalesData(eventId: string, filters?: SalesFilter): Promise<SalesData[]> {\n try {\n let query = supabase\n .from('tickets')\n .select(`\n id,\n event_id,\n ticket_type_id,\n price,\n refund_status,\n checked_in,\n purchaser_email,\n purchaser_name,\n created_at,\n uuid,\n ticket_types (\n name,\n price\n )\n `)\n .eq('event_id', eventId)\n .order('created_at', { ascending: false });\n\n // Apply filters\n if (filters?.ticketTypeId) {\n query = query.eq('ticket_type_id', filters.ticketTypeId);\n }\n\n if (filters?.status) {\n if (filters.status === 'confirmed') {\n query = query.or('refund_status.is.null,refund_status.eq.null');\n } else if (filters.status === 'refunded') {\n query = query.eq('refund_status', 'completed');\n } else if (filters.status === 'pending') {\n query = query.eq('refund_status', 'pending');\n } else if (filters.status === 'cancelled') {\n query = query.eq('refund_status', 'failed');\n }\n }\n\n if (filters?.checkedIn !== undefined) {\n query = query.eq('checked_in', filters.checkedIn);\n }\n\n if (filters?.searchTerm) {\n query = query.or(`purchaser_email.ilike.%${filters.searchTerm}%,purchaser_name.ilike.%${filters.searchTerm}%`);\n }\n\n if (filters?.dateFrom) {\n query = query.gte('created_at', filters.dateFrom);\n }\n\n if (filters?.dateTo) {\n query = query.lte('created_at', filters.dateTo);\n }\n\n const { data: sales, error } = await query;\n\n if (error) {\n\n return [];\n }\n\n return sales || [];\n } catch (error) {\n\n return [];\n }\n}\n\nexport function calculateSalesMetrics(salesData: SalesData[]): SalesMetrics {\n const confirmedSales = salesData.filter(sale => !sale.refund_status || sale.refund_status === null);\n const refundedSales = salesData.filter(sale => sale.refund_status === 'completed');\n\n const totalRevenue = confirmedSales.reduce((sum, sale) => sum + sale.price, 0);\n const netRevenue = totalRevenue * 0.97; // Assuming 3% platform fee\n const ticketsSold = confirmedSales.length;\n const averageTicketPrice = ticketsSold > 0 ? totalRevenue / ticketsSold : 0;\n const refundRate = salesData.length > 0 ? refundedSales.length / salesData.length : 0;\n\n return {\n totalRevenue,\n netRevenue,\n ticketsSold,\n averageTicketPrice,\n conversionRate: 0, // Would need pageview data to calculate\n refundRate\n };\n}\n\nexport function generateTimeSeries(salesData: SalesData[], groupBy: 'day' | 'week' | 'month' = 'day'): TimeSeries[] {\n const groupedData = new Map<string, { revenue: number; tickets: number }>();\n\n salesData.forEach(sale => {\n if (sale.refund_status && sale.refund_status !== null) return;\n\n const date = new Date(sale.created_at);\n let key: string;\n\n switch (groupBy) {\n case 'day':\n key = date.toISOString().split('T')[0];\n break;\n case 'week':\n const weekStart = new Date(date);\n weekStart.setDate(date.getDate() - date.getDay());\n key = weekStart.toISOString().split('T')[0];\n break;\n case 'month':\n key = `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}`;\n break;\n default:\n key = date.toISOString().split('T')[0];\n }\n\n const existing = groupedData.get(key) || { revenue: 0, tickets: 0 };\n existing.revenue += sale.price;\n existing.tickets += 1;\n groupedData.set(key, existing);\n });\n\n return Array.from(groupedData.entries())\n .map(([date, data]) => ({\n date,\n revenue: data.revenue,\n tickets: data.tickets\n }))\n .sort((a, b) => a.date.localeCompare(b.date));\n}\n\nexport function generateTicketTypeBreakdown(salesData: SalesData[]): TicketTypeBreakdown[] {\n const typeMap = new Map<string, {\n name: string;\n sold: number;\n revenue: number;\n refunded: number;\n }>();\n\n salesData.forEach(sale => {\n const key = sale.ticket_type_id;\n const existing = typeMap.get(key) || {\n name: sale.ticket_types.name,\n sold: 0,\n revenue: 0,\n refunded: 0\n };\n\n if (!sale.refund_status || sale.refund_status === null) {\n existing.sold += 1;\n existing.revenue += sale.price;\n } else if (sale.refund_status === 'completed') {\n existing.refunded += 1;\n }\n\n typeMap.set(key, existing);\n });\n\n const totalRevenue = Array.from(typeMap.values()).reduce((sum, type) => sum + type.revenue, 0);\n\n return Array.from(typeMap.entries())\n .map(([ticketTypeId, data]) => ({\n ticketTypeId,\n ticketTypeName: data.name,\n sold: data.sold,\n revenue: data.revenue,\n refunded: data.refunded,\n percentage: totalRevenue > 0 ? (data.revenue / totalRevenue) * 100 : 0\n }))\n .sort((a, b) => b.revenue - a.revenue);\n}\n\nexport async function exportSalesData(eventId: string, format: 'csv' | 'json' = 'csv'): Promise<string> {\n try {\n const salesData = await loadSalesData(eventId);\n \n if (format === 'json') {\n return JSON.stringify(salesData, null, 2);\n }\n\n // CSV format\n const headers = [\n 'Order ID',\n 'Purchaser Name',\n 'Purchaser Email',\n 'Ticket Type',\n 'Price Paid',\n 'Status',\n 'Checked In',\n 'Purchase Date',\n 'Ticket UUID'\n ];\n\n const rows = salesData.map(sale => [\n sale.id,\n sale.purchaser_name || '',\n sale.purchaser_email,\n sale.ticket_types.name,\n formatCurrency(sale.price),\n (!sale.refund_status || sale.refund_status === null) ? 'confirmed' : \n sale.refund_status === 'completed' ? 'refunded' : \n sale.refund_status === 'pending' ? 'pending' : 'failed',\n sale.checked_in ? 'Yes' : 'No',\n new Date(sale.created_at).toLocaleDateString(),\n sale.uuid\n ]);\n\n return [\n headers.join(','),\n ...rows.map(row => row.map(cell => `\"${cell}\"`).join(','))\n ].join('\\n');\n } catch (error) {\n\n return '';\n }\n}\n\nexport function formatCurrency(cents: number): string {\n return new Intl.NumberFormat('en-US', {\n style: 'currency',\n currency: 'USD'\n }).format(cents / 100);\n}\n\nexport function formatPercentage(value: number): string {\n return new Intl.NumberFormat('en-US', {\n style: 'percent',\n minimumFractionDigits: 1,\n maximumFractionDigits: 1\n }).format(value / 100);\n}\n\nexport function generateSalesReport(salesData: SalesData[]): {\n summary: SalesMetrics;\n timeSeries: TimeSeries[];\n ticketTypeBreakdown: TicketTypeBreakdown[];\n} {\n return {\n summary: calculateSalesMetrics(salesData),\n timeSeries: generateTimeSeries(salesData),\n ticketTypeBreakdown: generateTicketTypeBreakdown(salesData)\n };\n}","usedDeprecatedRules":[]},{"filePath":"/home/tyler/apps/bct-whitelabel/src/lib/scanner-lock.ts","messages":[{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":36,"column":12,"nodeType":null,"messageId":"unusedVar","endLine":36,"endColumn":17}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":1,"fixableErrorCount":0,"fixableWarningCount":0,"source":"import bcrypt from 'bcrypt';\n\nconst SALT_ROUNDS = 12;\n\nexport interface ScannerLockData {\n eventId: string;\n pin: string;\n organizerEmail: string;\n eventTitle: string;\n eventStartTime: string;\n}\n\nexport interface UnlockAttemptData {\n eventId: string;\n pin: string;\n ipAddress?: string;\n userAgent?: string;\n deviceInfo?: string;\n}\n\nexport async function hashPin(pin: string): Promise<string> {\n if (!pin || pin.length !== 4 || !/^\\d{4}$/.test(pin)) {\n throw new Error('PIN must be exactly 4 digits');\n }\n \n return await bcrypt.hash(pin, SALT_ROUNDS);\n}\n\nexport async function verifyPin(pin: string, hash: string): Promise<boolean> {\n if (!pin || pin.length !== 4 || !/^\\d{4}$/.test(pin)) {\n return false;\n }\n \n try {\n return await bcrypt.compare(pin, hash);\n } catch (error) {\n\n return false;\n }\n}\n\nexport function generateRandomPin(): string {\n return Math.floor(Math.random() * 10000).toString().padStart(4, '0');\n}\n\nexport function validatePin(pin: string): boolean {\n return /^\\d{4}$/.test(pin);\n}\n\nexport function getDeviceInfo(userAgent?: string): string {\n if (!userAgent) return 'Unknown device';\n \n const device = userAgent.includes('Mobile') ? 'Mobile' : 'Desktop';\n const browser = userAgent.includes('Chrome') ? 'Chrome' : \n userAgent.includes('Firefox') ? 'Firefox' : \n userAgent.includes('Safari') ? 'Safari' : 'Unknown';\n \n return `${device} - ${browser}`;\n}\n\nexport interface ScannerLockConfig {\n lockTimeoutMinutes?: number;\n maxUnlockAttempts?: number;\n lockoutDurationMinutes?: number;\n}\n\nexport const DEFAULT_SCANNER_LOCK_CONFIG: ScannerLockConfig = {\n lockTimeoutMinutes: 1440, // 24 hours\n maxUnlockAttempts: 5,\n lockoutDurationMinutes: 15\n};\n\nexport function shouldLockExpire(createdAt: string, config: ScannerLockConfig = DEFAULT_SCANNER_LOCK_CONFIG): boolean {\n const lockTime = new Date(createdAt);\n const now = new Date();\n const expirationTime = new Date(lockTime.getTime() + (config.lockTimeoutMinutes! * 60 * 1000));\n \n return now > expirationTime;\n}","usedDeprecatedRules":[]},{"filePath":"/home/tyler/apps/bct-whitelabel/src/lib/seating-management.ts","messages":[{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":12,"column":16,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":12,"endColumn":19,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[307,310],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[307,310],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":28,"column":12,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":28,"endColumn":15,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[608,611],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[608,611],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":52,"column":12,"nodeType":null,"messageId":"unusedVar","endLine":52,"endColumn":17},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":72,"column":12,"nodeType":null,"messageId":"unusedVar","endLine":72,"endColumn":17},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":95,"column":12,"nodeType":null,"messageId":"unusedVar","endLine":95,"endColumn":17},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":114,"column":12,"nodeType":null,"messageId":"unusedVar","endLine":114,"endColumn":17},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":144,"column":12,"nodeType":null,"messageId":"unusedVar","endLine":144,"endColumn":17},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":163,"column":12,"nodeType":null,"messageId":"unusedVar","endLine":163,"endColumn":17},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'containerHeight' is assigned a value but never used. Allowed unused args must match /^_/u.","line":322,"column":83,"nodeType":null,"messageId":"unusedVar","endLine":322,"endColumn":98}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":9,"fixableErrorCount":0,"fixableWarningCount":0,"source":"import { createClient } from '@supabase/supabase-js';\nimport type { Database } from './database.types';\n\nconst supabase = createClient<Database>(\n import.meta.env.PUBLIC_SUPABASE_URL,\n import.meta.env.PUBLIC_SUPABASE_ANON_KEY\n);\n\nexport interface SeatingMap {\n id: string;\n name: string;\n layout_data: any;\n organization_id: string;\n created_at: string;\n updated_at: string;\n}\n\nexport interface LayoutItem {\n id: string;\n type: 'table' | 'seat_row' | 'general_area';\n x: number;\n y: number;\n width: number;\n height: number;\n label: string;\n capacity?: number;\n rotation?: number;\n config?: any;\n}\n\nexport interface SeatingMapFormData {\n name: string;\n layout_data: LayoutItem[];\n}\n\nexport type LayoutType = 'theater' | 'reception' | 'concert_hall' | 'general';\n\nexport async function loadSeatingMaps(organizationId: string): Promise<SeatingMap[]> {\n try {\n const { data: seatingMaps, error } = await supabase\n .from('seating_maps')\n .select('*')\n .eq('organization_id', organizationId)\n .order('created_at', { ascending: false });\n\n if (error) {\n\n return [];\n }\n\n return seatingMaps || [];\n } catch (error) {\n\n return [];\n }\n}\n\nexport async function getSeatingMap(seatingMapId: string): Promise<SeatingMap | null> {\n try {\n const { data: seatingMap, error } = await supabase\n .from('seating_maps')\n .select('*')\n .eq('id', seatingMapId)\n .single();\n\n if (error) {\n\n return null;\n }\n\n return seatingMap;\n } catch (error) {\n\n return null;\n }\n}\n\nexport async function createSeatingMap(organizationId: string, seatingMapData: SeatingMapFormData): Promise<SeatingMap | null> {\n try {\n const { data: seatingMap, error } = await supabase\n .from('seating_maps')\n .insert({\n ...seatingMapData,\n organization_id: organizationId\n })\n .select()\n .single();\n\n if (error) {\n\n return null;\n }\n\n return seatingMap;\n } catch (error) {\n\n return null;\n }\n}\n\nexport async function updateSeatingMap(seatingMapId: string, updates: Partial<SeatingMapFormData>): Promise<boolean> {\n try {\n const { error } = await supabase\n .from('seating_maps')\n .update(updates)\n .eq('id', seatingMapId);\n\n if (error) {\n\n return false;\n }\n\n return true;\n } catch (error) {\n\n return false;\n }\n}\n\nexport async function deleteSeatingMap(seatingMapId: string): Promise<boolean> {\n try {\n // Check if any events are using this seating map\n const { data: events } = await supabase\n .from('events')\n .select('id')\n .eq('seating_map_id', seatingMapId)\n .limit(1);\n\n if (events && events.length > 0) {\n throw new Error('Cannot delete seating map that is in use by events');\n }\n\n const { error } = await supabase\n .from('seating_maps')\n .delete()\n .eq('id', seatingMapId);\n\n if (error) {\n\n return false;\n }\n\n return true;\n } catch (error) {\n\n return false;\n }\n}\n\nexport async function applySeatingMapToEvent(eventId: string, seatingMapId: string): Promise<boolean> {\n try {\n const { error } = await supabase\n .from('events')\n .update({ seating_map_id: seatingMapId })\n .eq('id', eventId);\n\n if (error) {\n\n return false;\n }\n\n return true;\n } catch (error) {\n\n return false;\n }\n}\n\nexport function generateInitialLayout(type: LayoutType, capacity: number = 100): LayoutItem[] {\n switch (type) {\n case 'theater':\n return generateTheaterLayout(capacity);\n case 'reception':\n return generateReceptionLayout(capacity);\n case 'concert_hall':\n return generateConcertHallLayout(capacity);\n case 'general':\n return generateGeneralLayout(capacity);\n default:\n return [];\n }\n}\n\nexport function generateTheaterLayout(capacity: number): LayoutItem[] {\n const items: LayoutItem[] = [];\n const seatsPerRow = Math.ceil(Math.sqrt(capacity));\n const numRows = Math.ceil(capacity / seatsPerRow);\n const rowHeight = 40;\n const rowSpacing = 10;\n\n for (let row = 0; row < numRows; row++) {\n const seatsInThisRow = Math.min(seatsPerRow, capacity - (row * seatsPerRow));\n if (seatsInThisRow <= 0) break;\n\n items.push({\n id: `row-${row}`,\n type: 'seat_row',\n x: 50,\n y: 50 + (row * (rowHeight + rowSpacing)),\n width: seatsInThisRow * 30,\n height: rowHeight,\n label: `Row ${String.fromCharCode(65 + row)}`,\n capacity: seatsInThisRow,\n config: {\n seats: seatsInThisRow,\n numbering: 'sequential'\n }\n });\n }\n\n return items;\n}\n\nexport function generateReceptionLayout(capacity: number): LayoutItem[] {\n const items: LayoutItem[] = [];\n const seatsPerTable = 8;\n const numTables = Math.ceil(capacity / seatsPerTable);\n const tableSize = 80;\n const spacing = 20;\n\n const tablesPerRow = Math.ceil(Math.sqrt(numTables));\n \n for (let i = 0; i < numTables; i++) {\n const row = Math.floor(i / tablesPerRow);\n const col = i % tablesPerRow;\n \n items.push({\n id: `table-${i + 1}`,\n type: 'table',\n x: 50 + (col * (tableSize + spacing)),\n y: 50 + (row * (tableSize + spacing)),\n width: tableSize,\n height: tableSize,\n label: `Table ${i + 1}`,\n capacity: Math.min(seatsPerTable, capacity - (i * seatsPerTable)),\n config: {\n shape: 'round',\n seating: 'around'\n }\n });\n }\n\n return items;\n}\n\nexport function generateConcertHallLayout(capacity: number): LayoutItem[] {\n const items: LayoutItem[] = [];\n \n // Main floor\n const mainFloorCapacity = Math.floor(capacity * 0.7);\n items.push({\n id: 'main-floor',\n type: 'general_area',\n x: 50,\n y: 200,\n width: 400,\n height: 200,\n label: 'Main Floor',\n capacity: mainFloorCapacity,\n config: {\n standing: true,\n area_type: 'general_admission'\n }\n });\n\n // Balcony\n const balconyCapacity = capacity - mainFloorCapacity;\n if (balconyCapacity > 0) {\n items.push({\n id: 'balcony',\n type: 'general_area',\n x: 50,\n y: 50,\n width: 400,\n height: 120,\n label: 'Balcony',\n capacity: balconyCapacity,\n config: {\n standing: false,\n area_type: 'assigned_seating'\n }\n });\n }\n\n return items;\n}\n\nexport function generateGeneralLayout(capacity: number): LayoutItem[] {\n return [{\n id: 'general-admission',\n type: 'general_area',\n x: 50,\n y: 50,\n width: 400,\n height: 300,\n label: 'General Admission',\n capacity: capacity,\n config: {\n standing: true,\n area_type: 'general_admission'\n }\n }];\n}\n\nexport function calculateLayoutCapacity(layoutItems: LayoutItem[]): number {\n return layoutItems.reduce((total, item) => total + (item.capacity || 0), 0);\n}\n\nexport function validateLayoutItem(item: LayoutItem): boolean {\n return !!(\n item.id &&\n item.type &&\n typeof item.x === 'number' &&\n typeof item.y === 'number' &&\n typeof item.width === 'number' &&\n typeof item.height === 'number' &&\n item.label &&\n (item.capacity === undefined || typeof item.capacity === 'number')\n );\n}\n\nexport function optimizeLayout(items: LayoutItem[], containerWidth: number = 500, containerHeight: number = 400): LayoutItem[] {\n // Simple auto-arrange algorithm\n const optimized = [...items];\n const padding = 20;\n const spacing = 10;\n \n let currentX = padding;\n let currentY = padding;\n let rowHeight = 0;\n \n optimized.forEach(item => {\n // Check if item fits in current row\n if (currentX + item.width > containerWidth - padding) {\n // Move to next row\n currentX = padding;\n currentY += rowHeight + spacing;\n rowHeight = 0;\n }\n \n // Position item\n item.x = currentX;\n item.y = currentY;\n \n // Update position for next item\n currentX += item.width + spacing;\n rowHeight = Math.max(rowHeight, item.height);\n });\n \n return optimized;\n}","usedDeprecatedRules":[]},{"filePath":"/home/tyler/apps/bct-whitelabel/src/lib/sentry.ts","messages":[{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'hint' is defined but never used. Allowed unused args must match /^_/u.","line":34,"column":23,"nodeType":null,"messageId":"unusedVar","endLine":34,"endColumn":27},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'hint' is defined but never used. Allowed unused args must match /^_/u.","line":81,"column":34,"nodeType":null,"messageId":"unusedVar","endLine":81,"endColumn":38},{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":102,"column":35,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":102,"endColumn":38,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[3176,3179],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[3176,3179],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":133,"column":35,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":133,"endColumn":38,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[3896,3899],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[3896,3899],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":174,"column":94,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":174,"endColumn":97,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[4727,4730],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[4727,4730],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":189,"column":65,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":189,"endColumn":68,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[4960,4963],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[4960,4963],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":200,"column":165,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":200,"endColumn":168,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[5255,5258],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[5255,5258],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":227,"column":48,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":227,"endColumn":51,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[5701,5704],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[5701,5704],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":227,"column":58,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":227,"endColumn":61,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[5711,5714],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[5711,5714],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":228,"column":21,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":228,"endColumn":24,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[5748,5751],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[5748,5751],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":253,"column":18,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":253,"endColumn":21,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[6286,6289],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[6286,6289],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":253,"column":28,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":253,"endColumn":31,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[6296,6299],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[6296,6299],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":253,"column":39,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":253,"endColumn":42,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[6307,6310],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[6307,6310],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":261,"column":20,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":261,"endColumn":23,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[6460,6463],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[6460,6463],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":261,"column":30,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":261,"endColumn":33,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[6470,6473],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[6470,6473],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":261,"column":40,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":261,"endColumn":43,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[6480,6483],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[6480,6483],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":261,"column":51,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":261,"endColumn":54,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[6491,6494],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[6491,6494],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":17,"fixableErrorCount":0,"fixableWarningCount":0,"source":"import * as Sentry from '@sentry/node';\n\n// Sentry configuration\nexport const SENTRY_CONFIG = {\n DSN: process.env.SENTRY_DSN,\n ENVIRONMENT: process.env.NODE_ENV || 'development',\n RELEASE: process.env.SENTRY_RELEASE || 'unknown',\n SAMPLE_RATE: process.env.NODE_ENV === 'production' ? 0.1 : 1.0,\n TRACES_SAMPLE_RATE: process.env.NODE_ENV === 'production' ? 0.1 : 1.0\n};\n\n// Initialize Sentry\nif (SENTRY_CONFIG.DSN) {\n Sentry.init({\n dsn: SENTRY_CONFIG.DSN,\n environment: SENTRY_CONFIG.ENVIRONMENT,\n release: SENTRY_CONFIG.RELEASE,\n sampleRate: SENTRY_CONFIG.SAMPLE_RATE,\n tracesSampleRate: SENTRY_CONFIG.TRACES_SAMPLE_RATE,\n \n // Configure integrations\n integrations: [\n // HTTP integration for tracking HTTP requests\n new Sentry.Integrations.Http({ tracing: true }),\n \n // Express integration if using Express\n // new Sentry.Integrations.Express({ app }),\n \n // Database integration\n new Sentry.Integrations.Postgres(),\n ],\n\n // Configure beforeSend to filter sensitive data\n beforeSend(event, hint) {\n // Filter out sensitive information\n if (event.request) {\n // Remove sensitive headers\n if (event.request.headers) {\n delete event.request.headers['authorization'];\n delete event.request.headers['cookie'];\n delete event.request.headers['x-api-key'];\n }\n\n // Remove sensitive query parameters\n if (event.request.query_string) {\n const sensitiveParams = ['password', 'token', 'key', 'secret'];\n for (const param of sensitiveParams) {\n if (event.request.query_string.includes(param)) {\n event.request.query_string = event.request.query_string.replace(\n new RegExp(`${param}=[^&]*`, 'gi'),\n `${param}=[FILTERED]`\n );\n }\n }\n }\n }\n\n // Filter out sensitive data from breadcrumbs\n if (event.breadcrumbs) {\n event.breadcrumbs = event.breadcrumbs.map(breadcrumb => {\n if (breadcrumb.data) {\n const filteredData = { ...breadcrumb.data };\n for (const key in filteredData) {\n if (key.toLowerCase().includes('password') || \n key.toLowerCase().includes('token') ||\n key.toLowerCase().includes('key') ||\n key.toLowerCase().includes('secret')) {\n filteredData[key] = '[FILTERED]';\n }\n }\n breadcrumb.data = filteredData;\n }\n return breadcrumb;\n });\n }\n\n return event;\n },\n\n // Configure error filtering\n beforeBreadcrumb(breadcrumb, hint) {\n // Filter out noisy breadcrumbs\n if (breadcrumb.category === 'console' && breadcrumb.level === 'log') {\n return null;\n }\n \n return breadcrumb;\n }\n });\n\n} else {\n // Sentry not configured - will use console logging instead\n}\n\n/**\n * Capture an exception with additional context\n */\nexport function captureException(error: Error, context?: {\n userId?: string;\n userEmail?: string;\n requestId?: string;\n additionalData?: Record<string, any>;\n}) {\n if (!SENTRY_CONFIG.DSN) {\n\n return;\n }\n\n Sentry.withScope((scope) => {\n if (context?.userId) {\n scope.setUser({ id: context.userId, email: context.userEmail });\n }\n \n if (context?.requestId) {\n scope.setTag('requestId', context.requestId);\n }\n \n if (context?.additionalData) {\n scope.setContext('additional', context.additionalData);\n }\n \n Sentry.captureException(error);\n });\n}\n\n/**\n * Capture a message with additional context\n */\nexport function captureMessage(message: string, level: 'fatal' | 'error' | 'warning' | 'info' | 'debug' = 'info', context?: {\n userId?: string;\n userEmail?: string;\n requestId?: string;\n additionalData?: Record<string, any>;\n}) {\n if (!SENTRY_CONFIG.DSN) {\n\n return;\n }\n\n Sentry.withScope((scope) => {\n if (context?.userId) {\n scope.setUser({ id: context.userId, email: context.userEmail });\n }\n \n if (context?.requestId) {\n scope.setTag('requestId', context.requestId);\n }\n \n if (context?.additionalData) {\n scope.setContext('additional', context.additionalData);\n }\n \n Sentry.captureMessage(message, level);\n });\n}\n\n/**\n * Track performance transactions\n */\nexport function startTransaction(name: string, operation: string = 'http') {\n if (!SENTRY_CONFIG.DSN) {\n return null;\n }\n\n return Sentry.startTransaction({\n name,\n op: operation\n });\n}\n\n/**\n * Set user context for current scope\n */\nexport function setUserContext(userId: string, userEmail?: string, userData?: Record<string, any>) {\n if (!SENTRY_CONFIG.DSN) {\n return;\n }\n\n Sentry.setUser({\n id: userId,\n email: userEmail,\n ...userData\n });\n}\n\n/**\n * Set additional context\n */\nexport function setContext(key: string, context: Record<string, any>) {\n if (!SENTRY_CONFIG.DSN) {\n return;\n }\n\n Sentry.setContext(key, context);\n}\n\n/**\n * Add breadcrumb for debugging\n */\nexport function addBreadcrumb(message: string, category: string = 'custom', level: 'fatal' | 'error' | 'warning' | 'info' | 'debug' = 'info', data?: Record<string, any>) {\n if (!SENTRY_CONFIG.DSN) {\n return;\n }\n\n Sentry.addBreadcrumb({\n message,\n category,\n level,\n data\n });\n}\n\n/**\n * Flush Sentry (useful for serverless environments)\n */\nexport async function flush(timeout: number = 2000): Promise<boolean> {\n if (!SENTRY_CONFIG.DSN) {\n return true;\n }\n\n return await Sentry.flush(timeout);\n}\n\n/**\n * Error boundary for API routes\n */\nexport function withSentry<T extends (...args: any[]) => any>(fn: T): T {\n return ((...args: any[]) => {\n try {\n const result = fn(...args);\n \n // Handle async functions\n if (result && typeof result.catch === 'function') {\n return result.catch((error: Error) => {\n captureException(error);\n throw error;\n });\n }\n \n return result;\n } catch (error) {\n captureException(error);\n throw error;\n }\n }) as T;\n}\n\n/**\n * Express middleware for Sentry (if needed)\n */\nexport function sentryRequestHandler() {\n if (!SENTRY_CONFIG.DSN) {\n return (req: any, res: any, next: any) => next();\n }\n\n return Sentry.Handlers.requestHandler();\n}\n\nexport function sentryErrorHandler() {\n if (!SENTRY_CONFIG.DSN) {\n return (error: any, req: any, res: any, next: any) => next(error);\n }\n\n return Sentry.Handlers.errorHandler();\n}\n\n/**\n * Health check for Sentry\n */\nexport function healthCheck(): boolean {\n return !!SENTRY_CONFIG.DSN;\n}\n\n// Export Sentry instance for direct use\nexport { Sentry };","usedDeprecatedRules":[]},{"filePath":"/home/tyler/apps/bct-whitelabel/src/lib/simple-auth.ts","messages":[{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'parseCookies' is defined but never used. Allowed unused vars must match /^_/u.","line":8,"column":10,"nodeType":null,"messageId":"unusedVar","endLine":8,"endColumn":22},{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":72,"column":39,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":72,"endColumn":42,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[2216,2219],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[2216,2219],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":72,"column":81,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":72,"endColumn":84,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[2258,2261],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[2258,2261],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":3,"fixableErrorCount":0,"fixableWarningCount":0,"source":"import { supabase } from './supabase';\nimport { createSupabaseServerClientFromRequest } from './supabase-ssr';\nimport type { User, Session } from '@supabase/supabase-js';\n\n/**\n * Parse cookie header into key-value pairs\n */\nfunction parseCookies(cookieHeader: string): Record<string, string> {\n return cookieHeader.split(';').reduce((acc, cookie) => {\n const [key, value] = cookie.trim().split('=');\n if (key && value) {\n acc[key] = decodeURIComponent(value);\n }\n return acc;\n }, {} as Record<string, string>);\n}\n\nexport interface AuthContext {\n user: User;\n session: Session;\n isAdmin?: boolean;\n isSuperAdmin?: boolean;\n organizationId?: string;\n}\n\n/**\n * Simplified authentication verification without logging\n * Uses Supabase's built-in server-side session parsing\n */\nexport async function verifyAuthSimple(request: Request): Promise<AuthContext | null> {\n try {\n // Create SSR Supabase client\n const supabaseSSR = createSupabaseServerClientFromRequest(request);\n \n // Get the session from cookies\n const { data: { session }, error } = await supabaseSSR.auth.getSession();\n \n console.log('Session check:', session ? 'Session found' : 'No session', error?.message);\n \n if (error || !session) {\n // Try Authorization header as fallback\n const authHeader = request.headers.get('Authorization');\n console.log('Auth header:', authHeader ? 'Present' : 'Not present');\n \n if (authHeader && authHeader.startsWith('Bearer ')) {\n const accessToken = authHeader.substring(7);\n \n // Verify the token with Supabase\n const { data, error } = await supabase.auth.getUser(accessToken);\n \n if (error || !data.user) {\n console.log('Bearer token verification failed:', error?.message);\n return null;\n }\n\n return await buildAuthContext(data.user, accessToken, supabaseSSR);\n }\n \n return null;\n }\n\n return await buildAuthContext(session.user, session.access_token, supabaseSSR);\n } catch (error) {\n console.error('Auth verification failed:', error);\n return null;\n }\n}\n\n/**\n * Build auth context with user data\n */\nasync function buildAuthContext(user: any, accessToken: string, supabaseClient: any): Promise<AuthContext> {\n // Get user role from database\n const { data: userRecord, error: dbError } = await supabaseClient\n .from('users')\n .select('role, organization_id, is_super_admin')\n .eq('id', user.id)\n .single();\n\n if (dbError) {\n console.log('Database user lookup failed:', dbError.message);\n // Continue without role data - better than failing entirely\n }\n\n return {\n user,\n session: { access_token: accessToken } as Session,\n isAdmin: userRecord?.role === 'admin' || false,\n isSuperAdmin: userRecord?.role === 'admin' && userRecord?.is_super_admin === true,\n organizationId: userRecord?.organization_id\n };\n}\n\n/**\n * Simplified admin requirement check\n */\nexport async function requireAdminSimple(request: Request): Promise<AuthContext> {\n const auth = await verifyAuthSimple(request);\n \n if (!auth) {\n throw new Error('Authentication required');\n }\n \n if (!auth.isAdmin) {\n throw new Error('Admin access required');\n }\n \n return auth;\n}\n\n/**\n * Super admin requirement check\n */\nexport async function requireSuperAdminSimple(request: Request): Promise<AuthContext> {\n const auth = await verifyAuthSimple(request);\n \n if (!auth) {\n throw new Error('Authentication required');\n }\n \n if (!auth.isSuperAdmin) {\n throw new Error('Super admin access required');\n }\n \n return auth;\n}","usedDeprecatedRules":[]},{"filePath":"/home/tyler/apps/bct-whitelabel/src/lib/social-media-generator.ts","messages":[{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":9,"column":21,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":9,"endColumn":24,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[262,265],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[262,265],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":21,"column":18,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":21,"endColumn":21,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[460,463],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[460,463],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":26,"column":20,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":26,"endColumn":23,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[564,567],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[564,567],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":247,"column":40,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":247,"endColumn":43,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[6964,6967],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[6964,6967],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"no-useless-escape","severity":2,"message":"Unnecessary escape character: \\/.","line":256,"column":49,"nodeType":"Literal","messageId":"unnecessaryEscape","endLine":256,"endColumn":50,"suggestions":[{"messageId":"removeEscape","fix":{"range":[7228,7229],"text":""},"desc":"Remove the `\\`. This maintains the current functionality."},{"messageId":"escapeBackslash","fix":{"range":[7228,7228],"text":"\\"},"desc":"Replace the `\\` with `\\\\` to include the actual backslash character."}]},{"ruleId":"no-useless-escape","severity":2,"message":"Unnecessary escape character: \\/.","line":259,"column":51,"nodeType":"Literal","messageId":"unnecessaryEscape","endLine":259,"endColumn":52,"suggestions":[{"messageId":"removeEscape","fix":{"range":[7372,7373],"text":""},"desc":"Remove the `\\`. This maintains the current functionality."},{"messageId":"escapeBackslash","fix":{"range":[7372,7372],"text":"\\"},"desc":"Replace the `\\` with `\\\\` to include the actual backslash character."}]},{"ruleId":"no-useless-escape","severity":2,"message":"Unnecessary escape character: \\/.","line":262,"column":50,"nodeType":"Literal","messageId":"unnecessaryEscape","endLine":262,"endColumn":51,"suggestions":[{"messageId":"removeEscape","fix":{"range":[7514,7515],"text":""},"desc":"Remove the `\\`. This maintains the current functionality."},{"messageId":"escapeBackslash","fix":{"range":[7514,7514],"text":"\\"},"desc":"Replace the `\\` with `\\\\` to include the actual backslash character."}]}],"suppressedMessages":[],"errorCount":3,"fatalErrorCount":0,"warningCount":4,"fixableErrorCount":0,"fixableWarningCount":0,"source":"import { qrGenerator } from './qr-generator';\nimport { canvasImageGenerator } from './canvas-image-generator';\n\ninterface SocialPost {\n text: string;\n imageUrl: string;\n hashtags: string[];\n dimensions: { width: number; height: number };\n platformSpecific: any;\n}\n\ninterface EventData {\n id: string;\n title: string;\n description: string;\n venue: string;\n start_time: string;\n end_time: string;\n slug: string;\n image_url?: string;\n social_links?: any;\n website_url?: string;\n organizations: {\n name: string;\n logo?: string;\n social_links?: any;\n website_url?: string;\n };\n}\n\nclass SocialMediaGenerator {\n private platformDimensions = {\n facebook: { width: 1200, height: 630 },\n instagram: { width: 1080, height: 1080 },\n twitter: { width: 1200, height: 675 },\n linkedin: { width: 1200, height: 627 }\n };\n\n private platformLimits = {\n facebook: { textLimit: 2000 },\n instagram: { textLimit: 2200 },\n twitter: { textLimit: 280 },\n linkedin: { textLimit: 3000 }\n };\n\n /**\n * Generate social media post for specific platform\n */\n async generatePost(event: EventData, platform: string): Promise<SocialPost> {\n const dimensions = this.platformDimensions[platform] || this.platformDimensions.facebook;\n \n // Generate post text\n const text = this.generatePostText(event, platform);\n \n // Generate hashtags\n const hashtags = this.generateHashtags(event, platform);\n \n // Generate image\n const imageUrl = await this.generateSocialImage(event, platform, dimensions);\n \n // Platform-specific configuration\n const platformSpecific = this.getPlatformSpecificConfig(event, platform);\n\n return {\n text,\n imageUrl,\n hashtags,\n dimensions,\n platformSpecific\n };\n }\n\n /**\n * Generate platform-appropriate post text\n */\n private generatePostText(event: EventData, platform: string): string {\n const eventDate = new Date(event.start_time);\n const formattedDate = eventDate.toLocaleDateString('en-US', {\n weekday: 'long',\n year: 'numeric',\n month: 'long',\n day: 'numeric'\n });\n const formattedTime = eventDate.toLocaleTimeString('en-US', {\n hour: 'numeric',\n minute: '2-digit',\n hour12: true\n });\n\n // Get social handles from event or organization\n const socialLinks = event.social_links || event.organizations.social_links || {};\n const orgHandle = this.getSocialHandle(socialLinks, platform);\n \n // Get ticket URL\n const ticketUrl = `${import.meta.env.PUBLIC_SITE_URL || 'https://portal.blackcanyontickets.com'}/e/${event.slug}`;\n\n const templates = {\n facebook: `🎉 You're Invited: ${event.title}\n\n📅 ${formattedDate} at ${formattedTime}\n📍 ${event.venue}\n\n${event.description ? event.description.substring(0, 300) + (event.description.length > 300 ? '...' : '') : 'Join us for an unforgettable experience!'}\n\n🎫 Get your tickets now: ${ticketUrl}\n\n${orgHandle ? `Follow us: ${orgHandle}` : ''}\n\n#Events #Tickets #${event.venue.replace(/\\s+/g, '')}`,\n\n instagram: `✨ ${event.title} ✨\n\n📅 ${formattedDate}\n⏰ ${formattedTime}\n📍 ${event.venue}\n\n${event.description ? event.description.substring(0, 200) + '...' : 'An experience you won\\'t want to miss! 🎭'}\n\nLink in bio for tickets 🎫\n👆 or scan the QR code in this post\n\n${orgHandle ? `Follow ${orgHandle} for more events` : ''}`,\n\n twitter: `🎉 ${event.title}\n\n📅 ${formattedDate} • ${formattedTime}\n📍 ${event.venue}\n\n🎫 Tickets: ${ticketUrl}\n\n${orgHandle || ''}`,\n\n linkedin: `Professional Event Announcement: ${event.title}\n\nDate: ${formattedDate}\nTime: ${formattedTime}\nVenue: ${event.venue}\n\n${event.description ? event.description.substring(0, 400) : 'We invite you to join us for this professional gathering.'}\n\nSecure your tickets: ${ticketUrl}\n\n${orgHandle ? `Connect with us: ${orgHandle}` : ''}\n\n#ProfessionalEvents #Networking #${event.organizations.name.replace(/\\s+/g, '')}`\n };\n\n const text = templates[platform] || templates.facebook;\n const limit = this.platformLimits[platform]?.textLimit || 2000;\n \n return text.length > limit ? text.substring(0, limit - 3) + '...' : text;\n }\n\n /**\n * Generate relevant hashtags for the event\n */\n private generateHashtags(event: EventData, platform: string): string[] {\n const baseHashtags = [\n 'Events',\n 'Tickets',\n event.organizations.name.replace(/\\s+/g, ''),\n event.venue.replace(/\\s+/g, ''),\n 'EventTickets'\n ];\n\n // Add date-based hashtags\n const eventDate = new Date(event.start_time);\n const month = eventDate.toLocaleDateString('en-US', { month: 'long' });\n const year = eventDate.getFullYear();\n baseHashtags.push(`${month}${year}`);\n\n // Platform-specific hashtag strategies\n const platformHashtags = {\n facebook: [...baseHashtags, 'LocalEvents', 'Community'],\n instagram: [...baseHashtags, 'InstaEvent', 'EventPlanning', 'Memories', 'Experience'],\n twitter: [...baseHashtags.slice(0, 3)], // Twitter users prefer fewer hashtags\n linkedin: [...baseHashtags, 'ProfessionalEvents', 'Networking', 'Business']\n };\n\n return platformHashtags[platform] || baseHashtags;\n }\n\n /**\n * Generate social media image with event details\n */\n private async generateSocialImage(\n event: EventData, \n platform: string, \n dimensions: { width: number; height: number }\n ): Promise<string> {\n // Generate QR code for the event\n const qrCode = await qrGenerator.generateEventQR(event.slug, {\n size: platform === 'instagram' ? 200 : 150,\n color: { dark: '#000000', light: '#FFFFFF' }\n });\n\n // Generate branded image with canvas\n const imageConfig = {\n width: dimensions.width,\n height: dimensions.height,\n platform,\n event,\n qrCode: qrCode.dataUrl,\n backgroundColor: this.getPlatformTheme(platform).backgroundColor,\n textColor: this.getPlatformTheme(platform).textColor,\n accentColor: this.getPlatformTheme(platform).accentColor\n };\n\n const imageUrl = await canvasImageGenerator.generateSocialImage(imageConfig);\n \n return imageUrl;\n }\n\n /**\n * Get platform-specific theme colors\n */\n private getPlatformTheme(platform: string) {\n const themes = {\n facebook: {\n backgroundColor: ['#1877F2', '#4267B2'],\n textColor: '#FFFFFF',\n accentColor: '#FF6B35'\n },\n instagram: {\n backgroundColor: ['#E4405F', '#F77737', '#FCAF45'],\n textColor: '#FFFFFF',\n accentColor: '#C13584'\n },\n twitter: {\n backgroundColor: ['#1DA1F2', '#0084b4'],\n textColor: '#FFFFFF',\n accentColor: '#FF6B6B'\n },\n linkedin: {\n backgroundColor: ['#0077B5', '#004182'],\n textColor: '#FFFFFF',\n accentColor: '#2867B2'\n }\n };\n\n return themes[platform] || themes.facebook;\n }\n\n /**\n * Get social handle for platform\n */\n private getSocialHandle(socialLinks: any, platform: string): string {\n if (!socialLinks || !socialLinks[platform]) {\n return '';\n }\n\n const url = socialLinks[platform];\n \n // Extract handle from URL\n if (platform === 'twitter') {\n const match = url.match(/twitter\\.com\\/([^\\/]+)/);\n return match ? `@${match[1]}` : '';\n } else if (platform === 'instagram') {\n const match = url.match(/instagram\\.com\\/([^\\/]+)/);\n return match ? `@${match[1]}` : '';\n } else if (platform === 'facebook') {\n const match = url.match(/facebook\\.com\\/([^\\/]+)/);\n return match ? `facebook.com/${match[1]}` : '';\n } else if (platform === 'linkedin') {\n return url;\n }\n\n return url;\n }\n\n /**\n * Get platform-specific configuration\n */\n private getPlatformSpecificConfig(event: EventData, platform: string) {\n const ticketUrl = `${import.meta.env.PUBLIC_SITE_URL || 'https://portal.blackcanyontickets.com'}/e/${event.slug}`;\n \n return {\n facebook: {\n linkUrl: ticketUrl,\n callToAction: 'Get Tickets',\n eventType: 'ticket_sales'\n },\n instagram: {\n linkInBio: true,\n storyLink: ticketUrl,\n callToAction: 'Link in Bio 👆'\n },\n twitter: {\n linkUrl: ticketUrl,\n tweetIntent: `Check out ${event.title} - ${ticketUrl}`,\n callToAction: 'Get Tickets'\n },\n linkedin: {\n linkUrl: ticketUrl,\n eventType: 'professional',\n callToAction: 'Secure Your Spot'\n }\n }[platform];\n }\n\n /**\n * Generate multiple variations of a post\n */\n async generateVariations(event: EventData, platform: string, count: number = 3): Promise<SocialPost[]> {\n const variations: SocialPost[] = [];\n \n for (let i = 0; i < count; i++) {\n // Modify the approach slightly for each variation\n const variation = await this.generatePost(event, platform);\n \n // TODO: Implement different text styles, image layouts, etc.\n variations.push(variation);\n }\n \n return variations;\n }\n\n /**\n * Get optimal posting times for platform\n */\n getOptimalPostingTimes(platform: string): string[] {\n const times = {\n facebook: ['9:00 AM', '1:00 PM', '7:00 PM'],\n instagram: ['11:00 AM', '2:00 PM', '8:00 PM'],\n twitter: ['8:00 AM', '12:00 PM', '6:00 PM'],\n linkedin: ['8:00 AM', '10:00 AM', '5:00 PM']\n };\n\n return times[platform] || times.facebook;\n }\n}\n\nexport const socialMediaGenerator = new SocialMediaGenerator();","usedDeprecatedRules":[]},{"filePath":"/home/tyler/apps/bct-whitelabel/src/lib/stripe-account-switching.ts","messages":[{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":85,"column":12,"nodeType":null,"messageId":"unusedVar","endLine":85,"endColumn":17},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":225,"column":12,"nodeType":null,"messageId":"unusedVar","endLine":225,"endColumn":17},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":238,"column":12,"nodeType":null,"messageId":"unusedVar","endLine":238,"endColumn":17}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":3,"fixableErrorCount":0,"fixableWarningCount":0,"source":"import Stripe from 'stripe';\nimport { supabase } from './supabase';\n\n// Main platform Stripe instance\nconst stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, {\n apiVersion: '2023-10-16',\n});\n\n// Interface for payment processing options\ninterface PaymentOptions {\n eventId: string;\n userId: string;\n amount: number;\n currency: string;\n customerId?: string;\n metadata?: Record<string, string>;\n}\n\n// Interface for platform fee calculation\ninterface FeeCalculation {\n platformFee: number;\n feeType: 'percentage' | 'fixed' | 'none';\n feePercentage?: number;\n feeFixed?: number;\n useCustomStripe: boolean;\n stripeAccountId?: string;\n}\n\n/**\n * Determines the appropriate Stripe account and fee structure for an event\n */\nexport async function getPaymentConfiguration(eventId: string, userId: string): Promise<FeeCalculation> {\n try {\n // Check if user has custom pricing profile\n const { data: profile } = await supabase\n .from('custom_pricing_profiles')\n .select('*')\n .eq('user_id', userId)\n .single();\n\n if (!profile) {\n // Use default platform settings\n return {\n platformFee: 0,\n feeType: 'percentage',\n feePercentage: 2.9, // Default platform fee\n useCustomStripe: false\n };\n }\n\n // Check for event-specific overrides\n const { data: override } = await supabase\n .from('event_pricing_overrides')\n .select('*')\n .eq('event_id', eventId)\n .eq('custom_pricing_profile_id', profile.id)\n .single();\n\n if (override) {\n // Use event-specific override\n const platformFee = calculatePlatformFee(100, override.platform_fee_type, override.platform_fee_percentage, override.platform_fee_fixed);\n \n return {\n platformFee,\n feeType: override.platform_fee_type || 'percentage',\n feePercentage: override.platform_fee_percentage,\n feeFixed: override.platform_fee_fixed,\n useCustomStripe: override.use_custom_stripe_account && !!profile.stripe_account_id,\n stripeAccountId: override.use_custom_stripe_account ? profile.stripe_account_id : undefined\n };\n }\n\n // Use profile defaults\n const platformFee = calculatePlatformFee(100, profile.custom_platform_fee_type, profile.custom_platform_fee_percentage, profile.custom_platform_fee_fixed);\n \n return {\n platformFee,\n feeType: profile.custom_platform_fee_type || 'percentage',\n feePercentage: profile.custom_platform_fee_percentage,\n feeFixed: profile.custom_platform_fee_fixed,\n useCustomStripe: profile.use_personal_stripe && !!profile.stripe_account_id,\n stripeAccountId: profile.use_personal_stripe ? profile.stripe_account_id : undefined\n };\n\n } catch (error) {\n\n // Return default configuration on error\n return {\n platformFee: 0,\n feeType: 'percentage',\n feePercentage: 2.9,\n useCustomStripe: false\n };\n }\n}\n\n/**\n * Calculates platform fee based on type and configuration\n */\nfunction calculatePlatformFee(\n amount: number,\n feeType?: string,\n feePercentage?: number,\n feeFixed?: number\n): number {\n switch (feeType) {\n case 'percentage':\n return Math.round((amount * (feePercentage || 0)) / 100);\n case 'fixed':\n return feeFixed || 0;\n case 'none':\n return 0;\n default:\n return Math.round((amount * 2.9) / 100); // Default 2.9%\n }\n}\n\n/**\n * Creates a payment intent with the appropriate Stripe account\n */\nexport async function createPaymentIntent(options: PaymentOptions): Promise<Stripe.PaymentIntent> {\n const config = await getPaymentConfiguration(options.eventId, options.userId);\n \n const platformFee = calculatePlatformFee(options.amount, config.feeType, config.feePercentage, config.feeFixed);\n \n if (config.useCustomStripe && config.stripeAccountId) {\n // Use custom Stripe account - payment goes directly to organizer\n const customStripe = new Stripe(process.env.STRIPE_SECRET_KEY!, {\n apiVersion: '2023-10-16',\n stripeAccount: config.stripeAccountId\n });\n\n return await customStripe.paymentIntents.create({\n amount: options.amount,\n currency: options.currency,\n customer: options.customerId,\n metadata: {\n event_id: options.eventId,\n user_id: options.userId,\n platform_fee: platformFee.toString(),\n custom_stripe_account: 'true',\n ...options.metadata\n }\n });\n } else {\n // Use platform Stripe account with application fee\n const applicationFee = platformFee > 0 ? platformFee : undefined;\n \n return await stripe.paymentIntents.create({\n amount: options.amount,\n currency: options.currency,\n customer: options.customerId,\n application_fee_amount: applicationFee,\n metadata: {\n event_id: options.eventId,\n user_id: options.userId,\n platform_fee: platformFee.toString(),\n custom_stripe_account: 'false',\n ...options.metadata\n }\n });\n }\n}\n\n/**\n * Creates a checkout session with the appropriate Stripe account\n */\nexport async function createCheckoutSession(options: {\n eventId: string;\n userId: string;\n lineItems: Stripe.Checkout.SessionCreateParams.LineItem[];\n successUrl: string;\n cancelUrl: string;\n metadata?: Record<string, string>;\n}): Promise<Stripe.Checkout.Session> {\n const config = await getPaymentConfiguration(options.eventId, options.userId);\n \n const sessionParams: Stripe.Checkout.SessionCreateParams = {\n payment_method_types: ['card'],\n line_items: options.lineItems,\n mode: 'payment',\n success_url: options.successUrl,\n cancel_url: options.cancelUrl,\n metadata: {\n event_id: options.eventId,\n user_id: options.userId,\n custom_stripe_account: config.useCustomStripe ? 'true' : 'false',\n ...options.metadata\n }\n };\n\n if (config.useCustomStripe && config.stripeAccountId) {\n // Use custom Stripe account\n const customStripe = new Stripe(process.env.STRIPE_SECRET_KEY!, {\n apiVersion: '2023-10-16',\n stripeAccount: config.stripeAccountId\n });\n\n return await customStripe.checkout.sessions.create(sessionParams);\n } else {\n // Use platform Stripe account\n const totalAmount = options.lineItems.reduce((sum, item) => {\n return sum + (item.price_data?.unit_amount || 0) * (item.quantity || 1);\n }, 0);\n\n const platformFee = calculatePlatformFee(totalAmount, config.feeType, config.feePercentage, config.feeFixed);\n \n if (platformFee > 0) {\n sessionParams.payment_intent_data = {\n application_fee_amount: platformFee\n };\n }\n\n return await stripe.checkout.sessions.create(sessionParams);\n }\n}\n\n/**\n * Validates that a custom Stripe account is properly set up\n */\nexport async function validateStripeAccount(stripeAccountId: string): Promise<boolean> {\n try {\n const account = await stripe.accounts.retrieve(stripeAccountId);\n return account.charges_enabled && account.payouts_enabled;\n } catch (error) {\n\n return false;\n }\n}\n\n/**\n * Gets the display name for a Stripe account\n */\nexport async function getStripeAccountName(stripeAccountId: string): Promise<string> {\n try {\n const account = await stripe.accounts.retrieve(stripeAccountId);\n return account.display_name || account.email || 'Custom Account';\n } catch (error) {\n\n return 'Custom Account';\n }\n}","usedDeprecatedRules":[]},{"filePath":"/home/tyler/apps/bct-whitelabel/src/lib/stripe.ts","messages":[{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'isConfigValid' is assigned a value but never used. Allowed unused vars must match /^_/u.","line":47,"column":7,"nodeType":null,"messageId":"unusedVar","endLine":47,"endColumn":20}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":1,"fixableErrorCount":0,"fixableWarningCount":0,"source":"import Stripe from 'stripe';\n\n// Stripe configuration for Connect integration\nexport const STRIPE_CONFIG = {\n // Stripe Connect settings\n CONNECT_CLIENT_ID: import.meta.env.STRIPE_CONNECT_CLIENT_ID,\n PUBLISHABLE_KEY: import.meta.env.PUBLIC_STRIPE_PUBLISHABLE_KEY || import.meta.env.STRIPE_PUBLISHABLE_KEY,\n SECRET_KEY: import.meta.env.STRIPE_SECRET_KEY,\n WEBHOOK_SECRET: import.meta.env.STRIPE_WEBHOOK_SECRET,\n};\n\n// Validate required environment variables\nfunction validateStripeConfig() {\n const errors = [];\n \n if (!STRIPE_CONFIG.SECRET_KEY && typeof window === 'undefined') {\n errors.push('STRIPE_SECRET_KEY is required for server-side operations');\n }\n \n if (!STRIPE_CONFIG.PUBLISHABLE_KEY) {\n errors.push('PUBLIC_STRIPE_PUBLISHABLE_KEY (or STRIPE_PUBLISHABLE_KEY) is required for client-side operations');\n }\n \n if (!STRIPE_CONFIG.WEBHOOK_SECRET && typeof window === 'undefined') {\n errors.push('STRIPE_WEBHOOK_SECRET is required for webhook validation');\n }\n \n if (errors.length > 0) {\n const errorMessage = `Stripe configuration errors:\\n${errors.map(e => ` - ${e}`).join('\\n')}`;\n \n if (import.meta.env.DEV) {\n console.error('🔥 Stripe Configuration Errors:');\n errors.forEach(error => console.error(` ❌ ${error}`));\n console.error('\\n📖 Check your .env file and ensure these variables are set correctly.');\n } else {\n // In production, throw errors for missing config\n throw new Error(errorMessage);\n }\n } else if (import.meta.env.DEV) {\n console.log('✅ Stripe configuration validated successfully');\n }\n \n return errors.length === 0;\n}\n\n// Run validation\nconst isConfigValid = validateStripeConfig();\n\n// Initialize Stripe instance (server-side only)\nexport const stripe = typeof window === 'undefined' && STRIPE_CONFIG.SECRET_KEY\n ? new Stripe(STRIPE_CONFIG.SECRET_KEY, {\n apiVersion: '2024-06-20'\n })\n : null;\n\n// Fee structure types\nexport type FeeType = 'percentage' | 'fixed' | 'percentage_plus_fixed';\nexport type FeeModel = 'customer_pays' | 'absorbed_in_price';\n\nexport interface FeeStructure {\n fee_type: FeeType;\n fee_percentage: number; // decimal (0.03 = 3%)\n fee_fixed: number; // cents\n fee_model: FeeModel;\n absorb_fee_in_price: boolean;\n}\n\n// Default BCT platform fee structure\nexport const DEFAULT_FEE_STRUCTURE: FeeStructure = {\n fee_type: 'percentage_plus_fixed',\n fee_percentage: 0.025, // 2.5% BCT platform fee\n fee_fixed: 150, // $1.50 BCT platform fee\n fee_model: 'customer_pays',\n absorb_fee_in_price: false,\n};\n\n// Stripe processing fee structure (for total cost calculation)\nexport const STRIPE_FEE_STRUCTURE: FeeStructure = {\n fee_type: 'percentage_plus_fixed',\n fee_percentage: 0.0299, // 2.99% Stripe fee\n fee_fixed: 30, // $0.30 Stripe fee\n fee_model: 'customer_pays',\n absorb_fee_in_price: false,\n};\n\n// Calculate platform fee for a given ticket price and fee structure\nexport function calculatePlatformFee(ticketPrice: number, feeStructure?: FeeStructure): number {\n const priceInCents = Math.round(ticketPrice * 100);\n const fees = feeStructure || DEFAULT_FEE_STRUCTURE;\n \n let fee = 0;\n \n switch (fees.fee_type) {\n case 'percentage':\n fee = Math.round(priceInCents * fees.fee_percentage);\n break;\n case 'fixed':\n fee = fees.fee_fixed;\n break;\n case 'percentage_plus_fixed':\n fee = Math.round(priceInCents * fees.fee_percentage) + fees.fee_fixed;\n break;\n default:\n fee = Math.round(priceInCents * DEFAULT_FEE_STRUCTURE.fee_percentage) + DEFAULT_FEE_STRUCTURE.fee_fixed;\n }\n \n return Math.max(0, fee); // Ensure fee is never negative\n}\n\n// Calculate net amount organizer receives\nexport function calculateOrganizerNet(ticketPrice: number, feeStructure?: FeeStructure): number {\n const priceInCents = Math.round(ticketPrice * 100);\n const fee = calculatePlatformFee(ticketPrice, feeStructure);\n return Math.max(0, priceInCents - fee); // Ensure net is never negative\n}\n\n// Format fee structure for display\nexport function formatFeeStructure(feeStructure: FeeStructure): string {\n switch (feeStructure.fee_type) {\n case 'percentage':\n return `${(feeStructure.fee_percentage * 100).toFixed(2)}%`;\n case 'fixed':\n return `$${(feeStructure.fee_fixed / 100).toFixed(2)}`;\n case 'percentage_plus_fixed':\n return `${(feeStructure.fee_percentage * 100).toFixed(2)}% + $${(feeStructure.fee_fixed / 100).toFixed(2)}`;\n default:\n return 'Unknown fee structure';\n }\n}\n\n// Calculate the display price shown to customers\nexport function calculateDisplayPrice(ticketPrice: number, feeStructure?: FeeStructure): number {\n const fees = feeStructure || DEFAULT_FEE_STRUCTURE;\n \n if (fees.fee_model === 'absorbed_in_price') {\n // If fee is absorbed, the display price includes the platform fee\n // to maintain the same organizer net, we need to add the fee to the display price\n const platformFee = calculatePlatformFee(ticketPrice, feeStructure);\n return Math.round(ticketPrice * 100) + platformFee;\n } else {\n // Customer pays fee separately, so display price is just the base ticket price\n return Math.round(ticketPrice * 100);\n }\n}\n\n// Calculate total amount customer actually pays\nexport function calculateCustomerTotal(ticketPrice: number, feeStructure?: FeeStructure): number {\n const fees = feeStructure || DEFAULT_FEE_STRUCTURE;\n const priceInCents = Math.round(ticketPrice * 100);\n \n if (fees.fee_model === 'absorbed_in_price') {\n // Customer pays only the display price (fee is included)\n return calculateDisplayPrice(ticketPrice, feeStructure);\n } else {\n // Customer pays base price + platform fee\n const platformFee = calculatePlatformFee(ticketPrice, feeStructure);\n return priceInCents + platformFee;\n }\n}\n\n// Calculate Stripe processing fee separately\nexport function calculateStripeFee(amount: number): number {\n const amountInCents = Math.round(amount * 100);\n return Math.round(amountInCents * STRIPE_FEE_STRUCTURE.fee_percentage) + STRIPE_FEE_STRUCTURE.fee_fixed;\n}\n\n// Calculate complete transaction breakdown including BCT and Stripe fees\nexport function calculateCompleteTransactionBreakdown(ticketPrice: number, quantity: number, feeStructure?: FeeStructure) {\n const fees = feeStructure || DEFAULT_FEE_STRUCTURE;\n const bctFeePerTicket = calculatePlatformFee(ticketPrice, feeStructure);\n const customerTotalPerTicket = calculateCustomerTotal(ticketPrice, feeStructure);\n const totalCustomerPays = customerTotalPerTicket * quantity;\n \n // Calculate Stripe fee on the total amount customer pays\n const stripeFeeTotal = calculateStripeFee(totalCustomerPays / 100);\n \n // Calculate what organizer actually receives after both BCT and Stripe fees\n const bctFeeTotal = bctFeePerTicket * quantity;\n const organizerGrossRevenue = (Math.round(ticketPrice * 100) * quantity);\n const organizerNetAfterBCT = organizerGrossRevenue - bctFeeTotal;\n const organizerNetAfterAllFees = organizerNetAfterBCT - stripeFeeTotal;\n \n return {\n // Customer perspective\n ticketPricePerTicket: Math.round(ticketPrice * 100),\n bctFeePerTicket: bctFeePerTicket,\n customerTotalPerTicket: customerTotalPerTicket,\n totalCustomerPays: totalCustomerPays,\n \n // Breakdown for quantity\n subtotalBeforeFees: organizerGrossRevenue,\n bctFeeTotal: bctFeeTotal,\n stripeFeeTotal: stripeFeeTotal,\n \n // Organizer perspective \n organizerGrossRevenue: organizerGrossRevenue,\n organizerNetAfterBCT: organizerNetAfterBCT,\n organizerNetAfterAllFees: organizerNetAfterAllFees,\n \n // Fee model info\n feeModel: fees.fee_model,\n feeAbsorbed: fees.absorb_fee_in_price,\n \n // Formatted strings\n ticketPricePerTicketFormatted: `$${(Math.round(ticketPrice * 100) / 100).toFixed(2)}`,\n bctFeePerTicketFormatted: `$${(bctFeePerTicket / 100).toFixed(2)}`,\n customerTotalPerTicketFormatted: `$${(customerTotalPerTicket / 100).toFixed(2)}`,\n totalCustomerPaysFormatted: `$${(totalCustomerPays / 100).toFixed(2)}`,\n subtotalBeforeFeesFormatted: `$${(organizerGrossRevenue / 100).toFixed(2)}`,\n bctFeeTotalFormatted: `$${(bctFeeTotal / 100).toFixed(2)}`,\n stripeFeeTotalFormatted: `$${(stripeFeeTotal / 100).toFixed(2)}`,\n organizerGrossRevenueFormatted: `$${(organizerGrossRevenue / 100).toFixed(2)}`,\n organizerNetAfterBCTFormatted: `$${(organizerNetAfterBCT / 100).toFixed(2)}`,\n organizerNetAfterAllFeesFormatted: `$${(organizerNetAfterAllFees / 100).toFixed(2)}`,\n };\n}\n\n// Calculate fee breakdown for display (legacy function, kept for compatibility)\nexport function calculateFeeBreakdown(ticketPrice: number, quantity: number, feeStructure?: FeeStructure) {\n const fees = feeStructure || DEFAULT_FEE_STRUCTURE;\n const subtotal = ticketPrice * quantity;\n const subtotalCents = Math.round(subtotal * 100);\n const platformFeePerTicket = calculatePlatformFee(ticketPrice, feeStructure);\n const totalPlatformFee = platformFeePerTicket * quantity;\n const organizerNetPerTicket = calculateOrganizerNet(ticketPrice, feeStructure);\n const totalOrganizerNet = organizerNetPerTicket * quantity;\n const displayPricePerTicket = calculateDisplayPrice(ticketPrice, feeStructure);\n const totalDisplayPrice = displayPricePerTicket * quantity;\n const customerTotalPerTicket = calculateCustomerTotal(ticketPrice, feeStructure);\n const totalCustomerPays = customerTotalPerTicket * quantity;\n \n return {\n // Base amounts\n subtotal: subtotalCents,\n platformFeePerTicket,\n totalPlatformFee,\n organizerNetPerTicket,\n totalOrganizerNet,\n \n // Display and customer totals\n displayPricePerTicket,\n totalDisplayPrice,\n customerTotalPerTicket,\n totalCustomerPays,\n \n // Fee model info\n feeModel: fees.fee_model,\n feeAbsorbed: fees.absorb_fee_in_price,\n \n // Formatted strings\n subtotalFormatted: `$${(subtotalCents / 100).toFixed(2)}`,\n platformFeePerTicketFormatted: `$${(platformFeePerTicket / 100).toFixed(2)}`,\n totalPlatformFeeFormatted: `$${(totalPlatformFee / 100).toFixed(2)}`,\n organizerNetPerTicketFormatted: `$${(organizerNetPerTicket / 100).toFixed(2)}`,\n totalOrganizerNetFormatted: `$${(totalOrganizerNet / 100).toFixed(2)}`,\n displayPricePerTicketFormatted: `$${(displayPricePerTicket / 100).toFixed(2)}`,\n totalDisplayPriceFormatted: `$${(totalDisplayPrice / 100).toFixed(2)}`,\n customerTotalPerTicketFormatted: `$${(customerTotalPerTicket / 100).toFixed(2)}`,\n totalCustomerPaysFormatted: `$${(totalCustomerPays / 100).toFixed(2)}`,\n };\n}\n\n// Generate Stripe Connect onboarding URL\nexport function generateConnectOnboardingUrl(organizationId: string): string {\n if (!STRIPE_CONFIG.CONNECT_CLIENT_ID) {\n throw new Error('Stripe Connect not configured');\n }\n \n const params = new URLSearchParams({\n client_id: STRIPE_CONFIG.CONNECT_CLIENT_ID,\n state: organizationId,\n scope: 'read_write',\n response_type: 'code',\n 'stripe_user[email]': '', // Will be filled by the form\n 'stripe_user[url]': 'https://portal.blackcanyontickets.com',\n 'stripe_user[country]': 'US',\n 'stripe_user[business_type]': 'individual', // or 'company'\n });\n \n return `https://connect.stripe.com/oauth/authorize?${params.toString()}`;\n}\n\n// Types for Stripe Connect\nexport interface StripeConnectAccount {\n id: string;\n email: string;\n details_submitted: boolean;\n charges_enabled: boolean;\n payouts_enabled: boolean;\n}","usedDeprecatedRules":[]},{"filePath":"/home/tyler/apps/bct-whitelabel/src/lib/supabase-admin.ts","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/home/tyler/apps/bct-whitelabel/src/lib/supabase-ssr.ts","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/home/tyler/apps/bct-whitelabel/src/lib/supabase.ts","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/home/tyler/apps/bct-whitelabel/src/lib/super-admin-auth.ts","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/home/tyler/apps/bct-whitelabel/src/lib/super-admin-types.ts","messages":[{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":130,"column":44,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":130,"endColumn":47,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[2598,2601],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[2598,2601],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":138,"column":18,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":138,"endColumn":21,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[2725,2728],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[2725,2728],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":2,"fixableErrorCount":0,"fixableWarningCount":0,"source":"// Super Admin Dashboard Types\n\nexport interface PlatformMetrics {\n totalRevenue: number;\n totalFees: number;\n activeOrganizers: number;\n totalTickets: number;\n revenueChange: number;\n feesChange: number;\n organizersChange: number;\n ticketsChange: number;\n}\n\nexport interface RevenueBreakdown {\n totals: {\n grossRevenue: number;\n platformFees: number;\n netRevenue: number;\n };\n}\n\nexport interface SalesTrends {\n trends: Array<{\n period: string;\n revenue: number;\n transactions: number;\n fees: number;\n }>;\n summary: {\n averageMonthlyRevenue: number;\n averageMonthlyFees: number;\n growth: number;\n totalPeriods: number;\n };\n}\n\nexport interface EventAnalytics {\n events: Array<{\n id: string;\n title: string;\n organizationId: string;\n organizerName?: string;\n startTime: string;\n isPublished: boolean;\n category?: string;\n ticketsSold: number;\n totalRevenue: number;\n sellThroughRate: number;\n avgTicketPrice: number;\n }>;\n summary: {\n totalEvents: number;\n totalTicketsSold: number;\n avgTicketPrice: number;\n avgSellThroughRate: number;\n };\n}\n\nexport interface OrganizerPerformance {\n organizers: Array<{\n id: string;\n name: string;\n email?: string;\n eventCount: number;\n publishedEvents: number;\n ticketsSold: number;\n totalRevenue: number;\n platformFees: number;\n avgTicketPrice: number;\n joinDate: string;\n }>;\n}\n\nexport interface TicketAnalytics {\n tickets: Array<{\n id: string;\n customerName?: string;\n customerEmail?: string;\n event: {\n title: string;\n organizationName: string;\n };\n ticketType: {\n name: string;\n };\n price: number;\n seatRow?: string;\n seatNumber?: string;\n status: 'active' | 'used' | 'refunded' | 'cancelled';\n createdAt: string;\n usedAt?: string;\n refundedAt?: string;\n }>;\n stats: {\n total: number;\n active: number;\n used: number;\n refunded: number;\n };\n pagination?: {\n page: number;\n total: number;\n pages: number;\n };\n}\n\nexport interface RecentActivity {\n activities: Array<{\n type: 'ticket_sale' | 'event_created' | 'organizer_joined';\n date: string;\n amount?: number;\n event_name?: string;\n organization_name?: string;\n name?: string;\n description?: string;\n details?: string;\n }>;\n}\n\nexport interface TerritoryManagerStats {\n activeManagers: number;\n pendingApplications: number;\n totalCommissions: number;\n}\n\nexport interface FeatureToggles {\n territoryManagerEnabled: boolean;\n}\n\nexport interface SuperAdminApiResponse<T = any> {\n success: boolean;\n data: T;\n error?: string;\n}\n\n// Export data types\nexport interface ExportData {\n [key: string]: any;\n}\n\nexport type ExportType = 'revenue' | 'organizers' | 'events' | 'tickets';\n\nexport interface FilterOptions {\n startDate?: string;\n endDate?: string;\n organizerId?: string;\n eventId?: string;\n status?: string;\n limit?: number;\n page?: number;\n}","usedDeprecatedRules":[]},{"filePath":"/home/tyler/apps/bct-whitelabel/src/lib/super-admin-utils.ts","messages":[{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":15,"column":54,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":15,"endColumn":57,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[336,339],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[336,339],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":22,"column":12,"nodeType":null,"messageId":"unusedVar","endLine":22,"endColumn":17},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":49,"column":12,"nodeType":null,"messageId":"unusedVar","endLine":49,"endColumn":17},{"ruleId":"no-undef","severity":2,"message":"'URL' is not defined.","line":154,"column":15,"nodeType":"Identifier","messageId":"undef","endLine":154,"endColumn":18},{"ruleId":"no-undef","severity":2,"message":"'URL' is not defined.","line":165,"column":3,"nodeType":"Identifier","messageId":"undef","endLine":165,"endColumn":6},{"ruleId":"no-case-declarations","severity":2,"message":"Unexpected lexical declaration in case block.","line":184,"column":9,"nodeType":"VariableDeclaration","messageId":"unexpected","endLine":184,"endColumn":86,"suggestions":[{"messageId":"addBrackets","fix":{"range":[4680,5369],"text":"{ const revenueResult = await loadSuperAdminData('revenue_breakdown', options);\n if (revenueResult.success) {\n const totals = revenueResult.data.totals || {};\n const processingFees = (totals.grossRevenue || 0) * 0.029;\n data = [{\n gross_revenue: totals.grossRevenue || 0,\n platform_fees: totals.platformFees || 0,\n processing_fees: processingFees,\n net_to_organizers: (totals.grossRevenue || 0) - (totals.platformFees || 0) - processingFees,\n export_date: new Date().toISOString()\n }];\n }\n filename = `revenue-report-${new Date().toISOString().split('T')[0]}.csv`;\n break; }"},"desc":"Add {} brackets around the case block."}]},{"ruleId":"no-case-declarations","severity":2,"message":"Unexpected lexical declaration in case block.","line":200,"column":9,"nodeType":"VariableDeclaration","messageId":"unexpected","endLine":200,"endColumn":92,"suggestions":[{"messageId":"addBrackets","fix":{"range":[5412,6137],"text":"{ const organizerResult = await loadSuperAdminData('organizer_performance', options);\n if (organizerResult.success) {\n data = (organizerResult.data.organizers || []).map((org: any) => ({\n name: org.name,\n email: org.email || '',\n events: org.eventCount || 0,\n published_events: org.publishedEvents || 0,\n tickets_sold: org.ticketsSold || 0,\n total_revenue: org.totalRevenue || 0,\n platform_fees: org.platformFees || 0,\n avg_ticket_price: org.avgTicketPrice || 0,\n join_date: org.joinDate || ''\n }));\n }\n filename = `organizers-${new Date().toISOString().split('T')[0]}.csv`;\n break; }"},"desc":"Add {} brackets around the case block."}]},{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":202,"column":68,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":202,"endColumn":71,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[5602,5605],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[5602,5605],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"no-case-declarations","severity":2,"message":"Unexpected lexical declaration in case block.","line":218,"column":9,"nodeType":"VariableDeclaration","messageId":"unexpected","endLine":218,"endColumn":82,"suggestions":[{"messageId":"addBrackets","fix":{"range":[6176,6956],"text":"{ const eventResult = await loadSuperAdminData('event_analytics', options);\n if (eventResult.success) {\n data = (eventResult.data.events || []).map((event: any) => ({\n title: event.title,\n organization_id: event.organizationId,\n organizer_name: event.organizerName || '',\n start_time: event.startTime,\n is_published: event.isPublished,\n category: event.category || '',\n tickets_sold: event.ticketsSold || 0,\n total_revenue: event.totalRevenue || 0,\n sell_through_rate: event.sellThroughRate || 0,\n avg_ticket_price: event.avgTicketPrice || 0\n }));\n }\n filename = `events-${new Date().toISOString().split('T')[0]}.csv`;\n break; }"},"desc":"Add {} brackets around the case block."}]},{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":220,"column":62,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":220,"endColumn":65,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[6346,6349],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[6346,6349],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"no-case-declarations","severity":2,"message":"Unexpected lexical declaration in case block.","line":237,"column":9,"nodeType":"VariableDeclaration","messageId":"unexpected","endLine":237,"endColumn":107,"suggestions":[{"messageId":"addBrackets","fix":{"range":[6996,7983],"text":"{ const ticketResult = await loadSuperAdminData('ticket_analytics', { ...options, export: 'true' });\n if (ticketResult.success) {\n data = (ticketResult.data.tickets || []).map((ticket: any) => ({\n ticket_id: ticket.id,\n customer_name: ticket.customerName || '',\n customer_email: ticket.customerEmail || '',\n event_title: ticket.event?.title || '',\n organizer_name: ticket.event?.organizationName || '',\n ticket_type: ticket.ticketType?.name || 'General',\n price: ticket.price || 0,\n seat: ticket.seatRow && ticket.seatNumber ? `${ticket.seatRow}-${ticket.seatNumber}` : 'GA',\n status: ticket.status || 'unknown',\n purchase_date: ticket.createdAt || '',\n used_date: ticket.usedAt || '',\n refunded_date: ticket.refundedAt || ''\n }));\n }\n filename = `tickets-${new Date().toISOString().split('T')[0]}.csv`;\n break; }"},"desc":"Add {} brackets around the case block."}]},{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":239,"column":65,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":239,"endColumn":68,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[7195,7198],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[7195,7198],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":295,"column":46,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":295,"endColumn":49,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[9318,9321],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[9318,9321],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":295,"column":56,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":295,"endColumn":59,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[9328,9331],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[9328,9331],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"no-undef","severity":2,"message":"'NodeJS' is not defined.","line":299,"column":16,"nodeType":"Identifier","messageId":"undef","endLine":299,"endColumn":22}],"suppressedMessages":[],"errorCount":7,"fatalErrorCount":0,"warningCount":8,"fixableErrorCount":0,"fixableWarningCount":0,"source":"// Super Admin Dashboard Utilities\n\nimport { makeAuthenticatedRequest } from './api-client';\nimport type { \n PlatformMetrics, \n SuperAdminApiResponse, \n ExportData, \n ExportType,\n FilterOptions \n} from './super-admin-types';\n\n/**\n * Check if user has super admin privileges\n */\nexport async function checkSuperAdminAuth(): Promise<any | null> {\n try {\n const result = await makeAuthenticatedRequest('/api/admin/check-super-admin');\n if (result.success && result.data.isSuperAdmin) {\n return result.data;\n }\n return null;\n } catch (error) {\n\n return null;\n }\n}\n\n/**\n * Load platform-wide metrics\n */\nexport async function loadPlatformMetrics(): Promise<PlatformMetrics | null> {\n try {\n const result = await makeAuthenticatedRequest('/api/admin/super-analytics?metric=platform_overview');\n \n if (result.success) {\n const data = result.data.summary;\n return {\n totalRevenue: data.totalRevenue || 0,\n totalFees: data.totalPlatformFees || 0,\n activeOrganizers: data.activeOrganizers || 0,\n totalTickets: data.totalTickets || 0,\n revenueChange: data.revenueGrowth || 0,\n feesChange: data.feesGrowth || 0,\n organizersChange: data.organizersThisMonth || 0,\n ticketsChange: data.ticketsThisMonth || 0\n };\n }\n return null;\n } catch (error) {\n\n return null;\n }\n}\n\n/**\n * Generic function to load super admin analytics\n */\nexport async function loadSuperAdminData<T>(\n metric: string, \n options: FilterOptions = {}\n): Promise<SuperAdminApiResponse<T>> {\n try {\n const params = new URLSearchParams({ metric });\n \n // Add filter options to params\n Object.entries(options).forEach(([key, value]) => {\n if (value !== undefined && value !== null && value !== '') {\n params.append(key, value.toString());\n }\n });\n \n const result = await makeAuthenticatedRequest(`/api/admin/super-analytics?${params}`);\n return result;\n } catch (error) {\n\n return {\n success: false,\n data: {} as T,\n error: error instanceof Error ? error.message : 'Unknown error'\n };\n }\n}\n\n/**\n * Format currency values consistently\n */\nexport function formatCurrency(amount: number): string {\n return new Intl.NumberFormat('en-US', {\n style: 'currency',\n currency: 'USD',\n minimumFractionDigits: 0,\n maximumFractionDigits: 2\n }).format(amount);\n}\n\n/**\n * Format percentage values\n */\nexport function formatPercentage(value: number, decimals: number = 1): string {\n return `${value.toFixed(decimals)}%`;\n}\n\n/**\n * Format large numbers with appropriate suffixes\n */\nexport function formatNumber(num: number): string {\n if (num >= 1000000) {\n return (num / 1000000).toFixed(1) + 'M';\n }\n if (num >= 1000) {\n return (num / 1000).toFixed(1) + 'K';\n }\n return num.toString();\n}\n\n/**\n * Get relative time string (e.g., \"2 days ago\")\n */\nexport function getRelativeTime(date: string | Date): string {\n const now = new Date();\n const past = new Date(date);\n const diffInMs = now.getTime() - past.getTime();\n const diffInDays = Math.floor(diffInMs / (1000 * 60 * 60 * 24));\n \n if (diffInDays === 0) return 'Today';\n if (diffInDays === 1) return 'Yesterday';\n if (diffInDays < 7) return `${diffInDays} days ago`;\n if (diffInDays < 30) return `${Math.floor(diffInDays / 7)} weeks ago`;\n if (diffInDays < 365) return `${Math.floor(diffInDays / 30)} months ago`;\n return `${Math.floor(diffInDays / 365)} years ago`;\n}\n\n/**\n * Convert data to CSV format\n */\nexport function convertToCSV(data: ExportData[]): string {\n if (!data.length) return '';\n \n const headers = Object.keys(data[0]).join(',');\n const rows = data.map(row => \n Object.values(row).map(value => \n typeof value === 'string' ? `\"${value.replace(/\"/g, '\"\"')}\"` : value\n ).join(',')\n );\n \n return [headers, ...rows].join('\\n');\n}\n\n/**\n * Download CSV file\n */\nexport function downloadCSV(csvContent: string, filename: string): void {\n const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' });\n const url = URL.createObjectURL(blob);\n const link = document.createElement('a');\n \n link.href = url;\n link.download = filename;\n link.style.display = 'none';\n \n document.body.appendChild(link);\n link.click();\n document.body.removeChild(link);\n \n URL.revokeObjectURL(url);\n}\n\n/**\n * Export data with loading state management\n */\nexport async function exportData(\n type: ExportType,\n setLoading: (loading: boolean) => void,\n options: FilterOptions = {}\n): Promise<void> {\n setLoading(true);\n \n try {\n let data: ExportData[] = [];\n let filename = '';\n \n switch (type) {\n case 'revenue':\n const revenueResult = await loadSuperAdminData('revenue_breakdown', options);\n if (revenueResult.success) {\n const totals = revenueResult.data.totals || {};\n const processingFees = (totals.grossRevenue || 0) * 0.029;\n data = [{\n gross_revenue: totals.grossRevenue || 0,\n platform_fees: totals.platformFees || 0,\n processing_fees: processingFees,\n net_to_organizers: (totals.grossRevenue || 0) - (totals.platformFees || 0) - processingFees,\n export_date: new Date().toISOString()\n }];\n }\n filename = `revenue-report-${new Date().toISOString().split('T')[0]}.csv`;\n break;\n \n case 'organizers':\n const organizerResult = await loadSuperAdminData('organizer_performance', options);\n if (organizerResult.success) {\n data = (organizerResult.data.organizers || []).map((org: any) => ({\n name: org.name,\n email: org.email || '',\n events: org.eventCount || 0,\n published_events: org.publishedEvents || 0,\n tickets_sold: org.ticketsSold || 0,\n total_revenue: org.totalRevenue || 0,\n platform_fees: org.platformFees || 0,\n avg_ticket_price: org.avgTicketPrice || 0,\n join_date: org.joinDate || ''\n }));\n }\n filename = `organizers-${new Date().toISOString().split('T')[0]}.csv`;\n break;\n \n case 'events':\n const eventResult = await loadSuperAdminData('event_analytics', options);\n if (eventResult.success) {\n data = (eventResult.data.events || []).map((event: any) => ({\n title: event.title,\n organization_id: event.organizationId,\n organizer_name: event.organizerName || '',\n start_time: event.startTime,\n is_published: event.isPublished,\n category: event.category || '',\n tickets_sold: event.ticketsSold || 0,\n total_revenue: event.totalRevenue || 0,\n sell_through_rate: event.sellThroughRate || 0,\n avg_ticket_price: event.avgTicketPrice || 0\n }));\n }\n filename = `events-${new Date().toISOString().split('T')[0]}.csv`;\n break;\n \n case 'tickets':\n const ticketResult = await loadSuperAdminData('ticket_analytics', { ...options, export: 'true' });\n if (ticketResult.success) {\n data = (ticketResult.data.tickets || []).map((ticket: any) => ({\n ticket_id: ticket.id,\n customer_name: ticket.customerName || '',\n customer_email: ticket.customerEmail || '',\n event_title: ticket.event?.title || '',\n organizer_name: ticket.event?.organizationName || '',\n ticket_type: ticket.ticketType?.name || 'General',\n price: ticket.price || 0,\n seat: ticket.seatRow && ticket.seatNumber ? `${ticket.seatRow}-${ticket.seatNumber}` : 'GA',\n status: ticket.status || 'unknown',\n purchase_date: ticket.createdAt || '',\n used_date: ticket.usedAt || '',\n refunded_date: ticket.refundedAt || ''\n }));\n }\n filename = `tickets-${new Date().toISOString().split('T')[0]}.csv`;\n break;\n }\n \n if (data.length > 0) {\n const csvContent = convertToCSV(data);\n downloadCSV(csvContent, filename);\n } else {\n throw new Error('No data available for export');\n }\n \n } catch (error) {\n\n alert(`Error exporting ${type} data: ${error instanceof Error ? error.message : 'Unknown error'}`);\n } finally {\n setLoading(false);\n }\n}\n\n/**\n * Get status color classes for UI elements\n */\nexport function getStatusColor(status: string): string {\n const statusColors: Record<string, string> = {\n 'active': 'bg-green-500/20 text-green-400 border-green-500/30',\n 'used': 'bg-blue-500/20 text-blue-400 border-blue-500/30',\n 'refunded': 'bg-red-500/20 text-red-400 border-red-500/30',\n 'cancelled': 'bg-gray-500/20 text-gray-400 border-gray-500/30',\n 'pending': 'bg-yellow-500/20 text-yellow-400 border-yellow-500/30',\n 'confirmed': 'bg-green-500/20 text-green-400 border-green-500/30',\n 'draft': 'bg-gray-500/20 text-gray-400 border-gray-500/30',\n 'published': 'bg-green-500/20 text-green-400 border-green-500/30',\n 'past': 'bg-gray-500/20 text-gray-400 border-gray-500/30'\n };\n \n return statusColors[status.toLowerCase()] || 'bg-gray-500/20 text-gray-400 border-gray-500/30';\n}\n\n/**\n * Debounce function for search inputs\n */\nexport function debounce<T extends (...args: any[]) => any>(\n func: T,\n wait: number\n): (...args: Parameters<T>) => void {\n let timeout: NodeJS.Timeout;\n \n return (...args: Parameters<T>) => {\n clearTimeout(timeout);\n timeout = setTimeout(() => func(...args), wait);\n };\n}","usedDeprecatedRules":[]},{"filePath":"/home/tyler/apps/bct-whitelabel/src/lib/territory-manager-api.ts","messages":[{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'eventId' is defined but never used. Allowed unused args must match /^_/u.","line":310,"column":29,"nodeType":null,"messageId":"unusedVar","endLine":310,"endColumn":36},{"ruleId":"no-undef","severity":2,"message":"'crypto' is not defined.","line":456,"column":11,"nodeType":"Identifier","messageId":"undef","endLine":456,"endColumn":17},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'territoryManagerId' is defined but never used. Allowed unused args must match /^_/u.","line":462,"column":26,"nodeType":null,"messageId":"unusedVar","endLine":462,"endColumn":44},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'period' is assigned a value but never used. Allowed unused args must match /^_/u.","line":469,"column":24,"nodeType":null,"messageId":"unusedVar","endLine":469,"endColumn":30}],"suppressedMessages":[],"errorCount":1,"fatalErrorCount":0,"warningCount":3,"fixableErrorCount":0,"fixableWarningCount":0,"source":"import { supabase } from './supabase';\nimport type {\n Territory,\n TerritoryManager,\n TerritoryApplication,\n Lead,\n Commission,\n TrainingProgress,\n Achievement,\n TerritoryManagerAchievement,\n ExpenseReport,\n TMNotification,\n ReferralLink,\n MarketingMaterial,\n ApplicationFormData,\n LeadFormData,\n ExpenseReportFormData,\n TerritoryManagerStats,\n LeaderboardEntry,\n TerritoryManagerAPI\n} from './territory-manager-types';\n\nclass TerritoryManagerAPIClient implements TerritoryManagerAPI {\n \n // Applications\n async submitApplication(data: ApplicationFormData): Promise<TerritoryApplication> {\n const applicationData = {\n email: data.personalInfo.email,\n full_name: data.personalInfo.fullName,\n phone: data.personalInfo.phone,\n address: data.personalInfo.address,\n desired_territory: data.preferences.desiredTerritory,\n has_transportation: data.preferences.hasTransportation,\n has_event_experience: data.preferences.hasEventExperience,\n motivation: data.preferences.motivation,\n consent: data.consent,\n documents: {\n // File uploads would be handled separately\n }\n };\n\n const { data: result, error } = await supabase\n .from('territory_applications')\n .insert(applicationData)\n .select()\n .single();\n\n if (error) throw error;\n return result;\n }\n\n async getApplications(status?: string): Promise<TerritoryApplication[]> {\n let query = supabase\n .from('territory_applications')\n .select('*')\n .order('created_at', { ascending: false });\n\n if (status) {\n query = query.eq('status', status);\n }\n\n const { data, error } = await query;\n if (error) throw error;\n return data || [];\n }\n\n async reviewApplication(id: string, action: 'approve' | 'reject', notes?: string): Promise<void> {\n const { data: user } = await supabase.auth.getUser();\n if (!user.user) throw new Error('User not authenticated');\n\n const updateData = {\n status: action === 'approve' ? 'approved' : 'rejected',\n reviewed_by: user.user.id,\n reviewed_at: new Date().toISOString(),\n review_notes: notes\n };\n\n const { error } = await supabase\n .from('territory_applications')\n .update(updateData)\n .eq('id', id);\n\n if (error) throw error;\n\n // If approved, create territory manager record\n if (action === 'approve') {\n const { data: application } = await supabase\n .from('territory_applications')\n .select('*')\n .eq('id', id)\n .single();\n\n if (application) {\n await this.createTerritoryManagerFromApplication(application);\n }\n }\n }\n\n private async createTerritoryManagerFromApplication(application: TerritoryApplication): Promise<void> {\n // First, create or get user\n const { data: existingUser } = await supabase\n .from('users')\n .select('id')\n .eq('email', application.email)\n .single();\n\n let userId = existingUser?.id;\n\n if (!userId) {\n // Create Supabase Auth user first\n const { data: authUser, error: authError } = await supabase.auth.admin.createUser({\n email: application.email,\n password: Math.random().toString(36).slice(-8) + Math.random().toString(36).slice(-8), // Generate temporary password\n email_confirm: true,\n user_metadata: {\n full_name: application.full_name,\n role: 'territory_manager'\n }\n });\n\n if (authError) throw authError;\n \n // Create user record in users table\n const { data: newUser, error: userError } = await supabase\n .from('users')\n .insert({\n id: authUser.user.id,\n email: application.email,\n name: application.full_name,\n role: 'territory_manager'\n })\n .select()\n .single();\n\n if (userError) throw userError;\n userId = newUser.id;\n }\n\n // Create territory manager record\n const { error } = await supabase\n .from('territory_managers')\n .insert({\n user_id: userId,\n status: 'active',\n approved_date: new Date().toISOString(),\n approved_by: application.reviewed_by,\n profile: {\n full_name: application.full_name,\n phone: application.phone,\n address: application.address,\n has_transportation: application.has_transportation,\n has_event_experience: application.has_event_experience,\n motivation: application.motivation\n },\n documents: application.documents\n });\n\n if (error) throw error;\n }\n\n // Territory Managers\n async getTerritoryManager(id: string): Promise<TerritoryManager> {\n const { data, error } = await supabase\n .from('territory_managers')\n .select('*')\n .eq('id', id)\n .single();\n\n if (error) throw error;\n return data;\n }\n\n async updateTerritoryManager(id: string, data: Partial<TerritoryManager>): Promise<TerritoryManager> {\n const { data: result, error } = await supabase\n .from('territory_managers')\n .update(data)\n .eq('id', id)\n .select()\n .single();\n\n if (error) throw error;\n return result;\n }\n\n async getTerritoryManagerStats(id: string): Promise<TerritoryManagerStats> {\n // Get commission data\n const { data: commissions } = await supabase\n .from('commissions')\n .select('*')\n .eq('territory_manager_id', id);\n\n const totalCommission = commissions?.reduce((sum, c) => sum + c.total_commission, 0) || 0;\n \n const currentMonth = new Date().toISOString().slice(0, 7); // YYYY-MM format\n const currentMonthCommission = commissions?.filter(c => \n c.created_at.startsWith(currentMonth)\n ).reduce((sum, c) => sum + c.total_commission, 0) || 0;\n\n // Get events referred\n const { data: events } = await supabase\n .from('events')\n .select('id')\n .eq('created_by', id); // Assuming events have a created_by field for referrals\n\n const totalEventsReferred = events?.length || 0;\n\n // Calculate success rate (events that actually happened vs referred)\n const successRate = totalEventsReferred > 0 ? \n (commissions?.filter(c => c.status === 'paid').length || 0) / totalEventsReferred : 0;\n\n // Get achievements\n const { data: achievements } = await supabase\n .from('territory_manager_achievements')\n .select('*')\n .eq('territory_manager_id', id);\n\n const achievementsEarned = achievements?.length || 0;\n\n // Get pending payouts\n const pendingPayouts = commissions?.filter(c => c.status === 'unpaid').length || 0;\n\n return {\n total_commission: totalCommission,\n current_month_commission: currentMonthCommission,\n total_events_referred: totalEventsReferred,\n success_rate: successRate,\n rank: 1, // TODO: Calculate actual rank\n achievements_earned: achievementsEarned,\n pending_payouts: pendingPayouts\n };\n }\n\n // Leads\n async createLead(data: LeadFormData): Promise<Lead> {\n const { data: user } = await supabase.auth.getUser();\n if (!user.user) throw new Error('User not authenticated');\n\n // Get territory manager ID for current user\n const { data: tm } = await supabase\n .from('territory_managers')\n .select('id')\n .eq('user_id', user.user.id)\n .single();\n\n if (!tm) throw new Error('Territory manager not found');\n\n const leadData = {\n territory_manager_id: tm.id,\n event_name: data.eventName,\n organizer_contact: data.organizerContact,\n event_details: data.eventDetails,\n notes: data.notes ? [data.notes] : [],\n follow_up_date: data.followUpDate\n };\n\n const { data: result, error } = await supabase\n .from('leads')\n .insert(leadData)\n .select()\n .single();\n\n if (error) throw error;\n return result;\n }\n\n async getLeads(territoryManagerId: string): Promise<Lead[]> {\n const { data, error } = await supabase\n .from('leads')\n .select('*')\n .eq('territory_manager_id', territoryManagerId)\n .order('created_at', { ascending: false });\n\n if (error) throw error;\n return data || [];\n }\n\n async updateLead(id: string, data: Partial<Lead>): Promise<Lead> {\n const { data: result, error } = await supabase\n .from('leads')\n .update(data)\n .eq('id', id)\n .select()\n .single();\n\n if (error) throw error;\n return result;\n }\n\n async deleteLead(id: string): Promise<void> {\n const { error } = await supabase\n .from('leads')\n .delete()\n .eq('id', id);\n\n if (error) throw error;\n }\n\n // Commissions\n async getCommissions(territoryManagerId: string): Promise<Commission[]> {\n const { data, error } = await supabase\n .from('commissions')\n .select('*')\n .eq('territory_manager_id', territoryManagerId)\n .order('created_at', { ascending: false });\n\n if (error) throw error;\n return data || [];\n }\n\n async calculateCommission(eventId: string): Promise<Commission> {\n // This would be called when an event is converted from a lead\n // Implementation depends on how event sales are tracked\n throw new Error('Not implemented');\n }\n\n // Training\n async getTrainingProgress(territoryManagerId: string): Promise<TrainingProgress[]> {\n const { data, error } = await supabase\n .from('training_progress')\n .select('*')\n .eq('territory_manager_id', territoryManagerId)\n .order('created_at', { ascending: false });\n\n if (error) throw error;\n return data || [];\n }\n\n async updateTrainingProgress(territoryManagerId: string, moduleId: string, score?: number): Promise<void> {\n const { error } = await supabase\n .from('training_progress')\n .upsert({\n territory_manager_id: territoryManagerId,\n module_id: moduleId,\n completed_at: new Date().toISOString(),\n score: score\n });\n\n if (error) throw error;\n }\n\n // Achievements\n async getAchievements(): Promise<Achievement[]> {\n const { data, error } = await supabase\n .from('achievements')\n .select('*')\n .eq('is_active', true)\n .order('created_at', { ascending: false });\n\n if (error) throw error;\n return data || [];\n }\n\n async getTerritoryManagerAchievements(territoryManagerId: string): Promise<TerritoryManagerAchievement[]> {\n const { data, error } = await supabase\n .from('territory_manager_achievements')\n .select('*')\n .eq('territory_manager_id', territoryManagerId)\n .order('earned_at', { ascending: false });\n\n if (error) throw error;\n return data || [];\n }\n\n // Expense Reports\n async submitExpenseReport(data: ExpenseReportFormData): Promise<ExpenseReport> {\n const { data: user } = await supabase.auth.getUser();\n if (!user.user) throw new Error('User not authenticated');\n\n const { data: tm } = await supabase\n .from('territory_managers')\n .select('id')\n .eq('user_id', user.user.id)\n .single();\n\n if (!tm) throw new Error('Territory manager not found');\n\n const expenseData = {\n territory_manager_id: tm.id,\n event_id: data.eventId,\n mileage: data.mileage,\n receipts: [], // File uploads would be handled separately\n total_amount: data.mileage * 0.65 // Standard IRS mileage rate\n };\n\n const { data: result, error } = await supabase\n .from('expense_reports')\n .insert(expenseData)\n .select()\n .single();\n\n if (error) throw error;\n return result;\n }\n\n async getExpenseReports(territoryManagerId: string): Promise<ExpenseReport[]> {\n const { data, error } = await supabase\n .from('expense_reports')\n .select('*')\n .eq('territory_manager_id', territoryManagerId)\n .order('created_at', { ascending: false });\n\n if (error) throw error;\n return data || [];\n }\n\n // Notifications\n async getNotifications(territoryManagerId: string): Promise<TMNotification[]> {\n const { data, error } = await supabase\n .from('tm_notifications')\n .select('*')\n .eq('territory_manager_id', territoryManagerId)\n .order('created_at', { ascending: false });\n\n if (error) throw error;\n return data || [];\n }\n\n async markNotificationAsRead(id: string): Promise<void> {\n const { error } = await supabase\n .from('tm_notifications')\n .update({ read: true })\n .eq('id', id);\n\n if (error) throw error;\n }\n\n // Referrals\n async generateReferralLink(territoryManagerId: string, eventType?: string): Promise<ReferralLink> {\n const { data: tm } = await supabase\n .from('territory_managers')\n .select('referral_code')\n .eq('id', territoryManagerId)\n .single();\n\n if (!tm) throw new Error('Territory manager not found');\n\n const baseUrl = `${window.location.origin}/e/`;\n const referralUrl = `${baseUrl}?ref=${tm.referral_code}${eventType ? `&type=${eventType}` : ''}`;\n \n // Generate QR code (would use QR code library)\n const qrCodeUrl = `${window.location.origin}/api/qr-code?data=${encodeURIComponent(referralUrl)}`;\n\n const linkData = {\n territory_manager_id: territoryManagerId,\n url: referralUrl,\n qr_code_url: qrCodeUrl,\n event_type: eventType,\n campaign_name: `${eventType || 'general'}-${Date.now()}`,\n clicks: 0,\n conversions: 0\n };\n\n // This would be stored in a referral_links table\n // For now, return the generated link\n return {\n id: crypto.randomUUID(),\n ...linkData,\n created_at: new Date().toISOString()\n };\n }\n\n async getReferralLinks(territoryManagerId: string): Promise<ReferralLink[]> {\n // Would query referral_links table\n // For now, return empty array\n return [];\n }\n\n // Leaderboard\n async getLeaderboard(period: 'monthly' | 'quarterly' | 'yearly' = 'monthly'): Promise<LeaderboardEntry[]> {\n // This would be a complex query joining multiple tables\n // For now, return empty array\n return [];\n }\n\n // Marketing Materials\n async getMarketingMaterials(): Promise<MarketingMaterial[]> {\n // This would query a marketing_materials table\n // For now, return empty array\n return [];\n }\n\n // Territories\n async getTerritories(): Promise<Territory[]> {\n const { data, error } = await supabase\n .from('territories')\n .select('*')\n .order('name');\n\n if (error) throw error;\n return data || [];\n }\n\n async assignTerritory(territoryId: string, territoryManagerId: string): Promise<void> {\n const { error } = await supabase\n .from('territory_managers')\n .update({ territory_id: territoryId })\n .eq('id', territoryManagerId);\n\n if (error) throw error;\n\n // Also update the territory\n const { error: territoryError } = await supabase\n .from('territories')\n .update({ assigned_to: territoryManagerId })\n .eq('id', territoryId);\n\n if (territoryError) throw territoryError;\n }\n}\n\nexport const territoryManagerAPI = new TerritoryManagerAPIClient();","usedDeprecatedRules":[]},{"filePath":"/home/tyler/apps/bct-whitelabel/src/lib/territory-manager-auth.ts","messages":[{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":19,"column":14,"nodeType":null,"messageId":"unusedVar","endLine":19,"endColumn":19},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":38,"column":14,"nodeType":null,"messageId":"unusedVar","endLine":38,"endColumn":19},{"ruleId":"no-useless-catch","severity":2,"message":"Unnecessary try/catch wrapper.","line":46,"column":5,"nodeType":"TryStatement","messageId":"unnecessaryCatch","endLine":65,"endColumn":6},{"ruleId":"no-useless-catch","severity":2,"message":"Unnecessary try/catch wrapper.","line":70,"column":5,"nodeType":"TryStatement","messageId":"unnecessaryCatch","endLine":76,"endColumn":6},{"ruleId":"no-useless-catch","severity":2,"message":"Unnecessary try/catch wrapper.","line":81,"column":5,"nodeType":"TryStatement","messageId":"unnecessaryCatch","endLine":90,"endColumn":6},{"ruleId":"no-useless-catch","severity":2,"message":"Unnecessary try/catch wrapper.","line":95,"column":5,"nodeType":"TryStatement","messageId":"unnecessaryCatch","endLine":104,"endColumn":6},{"ruleId":"no-useless-catch","severity":2,"message":"Unnecessary try/catch wrapper.","line":109,"column":5,"nodeType":"TryStatement","messageId":"unnecessaryCatch","endLine":132,"endColumn":6},{"ruleId":"no-useless-catch","severity":2,"message":"Unnecessary try/catch wrapper.","line":137,"column":5,"nodeType":"TryStatement","messageId":"unnecessaryCatch","endLine":175,"endColumn":6},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'email' is defined but never used. Allowed unused args must match /^_/u.","line":179,"column":44,"nodeType":null,"messageId":"unusedVar","endLine":179,"endColumn":49},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'tempPassword' is defined but never used. Allowed unused args must match /^_/u.","line":179,"column":59,"nodeType":null,"messageId":"unusedVar","endLine":179,"endColumn":71},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":196,"column":14,"nodeType":null,"messageId":"unusedVar","endLine":196,"endColumn":19},{"ruleId":"no-useless-catch","severity":2,"message":"Unnecessary try/catch wrapper.","line":204,"column":5,"nodeType":"TryStatement","messageId":"unnecessaryCatch","endLine":215,"endColumn":6},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":241,"column":14,"nodeType":null,"messageId":"unusedVar","endLine":241,"endColumn":19},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":252,"column":14,"nodeType":null,"messageId":"unusedVar","endLine":252,"endColumn":19},{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":259,"column":63,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":259,"endColumn":66,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[6559,6562],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[6559,6562],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]}],"suppressedMessages":[],"errorCount":7,"fatalErrorCount":0,"warningCount":8,"fixableErrorCount":0,"fixableWarningCount":0,"source":"import { supabase } from './supabase';\nimport type { TerritoryManager, TerritoryManagerProfile } from './territory-manager-types';\n\nexport class TerritoryManagerAuth {\n \n // Check if current user is a territory manager\n static async isTerritoryManager(): Promise<boolean> {\n try {\n const { data: { user } } = await supabase.auth.getUser();\n if (!user) return false;\n\n const { data: territoryManager } = await supabase\n .from('territory_managers')\n .select('id')\n .eq('user_id', user.id)\n .single();\n\n return !!territoryManager;\n } catch (error) {\n\n return false;\n }\n }\n\n // Get current territory manager data\n static async getCurrentTerritoryManager(): Promise<TerritoryManager | null> {\n try {\n const { data: { user } } = await supabase.auth.getUser();\n if (!user) return null;\n\n const { data: territoryManager } = await supabase\n .from('territory_managers')\n .select('*')\n .eq('user_id', user.id)\n .single();\n\n return territoryManager;\n } catch (error) {\n\n return null;\n }\n }\n\n // Sign in territory manager\n static async signIn(email: string, password: string) {\n try {\n const { data, error } = await supabase.auth.signInWithPassword({\n email,\n password\n });\n\n if (error) throw error;\n\n // Check if user is a territory manager\n const isTM = await this.isTerritoryManager();\n if (!isTM) {\n await supabase.auth.signOut();\n throw new Error('Access denied: Not a territory manager');\n }\n\n return data;\n } catch (error) {\n\n throw error;\n }\n }\n\n // Sign out\n static async signOut() {\n try {\n const { error } = await supabase.auth.signOut();\n if (error) throw error;\n } catch (error) {\n\n throw error;\n }\n }\n\n // Request password reset\n static async requestPasswordReset(email: string) {\n try {\n const { error } = await supabase.auth.resetPasswordForEmail(email, {\n redirectTo: `${window.location.origin}/territory-manager/reset-password`\n });\n \n if (error) throw error;\n } catch (error) {\n\n throw error;\n }\n }\n\n // Update password\n static async updatePassword(password: string) {\n try {\n const { error } = await supabase.auth.updateUser({\n password\n });\n \n if (error) throw error;\n } catch (error) {\n\n throw error;\n }\n }\n\n // Update profile\n static async updateProfile(profile: Partial<TerritoryManagerProfile>) {\n try {\n const { data: { user } } = await supabase.auth.getUser();\n if (!user) throw new Error('User not authenticated');\n\n // Update user metadata\n const { error: authError } = await supabase.auth.updateUser({\n data: {\n full_name: profile.full_name\n }\n });\n\n if (authError) throw authError;\n\n // Update territory manager profile\n const { error: profileError } = await supabase\n .from('territory_managers')\n .update({ profile })\n .eq('user_id', user.id);\n\n if (profileError) throw profileError;\n } catch (error) {\n\n throw error;\n }\n }\n\n // Create invitation for new territory manager\n static async createInvitation(email: string, territoryId?: string) {\n try {\n // Generate temporary password\n const tempPassword = Math.random().toString(36).slice(-8) + Math.random().toString(36).slice(-8);\n \n // Create Supabase Auth user\n const { data: authUser, error: authError } = await supabase.auth.admin.createUser({\n email,\n password: tempPassword,\n email_confirm: true,\n user_metadata: {\n role: 'territory_manager',\n must_change_password: true\n }\n });\n\n if (authError) throw authError;\n\n // Create territory manager record\n const { error: tmError } = await supabase\n .from('territory_managers')\n .insert({\n user_id: authUser.user.id,\n territory_id: territoryId,\n status: 'pending',\n profile: {\n full_name: email.split('@')[0] // Default name from email\n }\n });\n\n if (tmError) throw tmError;\n\n // Send invitation email with temporary password\n await this.sendInvitationEmail(email, tempPassword);\n\n return { success: true, userId: authUser.user.id };\n } catch (error) {\n\n throw error;\n }\n }\n\n // Send invitation email\n private static async sendInvitationEmail(email: string, tempPassword: string) {\n // TODO: Implement email sending logic\n\n // Email would include:\n // - Welcome message\n // - Temporary password\n // - Link to change password\n // - Instructions for getting started\n }\n\n // Check if user needs to change password\n static async mustChangePassword(): Promise<boolean> {\n try {\n const { data: { user } } = await supabase.auth.getUser();\n if (!user) return false;\n\n return user.user_metadata?.must_change_password === true;\n } catch (error) {\n\n return false;\n }\n }\n\n // Mark password as changed\n static async markPasswordChanged() {\n try {\n const { error } = await supabase.auth.updateUser({\n data: {\n must_change_password: false\n }\n });\n \n if (error) throw error;\n } catch (error) {\n\n throw error;\n }\n }\n\n // Get territory manager permissions\n static async getPermissions(): Promise<string[]> {\n try {\n const territoryManager = await this.getCurrentTerritoryManager();\n if (!territoryManager) return [];\n\n const basePermissions = [\n 'territory_manager.view_dashboard',\n 'territory_manager.manage_leads',\n 'territory_manager.view_commissions',\n 'territory_manager.submit_expenses',\n 'territory_manager.generate_referrals'\n ];\n\n // Add additional permissions based on status\n if (territoryManager.status === 'active') {\n basePermissions.push(\n 'territory_manager.access_training',\n 'territory_manager.view_marketing_materials'\n );\n }\n\n return basePermissions;\n } catch (error) {\n\n return [];\n }\n }\n\n // Check specific permission\n static async hasPermission(permission: string): Promise<boolean> {\n try {\n const permissions = await this.getPermissions();\n return permissions.includes(permission);\n } catch (error) {\n\n return false;\n }\n }\n\n // Listen for auth state changes\n static onAuthStateChange(callback: (event: string, session: any) => void) {\n return supabase.auth.onAuthStateChange(async (event, session) => {\n if (event === 'SIGNED_IN' && session) {\n // Check if user is a territory manager\n const isTM = await this.isTerritoryManager();\n if (!isTM) {\n await supabase.auth.signOut();\n return callback('SIGNED_OUT', null);\n }\n }\n callback(event, session);\n });\n }\n}\n\n// Export convenience functions\nexport const {\n isTerritoryManager,\n getCurrentTerritoryManager,\n signIn,\n signOut,\n requestPasswordReset,\n updatePassword,\n updateProfile,\n mustChangePassword,\n markPasswordChanged,\n getPermissions,\n hasPermission,\n onAuthStateChange\n} = TerritoryManagerAuth;","usedDeprecatedRules":[]},{"filePath":"/home/tyler/apps/bct-whitelabel/src/lib/territory-manager-router.ts","messages":[{"ruleId":"no-useless-catch","severity":2,"message":"Unnecessary try/catch wrapper.","line":31,"column":5,"nodeType":"TryStatement","messageId":"unnecessaryCatch","endLine":36,"endColumn":6},{"ruleId":"no-useless-catch","severity":2,"message":"Unnecessary try/catch wrapper.","line":40,"column":5,"nodeType":"TryStatement","messageId":"unnecessaryCatch","endLine":45,"endColumn":6},{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":50,"column":11,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":50,"endColumn":14,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[1130,1133],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[1130,1133],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":62,"column":14,"nodeType":null,"messageId":"unusedVar","endLine":62,"endColumn":19},{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":78,"column":22,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":78,"endColumn":25,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[1861,1864],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[1861,1864],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":82,"column":23,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":82,"endColumn":26,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[1986,1989],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[1986,1989],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":142,"column":14,"nodeType":null,"messageId":"unusedVar","endLine":142,"endColumn":19},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":183,"column":14,"nodeType":null,"messageId":"unusedVar","endLine":183,"endColumn":19},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":214,"column":14,"nodeType":null,"messageId":"unusedVar","endLine":214,"endColumn":19},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":285,"column":14,"nodeType":null,"messageId":"unusedVar","endLine":285,"endColumn":19},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":320,"column":14,"nodeType":null,"messageId":"unusedVar","endLine":320,"endColumn":19},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":363,"column":14,"nodeType":null,"messageId":"unusedVar","endLine":363,"endColumn":19},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":375,"column":14,"nodeType":null,"messageId":"unusedVar","endLine":375,"endColumn":19},{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":381,"column":48,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":381,"endColumn":51,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[9556,9559],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[9556,9559],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":387,"column":14,"nodeType":null,"messageId":"unusedVar","endLine":387,"endColumn":19},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":426,"column":14,"nodeType":null,"messageId":"unusedVar","endLine":426,"endColumn":19},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":476,"column":14,"nodeType":null,"messageId":"unusedVar","endLine":476,"endColumn":19},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":488,"column":14,"nodeType":null,"messageId":"unusedVar","endLine":488,"endColumn":19},{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":542,"column":70,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":542,"endColumn":73,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[13687,13690],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[13687,13690],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]}],"suppressedMessages":[],"errorCount":2,"fatalErrorCount":0,"warningCount":17,"fixableErrorCount":0,"fixableWarningCount":0,"source":"import { territoryManagerAPI } from './territory-manager-api';\nimport { TerritoryManagerAuth } from './territory-manager-auth';\nimport type {\n TerritoryManager,\n TerritoryManagerStats,\n TerritoryApplication,\n Lead,\n Commission,\n Achievement,\n ExpenseReport,\n TMNotification,\n ReferralLink,\n MarketingMaterial,\n LeaderboardEntry,\n ApplicationFormData,\n LeadFormData,\n ExpenseReportFormData\n} from './territory-manager-types';\n\n/**\n * Centralized Territory Manager API router\n * Browser-friendly API router that handles all Territory Manager API calls\n * This is designed to be used in Astro client-side scripts and React components\n */\nexport class TerritoryManagerRouter {\n\n /**\n * Authentication methods\n */\n static async signIn(email: string, password: string) {\n try {\n return await TerritoryManagerAuth.signIn(email, password);\n } catch (error) {\n\n throw error;\n }\n }\n\n static async signOut() {\n try {\n return await TerritoryManagerAuth.signOut();\n } catch (error) {\n\n throw error;\n }\n }\n\n static async checkAuth(): Promise<{\n authenticated: boolean;\n user: any;\n territoryManager: TerritoryManager | null;\n }> {\n try {\n const isTM = await TerritoryManagerAuth.isTerritoryManager();\n const territoryManager = isTM ? await TerritoryManagerAuth.getCurrentTerritoryManager() : null;\n \n return {\n authenticated: isTM,\n user: territoryManager ? { email: territoryManager.user_id } : null,\n territoryManager\n };\n } catch (error) {\n\n return {\n authenticated: false,\n user: null,\n territoryManager: null\n };\n }\n }\n\n /**\n * Dashboard data loading\n */\n static async loadDashboard(): Promise<{\n stats: TerritoryManagerStats | null;\n territoryManager: TerritoryManager | null;\n recent_activity: any[];\n active_leads: Lead[];\n achievements: Achievement[];\n notifications: TMNotification[];\n earnings_history: any[];\n error: string | null;\n }> {\n try {\n const territoryManager = await TerritoryManagerAuth.getCurrentTerritoryManager();\n if (!territoryManager) {\n return {\n stats: null,\n territoryManager: null,\n recent_activity: [],\n active_leads: [],\n achievements: [],\n notifications: [],\n earnings_history: [],\n error: 'Not authenticated as territory manager'\n };\n }\n\n // Load all dashboard data in parallel\n const [\n stats,\n leads,\n achievements,\n notifications,\n commissions\n ] = await Promise.all([\n territoryManagerAPI.getTerritoryManagerStats(territoryManager.id),\n territoryManagerAPI.getLeads(territoryManager.id),\n territoryManagerAPI.getAchievements(),\n territoryManagerAPI.getNotifications(territoryManager.id),\n territoryManagerAPI.getCommissions(territoryManager.id)\n ]);\n\n // Generate recent activity from commissions and leads\n const recent_activity = [\n ...commissions.slice(0, 3).map(c => ({\n type: 'commission',\n message: `Earned $${c.total_commission} commission`,\n created_at: c.created_at\n })),\n ...leads.slice(0, 2).map(l => ({\n type: 'lead',\n message: `${l.status === 'converted' ? 'Converted' : 'Added'} lead: ${l.event_name}`,\n created_at: l.created_at\n }))\n ].sort((a, b) => new Date(b.created_at).getTime() - new Date(a.created_at).getTime());\n\n // Generate earnings history from commissions\n const earnings_history = this.generateEarningsHistory(commissions);\n\n return {\n stats,\n territoryManager,\n recent_activity,\n active_leads: leads.filter(l => l.status !== 'converted' && l.status !== 'dead'),\n achievements,\n notifications,\n earnings_history,\n error: null\n };\n } catch (error) {\n\n return {\n stats: null,\n territoryManager: null,\n recent_activity: [],\n active_leads: [],\n achievements: [],\n notifications: [],\n earnings_history: [],\n error: 'Failed to load dashboard data'\n };\n }\n }\n\n /**\n * Application management\n */\n static async submitApplication(data: ApplicationFormData): Promise<{\n success: boolean;\n applicationId?: string;\n error?: string;\n }> {\n try {\n const application = await territoryManagerAPI.submitApplication(data);\n return {\n success: true,\n applicationId: application.id\n };\n } catch (error) {\n\n return {\n success: false,\n error: error instanceof Error ? error.message : 'Failed to submit application'\n };\n }\n }\n\n static async getApplications(status?: string): Promise<TerritoryApplication[]> {\n try {\n return await territoryManagerAPI.getApplications(status);\n } catch (error) {\n\n return [];\n }\n }\n\n static async reviewApplication(id: string, action: 'approve' | 'reject', notes?: string): Promise<{\n success: boolean;\n error?: string;\n }> {\n try {\n await territoryManagerAPI.reviewApplication(id, action, notes);\n return { success: true };\n } catch (error) {\n\n return {\n success: false,\n error: error instanceof Error ? error.message : 'Failed to review application'\n };\n }\n }\n\n /**\n * Lead management\n */\n static async loadLeads(): Promise<Lead[]> {\n try {\n const territoryManager = await TerritoryManagerAuth.getCurrentTerritoryManager();\n if (!territoryManager) return [];\n\n return await territoryManagerAPI.getLeads(territoryManager.id);\n } catch (error) {\n\n return [];\n }\n }\n\n static async createLead(data: LeadFormData): Promise<{\n success: boolean;\n lead?: Lead;\n error?: string;\n }> {\n try {\n const lead = await territoryManagerAPI.createLead(data);\n return {\n success: true,\n lead\n };\n } catch (error) {\n\n return {\n success: false,\n error: error instanceof Error ? error.message : 'Failed to create lead'\n };\n }\n }\n\n static async updateLead(id: string, data: Partial<Lead>): Promise<{\n success: boolean;\n lead?: Lead;\n error?: string;\n }> {\n try {\n const lead = await territoryManagerAPI.updateLead(id, data);\n return {\n success: true,\n lead\n };\n } catch (error) {\n\n return {\n success: false,\n error: error instanceof Error ? error.message : 'Failed to update lead'\n };\n }\n }\n\n static async deleteLead(id: string): Promise<{\n success: boolean;\n error?: string;\n }> {\n try {\n await territoryManagerAPI.deleteLead(id);\n return { success: true };\n } catch (error) {\n\n return {\n success: false,\n error: error instanceof Error ? error.message : 'Failed to delete lead'\n };\n }\n }\n\n /**\n * Commission management\n */\n static async loadCommissions(): Promise<Commission[]> {\n try {\n const territoryManager = await TerritoryManagerAuth.getCurrentTerritoryManager();\n if (!territoryManager) return [];\n\n return await territoryManagerAPI.getCommissions(territoryManager.id);\n } catch (error) {\n\n return [];\n }\n }\n\n /**\n * Expense management\n */\n static async submitExpenseReport(data: ExpenseReportFormData): Promise<{\n success: boolean;\n expenseReport?: ExpenseReport;\n error?: string;\n }> {\n try {\n const expenseReport = await territoryManagerAPI.submitExpenseReport(data);\n return {\n success: true,\n expenseReport\n };\n } catch (error) {\n\n return {\n success: false,\n error: error instanceof Error ? error.message : 'Failed to submit expense report'\n };\n }\n }\n\n static async loadExpenseReports(): Promise<ExpenseReport[]> {\n try {\n const territoryManager = await TerritoryManagerAuth.getCurrentTerritoryManager();\n if (!territoryManager) return [];\n\n return await territoryManagerAPI.getExpenseReports(territoryManager.id);\n } catch (error) {\n\n return [];\n }\n }\n\n /**\n * Referral management\n */\n static async generateReferralLink(eventType?: string): Promise<{\n success: boolean;\n referralLink?: ReferralLink;\n error?: string;\n }> {\n try {\n const territoryManager = await TerritoryManagerAuth.getCurrentTerritoryManager();\n if (!territoryManager) {\n return {\n success: false,\n error: 'Not authenticated as territory manager'\n };\n }\n\n const referralLink = await territoryManagerAPI.generateReferralLink(territoryManager.id, eventType);\n return {\n success: true,\n referralLink\n };\n } catch (error) {\n\n return {\n success: false,\n error: error instanceof Error ? error.message : 'Failed to generate referral link'\n };\n }\n }\n\n static async loadReferralLinks(): Promise<ReferralLink[]> {\n try {\n const territoryManager = await TerritoryManagerAuth.getCurrentTerritoryManager();\n if (!territoryManager) return [];\n\n return await territoryManagerAPI.getReferralLinks(territoryManager.id);\n } catch (error) {\n\n return [];\n }\n }\n\n /**\n * Training and achievements\n */\n static async loadAchievements(): Promise<Achievement[]> {\n try {\n return await territoryManagerAPI.getAchievements();\n } catch (error) {\n\n return [];\n }\n }\n\n static async loadTrainingProgress(): Promise<any[]> {\n try {\n const territoryManager = await TerritoryManagerAuth.getCurrentTerritoryManager();\n if (!territoryManager) return [];\n\n return await territoryManagerAPI.getTrainingProgress(territoryManager.id);\n } catch (error) {\n\n return [];\n }\n }\n\n static async updateTrainingProgress(moduleId: string, score?: number): Promise<{\n success: boolean;\n error?: string;\n }> {\n try {\n const territoryManager = await TerritoryManagerAuth.getCurrentTerritoryManager();\n if (!territoryManager) {\n return {\n success: false,\n error: 'Not authenticated as territory manager'\n };\n }\n\n await territoryManagerAPI.updateTrainingProgress(territoryManager.id, moduleId, score);\n return { success: true };\n } catch (error) {\n\n return {\n success: false,\n error: error instanceof Error ? error.message : 'Failed to update training progress'\n };\n }\n }\n\n /**\n * Notifications\n */\n static async loadNotifications(): Promise<TMNotification[]> {\n try {\n const territoryManager = await TerritoryManagerAuth.getCurrentTerritoryManager();\n if (!territoryManager) return [];\n\n return await territoryManagerAPI.getNotifications(territoryManager.id);\n } catch (error) {\n\n return [];\n }\n }\n\n static async markNotificationAsRead(id: string): Promise<{\n success: boolean;\n error?: string;\n }> {\n try {\n await territoryManagerAPI.markNotificationAsRead(id);\n return { success: true };\n } catch (error) {\n\n return {\n success: false,\n error: error instanceof Error ? error.message : 'Failed to mark notification as read'\n };\n }\n }\n\n static async markAllNotificationsAsRead(): Promise<{\n success: boolean;\n error?: string;\n }> {\n try {\n const notifications = await this.loadNotifications();\n const unreadNotifications = notifications.filter(n => !n.read);\n \n await Promise.all(\n unreadNotifications.map(n => territoryManagerAPI.markNotificationAsRead(n.id))\n );\n \n return { success: true };\n } catch (error) {\n\n return {\n success: false,\n error: error instanceof Error ? error.message : 'Failed to mark all notifications as read'\n };\n }\n }\n\n /**\n * Leaderboard\n */\n static async loadLeaderboard(period: 'monthly' | 'quarterly' | 'yearly' = 'monthly'): Promise<LeaderboardEntry[]> {\n try {\n return await territoryManagerAPI.getLeaderboard(period);\n } catch (error) {\n\n return [];\n }\n }\n\n /**\n * Marketing materials\n */\n static async loadMarketingMaterials(): Promise<MarketingMaterial[]> {\n try {\n return await territoryManagerAPI.getMarketingMaterials();\n } catch (error) {\n\n return [];\n }\n }\n\n /**\n * Utility methods\n */\n static formatCurrency(amount: number): string {\n return new Intl.NumberFormat('en-US', {\n style: 'currency',\n currency: 'USD'\n }).format(amount);\n }\n\n static formatDate(dateString: string): string {\n return new Date(dateString).toLocaleDateString('en-US', {\n weekday: 'long',\n year: 'numeric',\n month: 'long',\n day: 'numeric'\n });\n }\n\n static formatDateTime(dateString: string): string {\n return new Date(dateString).toLocaleDateString('en-US', {\n weekday: 'long',\n year: 'numeric',\n month: 'long',\n day: 'numeric',\n hour: 'numeric',\n minute: '2-digit'\n });\n }\n\n static getStatusColor(status: string): string {\n const colors = {\n 'pending': 'bg-yellow-600 text-white',\n 'active': 'bg-green-600 text-white',\n 'suspended': 'bg-red-600 text-white',\n 'inactive': 'bg-gray-600 text-white',\n 'cold': 'bg-gray-600 text-white',\n 'contacted': 'bg-blue-600 text-white',\n 'confirmed': 'bg-green-600 text-white',\n 'converted': 'bg-purple-600 text-white',\n 'dead': 'bg-red-600 text-white'\n };\n return colors[status] || 'bg-gray-600 text-white';\n }\n\n /**\n * Private utility methods\n */\n private static generateEarningsHistory(commissions: Commission[]): any[] {\n const monthlyEarnings = new Map<string, number>();\n \n commissions.forEach(commission => {\n const month = commission.created_at.substring(0, 7); // YYYY-MM\n const current = monthlyEarnings.get(month) || 0;\n monthlyEarnings.set(month, current + commission.total_commission);\n });\n\n return Array.from(monthlyEarnings.entries())\n .map(([month, amount]) => ({\n date: month + '-01',\n amount\n }))\n .sort((a, b) => a.date.localeCompare(b.date));\n }\n}\n\n// Export the router for global use\nexport const territoryManagerRouter = TerritoryManagerRouter;","usedDeprecatedRules":[]},{"filePath":"/home/tyler/apps/bct-whitelabel/src/lib/territory-manager-types.ts","messages":[{"ruleId":"no-undef","severity":2,"message":"'GeoJSON' is not defined.","line":5,"column":14,"nodeType":"Identifier","messageId":"undef","endLine":5,"endColumn":21},{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":214,"column":10,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":214,"endColumn":13,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[4535,4538],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[4535,4538],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]}],"suppressedMessages":[],"errorCount":1,"fatalErrorCount":0,"warningCount":1,"fixableErrorCount":0,"fixableWarningCount":0,"source":"// Territory Manager System Types\nexport interface Territory {\n id: string;\n name: string;\n boundary?: GeoJSON.Polygon;\n population?: number;\n market_size?: string;\n assigned_to?: string;\n is_active: boolean;\n created_at: string;\n updated_at: string;\n}\n\nexport interface TerritoryManager {\n id: string;\n user_id: string;\n territory_id?: string;\n referral_code: string;\n status: 'pending' | 'active' | 'suspended' | 'inactive';\n application_date: string;\n approved_date?: string;\n approved_by?: string;\n profile?: TerritoryManagerProfile;\n documents?: Documents;\n earnings_data?: EarningsData;\n created_at: string;\n updated_at: string;\n}\n\nexport interface TerritoryManagerProfile {\n full_name: string;\n phone?: string;\n address?: Address;\n has_transportation: boolean;\n has_event_experience: boolean;\n motivation?: string;\n availability?: Availability;\n skills?: string[];\n bio?: string;\n}\n\nexport interface Address {\n street: string;\n city: string;\n state: string;\n zip_code: string;\n country: string;\n}\n\nexport interface Availability {\n days_of_week: string[];\n time_ranges: TimeRange[];\n travel_radius: number;\n}\n\nexport interface TimeRange {\n start: string;\n end: string;\n}\n\nexport interface Documents {\n id_upload?: FileUpload;\n w9_form?: FileUpload;\n background_check?: FileUpload;\n resume?: FileUpload;\n}\n\nexport interface FileUpload {\n filename: string;\n url: string;\n uploaded_at: string;\n size: number;\n type: string;\n}\n\nexport interface EarningsData {\n total_commission: number;\n current_month_commission: number;\n last_payout_date?: string;\n total_events_referred: number;\n success_rate: number;\n average_commission_per_event: number;\n}\n\nexport interface TerritoryApplication {\n id: string;\n email: string;\n full_name: string;\n phone?: string;\n address?: Address;\n desired_territory: string;\n has_transportation: boolean;\n has_event_experience: boolean;\n motivation?: string;\n documents?: Documents;\n consent?: ApplicationConsent;\n status: 'pending' | 'approved' | 'rejected';\n reviewed_by?: string;\n reviewed_at?: string;\n review_notes?: string;\n created_at: string;\n updated_at: string;\n}\n\nexport interface ApplicationConsent {\n background_check: boolean;\n data_processing: boolean;\n terms_of_service: boolean;\n privacy_policy: boolean;\n}\n\nexport interface Lead {\n id: string;\n territory_manager_id: string;\n event_name: string;\n organizer_contact: ContactInfo;\n event_details: EventDetails;\n status: 'cold' | 'contacted' | 'confirmed' | 'converted' | 'dead';\n notes: string[];\n follow_up_date?: string;\n created_at: string;\n updated_at: string;\n}\n\nexport interface ContactInfo {\n name: string;\n email: string;\n phone?: string;\n organization?: string;\n title?: string;\n}\n\nexport interface EventDetails {\n event_type: string;\n event_date?: string;\n venue?: string;\n expected_attendance?: number;\n budget_range?: string;\n description?: string;\n}\n\nexport interface Commission {\n id: string;\n territory_manager_id: string;\n event_id: string;\n tickets_sold: number;\n commission_per_ticket: number;\n total_commission: number;\n status: 'unpaid' | 'paid' | 'pending';\n payout_date?: string;\n stripe_transfer_id?: string;\n created_at: string;\n updated_at: string;\n}\n\nexport interface TrainingProgress {\n id: string;\n territory_manager_id: string;\n module_id: string;\n completed_at?: string;\n score?: number;\n created_at: string;\n}\n\nexport interface Achievement {\n id: string;\n name: string;\n description: string;\n requirements: AchievementRequirement;\n reward_amount: number;\n badge_icon: string;\n is_active: boolean;\n created_at: string;\n}\n\nexport interface AchievementRequirement {\n referrals?: number;\n monthly_referrals?: number;\n success_rate?: number;\n months?: number;\n training_modules?: string;\n recurring_events?: number;\n quarterly_commission?: number;\n}\n\nexport interface TerritoryManagerAchievement {\n id: string;\n territory_manager_id: string;\n achievement_id: string;\n earned_at: string;\n}\n\nexport interface ExpenseReport {\n id: string;\n territory_manager_id: string;\n event_id?: string;\n mileage: number;\n receipts?: FileUpload[];\n total_amount: number;\n status: 'pending' | 'approved' | 'paid' | 'rejected';\n submitted_date: string;\n approved_date?: string;\n approved_by?: string;\n created_at: string;\n updated_at: string;\n}\n\nexport interface TMNotification {\n id: string;\n territory_manager_id: string;\n type: string;\n title: string;\n message?: string;\n data?: any;\n read: boolean;\n created_at: string;\n}\n\n// API Response Types\nexport interface TerritoryManagerStats {\n total_commission: number;\n current_month_commission: number;\n total_events_referred: number;\n success_rate: number;\n rank: number;\n achievements_earned: number;\n pending_payouts: number;\n}\n\nexport interface LeaderboardEntry {\n rank: number;\n territory_manager_id: string;\n name: string;\n territory_name: string;\n total_commission: number;\n events_referred: number;\n success_rate: number;\n}\n\nexport interface MarketingMaterial {\n id: string;\n name: string;\n type: 'flyer' | 'email_template' | 'social_media' | 'video' | 'document';\n category: string;\n file_url?: string;\n content?: string;\n customizable: boolean;\n created_at: string;\n}\n\nexport interface ReferralLink {\n id: string;\n territory_manager_id: string;\n url: string;\n qr_code_url: string;\n event_type?: string;\n campaign_name?: string;\n clicks: number;\n conversions: number;\n created_at: string;\n}\n\n// Form Types\nexport interface ApplicationFormData {\n personalInfo: {\n fullName: string;\n email: string;\n phone: string;\n address: Address;\n };\n preferences: {\n desiredTerritory: string;\n hasTransportation: boolean;\n hasEventExperience: boolean;\n motivation: string;\n availability: Availability;\n };\n documents: {\n idUpload?: File;\n resume?: File;\n };\n consent: ApplicationConsent;\n}\n\nexport interface LeadFormData {\n eventName: string;\n organizerContact: ContactInfo;\n eventDetails: EventDetails;\n notes?: string;\n followUpDate?: string;\n}\n\nexport interface ExpenseReportFormData {\n eventId?: string;\n mileage: number;\n receipts: File[];\n description?: string;\n}\n\n// API Function Types\nexport interface TerritoryManagerAPI {\n // Applications\n submitApplication: (data: ApplicationFormData) => Promise<TerritoryApplication>;\n getApplications: (status?: string) => Promise<TerritoryApplication[]>;\n reviewApplication: (id: string, action: 'approve' | 'reject', notes?: string) => Promise<void>;\n \n // Territory Managers\n getTerritoryManager: (id: string) => Promise<TerritoryManager>;\n updateTerritoryManager: (id: string, data: Partial<TerritoryManager>) => Promise<TerritoryManager>;\n getTerritoryManagerStats: (id: string) => Promise<TerritoryManagerStats>;\n \n // Leads\n createLead: (data: LeadFormData) => Promise<Lead>;\n getLeads: (territoryManagerId: string) => Promise<Lead[]>;\n updateLead: (id: string, data: Partial<Lead>) => Promise<Lead>;\n deleteLead: (id: string) => Promise<void>;\n \n // Commissions\n getCommissions: (territoryManagerId: string) => Promise<Commission[]>;\n calculateCommission: (eventId: string) => Promise<Commission>;\n \n // Training\n getTrainingProgress: (territoryManagerId: string) => Promise<TrainingProgress[]>;\n updateTrainingProgress: (territoryManagerId: string, moduleId: string, score?: number) => Promise<void>;\n \n // Achievements\n getAchievements: () => Promise<Achievement[]>;\n getTerritoryManagerAchievements: (territoryManagerId: string) => Promise<TerritoryManagerAchievement[]>;\n \n // Expense Reports\n submitExpenseReport: (data: ExpenseReportFormData) => Promise<ExpenseReport>;\n getExpenseReports: (territoryManagerId: string) => Promise<ExpenseReport[]>;\n \n // Notifications\n getNotifications: (territoryManagerId: string) => Promise<TMNotification[]>;\n markNotificationAsRead: (id: string) => Promise<void>;\n \n // Referrals\n generateReferralLink: (territoryManagerId: string, eventType?: string) => Promise<ReferralLink>;\n getReferralLinks: (territoryManagerId: string) => Promise<ReferralLink[]>;\n \n // Leaderboard\n getLeaderboard: (period?: 'monthly' | 'quarterly' | 'yearly') => Promise<LeaderboardEntry[]>;\n \n // Marketing Materials\n getMarketingMaterials: () => Promise<MarketingMaterial[]>;\n \n // Territories\n getTerritories: () => Promise<Territory[]>;\n assignTerritory: (territoryId: string, territoryManagerId: string) => Promise<void>;\n}","usedDeprecatedRules":[]},{"filePath":"/home/tyler/apps/bct-whitelabel/src/lib/theme.ts","messages":[{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":6,"column":35,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":6,"endColumn":38,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[270,273],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[270,273],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":20,"column":14,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":20,"endColumn":17,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[719,722],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[719,722],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"no-undef","severity":2,"message":"'CustomEvent' is not defined.","line":23,"column":28,"nodeType":"Identifier","messageId":"undef","endLine":23,"endColumn":39}],"suppressedMessages":[],"errorCount":1,"fatalErrorCount":0,"warningCount":2,"fixableErrorCount":0,"fixableWarningCount":0,"source":"// Theme utility functions with enhanced authentication flow support\nexport function getCurrentTheme(): 'light' | 'dark' {\n if (typeof window === 'undefined') return 'dark';\n \n // Check if theme was already set during initialization\n const initialTheme = (window as any).__INITIAL_THEME__;\n if (initialTheme) return initialTheme;\n \n const savedTheme = localStorage.getItem('theme') as 'light' | 'dark';\n if (savedTheme) return savedTheme;\n \n // Default to system preference\n return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';\n}\n\nexport function setTheme(theme: 'light' | 'dark') {\n if (typeof window === 'undefined') return;\n \n // Store for immediate access\n (window as any).__INITIAL_THEME__ = theme;\n \n // Dispatch event - let the layout handle the actual DOM updates\n window.dispatchEvent(new CustomEvent('themeChanged', { \n detail: { theme } \n }));\n}\n\nexport function initializeTheme() {\n if (typeof window === 'undefined') return;\n \n const savedTheme = getCurrentTheme();\n setTheme(savedTheme);\n}\n\nexport function toggleTheme() {\n const currentTheme = getCurrentTheme();\n const newTheme = currentTheme === 'light' ? 'dark' : 'light';\n setTheme(newTheme);\n return newTheme;\n}\n\n// Enhanced function to ensure theme consistency during auth flows\nexport function ensureThemeConsistency() {\n if (typeof window === 'undefined') return;\n \n const currentTheme = getCurrentTheme();\n const documentTheme = document.documentElement.getAttribute('data-theme');\n \n // If themes are out of sync, force synchronization\n if (currentTheme !== documentTheme) {\n document.documentElement.setAttribute('data-theme', currentTheme);\n document.documentElement.classList.remove('light', 'dark');\n document.documentElement.classList.add(currentTheme);\n \n document.body.classList.remove('light', 'dark');\n document.body.classList.add(currentTheme);\n \n localStorage.setItem('theme', currentTheme);\n }\n}","usedDeprecatedRules":[]},{"filePath":"/home/tyler/apps/bct-whitelabel/src/lib/ticket-management.ts","messages":[{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":61,"column":12,"nodeType":null,"messageId":"unusedVar","endLine":61,"endColumn":17},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":95,"column":12,"nodeType":null,"messageId":"unusedVar","endLine":95,"endColumn":17},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":114,"column":12,"nodeType":null,"messageId":"unusedVar","endLine":114,"endColumn":17},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":144,"column":12,"nodeType":null,"messageId":"unusedVar","endLine":144,"endColumn":17},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":206,"column":12,"nodeType":null,"messageId":"unusedVar","endLine":206,"endColumn":17},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":225,"column":12,"nodeType":null,"messageId":"unusedVar","endLine":225,"endColumn":17},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":244,"column":12,"nodeType":null,"messageId":"unusedVar","endLine":244,"endColumn":17}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":7,"fixableErrorCount":0,"fixableWarningCount":0,"source":"import { createClient } from '@supabase/supabase-js';\nimport type { Database } from './database.types';\n\nconst supabase = createClient<Database>(\n import.meta.env.PUBLIC_SUPABASE_URL,\n import.meta.env.PUBLIC_SUPABASE_ANON_KEY\n);\n\nexport interface TicketType {\n id: string;\n name: string;\n description: string;\n price: number;\n quantity_available: number;\n quantity_sold: number;\n is_active: boolean;\n event_id: string;\n sort_order: number;\n created_at: string;\n updated_at?: string;\n}\n\nexport interface TicketTypeFormData {\n name: string;\n description: string;\n price: number;\n quantity_available: number;\n is_active: boolean;\n}\n\nexport interface TicketSale {\n id: string;\n event_id: string;\n ticket_type_id: string;\n price: number;\n checked_in: boolean;\n purchaser_email: string;\n purchaser_name: string;\n created_at: string;\n uuid: string;\n ticket_types: {\n name: string;\n price: number;\n };\n}\n\nexport async function loadTicketTypes(eventId: string): Promise<TicketType[]> {\n try {\n const { data: ticketTypes, error } = await supabase\n .from('ticket_types')\n .select('*')\n .eq('event_id', eventId)\n .order('sort_order', { ascending: true });\n\n if (error) {\n\n return [];\n }\n\n return ticketTypes || [];\n } catch (error) {\n\n return [];\n }\n}\n\nexport async function createTicketType(eventId: string, ticketTypeData: TicketTypeFormData): Promise<TicketType | null> {\n try {\n // Get the next sort order\n const { data: existingTypes } = await supabase\n .from('ticket_types')\n .select('sort_order')\n .eq('event_id', eventId)\n .order('sort_order', { ascending: false })\n .limit(1);\n\n const nextSortOrder = existingTypes?.[0]?.sort_order ? existingTypes[0].sort_order + 1 : 1;\n\n const { data: ticketType, error } = await supabase\n .from('ticket_types')\n .insert({\n ...ticketTypeData,\n event_id: eventId,\n sort_order: nextSortOrder\n })\n .select()\n .single();\n\n if (error) {\n\n return null;\n }\n\n return ticketType;\n } catch (error) {\n\n return null;\n }\n}\n\nexport async function updateTicketType(ticketTypeId: string, updates: Partial<TicketTypeFormData>): Promise<boolean> {\n try {\n const { error } = await supabase\n .from('ticket_types')\n .update(updates)\n .eq('id', ticketTypeId);\n\n if (error) {\n\n return false;\n }\n\n return true;\n } catch (error) {\n\n return false;\n }\n}\n\nexport async function deleteTicketType(ticketTypeId: string): Promise<boolean> {\n try {\n // Check if there are any tickets sold for this type\n const { data: tickets } = await supabase\n .from('tickets')\n .select('id')\n .eq('ticket_type_id', ticketTypeId)\n .limit(1);\n\n if (tickets && tickets.length > 0) {\n throw new Error('Cannot delete ticket type with existing sales');\n }\n\n const { error } = await supabase\n .from('ticket_types')\n .delete()\n .eq('id', ticketTypeId);\n\n if (error) {\n\n return false;\n }\n\n return true;\n } catch (error) {\n\n return false;\n }\n}\n\nexport async function toggleTicketTypeStatus(ticketTypeId: string, isActive: boolean): Promise<boolean> {\n return updateTicketType(ticketTypeId, { is_active: isActive });\n}\n\nexport async function loadTicketSales(eventId: string, filters?: {\n ticketTypeId?: string;\n searchTerm?: string;\n status?: string;\n}): Promise<TicketSale[]> {\n try {\n let query = supabase\n .from('tickets')\n .select(`\n id,\n event_id,\n ticket_type_id,\n price,\n refund_status,\n checked_in,\n purchaser_email,\n purchaser_name,\n created_at,\n uuid,\n ticket_types (\n name,\n price\n )\n `)\n .eq('event_id', eventId)\n .order('created_at', { ascending: false });\n\n // Apply filters\n if (filters?.ticketTypeId) {\n query = query.eq('ticket_type_id', filters.ticketTypeId);\n }\n\n if (filters?.status) {\n if (filters.status === 'confirmed') {\n query = query.or('refund_status.is.null,refund_status.eq.null');\n } else if (filters.status === 'refunded') {\n query = query.eq('refund_status', 'completed');\n }\n }\n\n if (filters?.searchTerm) {\n query = query.or(`purchaser_email.ilike.%${filters.searchTerm}%,purchaser_name.ilike.%${filters.searchTerm}%`);\n }\n\n const { data: tickets, error } = await query;\n\n if (error) {\n\n return [];\n }\n\n return tickets || [];\n } catch (error) {\n\n return [];\n }\n}\n\nexport async function checkInTicket(ticketId: string): Promise<boolean> {\n try {\n const { error } = await supabase\n .from('tickets')\n .update({ checked_in: true })\n .eq('id', ticketId);\n\n if (error) {\n\n return false;\n }\n\n return true;\n } catch (error) {\n\n return false;\n }\n}\n\nexport async function refundTicket(ticketId: string): Promise<boolean> {\n try {\n const { error } = await supabase\n .from('tickets')\n .update({ refund_status: 'completed' })\n .eq('id', ticketId);\n\n if (error) {\n\n return false;\n }\n\n return true;\n } catch (error) {\n\n return false;\n }\n}\n\nexport function formatTicketPrice(cents: number): string {\n return new Intl.NumberFormat('en-US', {\n style: 'currency',\n currency: 'USD'\n }).format(cents / 100);\n}\n\nexport function calculateTicketTypeStats(ticketType: TicketType, sales: TicketSale[]): {\n sold: number;\n available: number;\n revenue: number;\n} {\n const typeSales = sales.filter(sale => sale.ticket_type_id === ticketType.id && sale.status === 'confirmed');\n const sold = typeSales.length;\n const available = ticketType.quantity - sold;\n const revenue = typeSales.reduce((sum, sale) => sum + sale.price_paid, 0);\n\n return { sold, available, revenue };\n}","usedDeprecatedRules":[]},{"filePath":"/home/tyler/apps/bct-whitelabel/src/lib/validation.ts","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/home/tyler/apps/bct-whitelabel/src/middleware.ts","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/home/tyler/apps/bct-whitelabel/src/pages/api/admin/approve-organization.ts","messages":[{"ruleId":"no-empty","severity":1,"message":"Empty block statement.","line":91,"column":21,"nodeType":"BlockStatement","messageId":"unexpected","endLine":93,"endColumn":6,"suggestions":[{"messageId":"suggestComment","data":{"type":"block"},"fix":{"range":[2854,2860],"text":" /* empty */ "},"desc":"Add comment inside empty block statement."}]},{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":103,"column":40,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":103,"endColumn":43,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[3146,3149],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[3146,3149],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":113,"column":38,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":113,"endColumn":41,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[3443,3446],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[3443,3446],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":114,"column":37,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":114,"endColumn":40,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[3491,3494],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[3491,3494],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":114,"column":68,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":114,"endColumn":71,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[3522,3525],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[3522,3525],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"no-undef","severity":2,"message":"'URL' is not defined.","line":116,"column":37,"nodeType":"Identifier","messageId":"undef","endLine":116,"endColumn":40},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'emailError' is defined but never used.","line":125,"column":14,"nodeType":null,"messageId":"unusedVar","endLine":125,"endColumn":24}],"suppressedMessages":[],"errorCount":1,"fatalErrorCount":0,"warningCount":6,"fixableErrorCount":0,"fixableWarningCount":0,"source":"export const prerender = false;\n\nimport type { APIRoute } from 'astro';\nimport { supabase } from '../../../lib/supabase';\nimport { verifyAuth } from '../../../lib/auth';\nimport { logUserActivity } from '../../../lib/logger';\nimport { sendApprovalNotificationEmail } from '../../../lib/email';\n\nexport const POST: APIRoute = async ({ request }) => {\n try {\n // Verify authentication and admin role\n const authContext = await verifyAuth(request);\n if (!authContext || !authContext.isAdmin) {\n return new Response(JSON.stringify({ error: 'Admin access required' }), { \n status: 403,\n headers: { 'Content-Type': 'application/json' }\n });\n }\n\n const { user } = authContext;\n const { organization_id, welcome_message } = await request.json();\n\n if (!organization_id) {\n return new Response(JSON.stringify({ error: 'Organization ID required' }), { \n status: 400,\n headers: { 'Content-Type': 'application/json' }\n });\n }\n\n // Get organization and user data\n const { data: orgData, error: orgError } = await supabase\n .from('organizations')\n .select(`\n *,\n users!inner(id, email, name)\n `)\n .eq('id', organization_id)\n .eq('users.role', 'organizer')\n .single();\n\n if (orgError || !orgData) {\n return new Response(JSON.stringify({ error: 'Organization not found' }), { \n status: 404,\n headers: { 'Content-Type': 'application/json' }\n });\n }\n\n // Check if organization is already approved\n if (orgData.account_status === 'approved' || orgData.account_status === 'active') {\n return new Response(JSON.stringify({ error: 'Organization is already approved' }), { \n status: 400,\n headers: { 'Content-Type': 'application/json' }\n });\n }\n\n // Update organization status\n const { error: updateError } = await supabase\n .from('organizations')\n .update({\n account_status: 'approved',\n approved_at: new Date().toISOString(),\n approved_by: user.id,\n approval_reason: 'Manually approved by admin'\n })\n .eq('id', organization_id);\n\n if (updateError) {\n\n return new Response(JSON.stringify({ error: 'Failed to approve organization' }), { \n status: 500,\n headers: { 'Content-Type': 'application/json' }\n });\n }\n\n // Log the approval\n const { error: auditError } = await supabase\n .from('approval_audit_log')\n .insert({\n organization_id,\n action: 'manually_approved',\n actor_id: user.id,\n reason: 'Manually approved by admin',\n previous_status: 'pending_approval',\n new_status: 'approved',\n metadata: {\n welcome_message: welcome_message || null,\n approval_score: orgData.approval_score\n }\n });\n\n if (auditError) {\n\n }\n\n // Log user activity\n await logUserActivity({\n userId: user.id,\n action: 'organization_approved',\n resourceType: 'organization',\n resourceId: organization_id,\n details: {\n organization_name: orgData.name,\n owner_email: (orgData.users as any).email,\n approval_score: orgData.approval_score,\n welcome_message: welcome_message || null\n }\n });\n\n // Send approval notification email\n try {\n await sendApprovalNotificationEmail({\n organizationName: orgData.name,\n userEmail: (orgData.users as any).email,\n userName: (orgData.users as any).name || (orgData.users as any).email,\n approvedBy: user.name || user.email || 'Admin',\n stripeOnboardingUrl: `${new URL(request.url).origin}/onboarding/stripe`,\n nextSteps: [\n 'Complete secure Stripe Connect verification',\n 'Provide bank account details for automated payouts',\n 'Review and accept terms of service',\n 'Your account will be activated immediately after completion'\n ],\n welcomeMessage: welcome_message || undefined\n });\n } catch (emailError) {\n\n // Don't fail the request for email errors\n }\n\n return new Response(JSON.stringify({\n success: true,\n message: 'Organization approved successfully',\n organization_id\n }), {\n status: 200,\n headers: { 'Content-Type': 'application/json' }\n });\n\n } catch (error) {\n\n return new Response(JSON.stringify({ \n error: 'Failed to approve organization',\n details: error instanceof Error ? error.message : 'Unknown error'\n }), { \n status: 500,\n headers: { 'Content-Type': 'application/json' }\n });\n }\n};","usedDeprecatedRules":[]},{"filePath":"/home/tyler/apps/bct-whitelabel/src/pages/api/admin/check-super-admin.ts","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/home/tyler/apps/bct-whitelabel/src/pages/api/admin/events.ts","messages":[{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":10,"column":15,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":10,"endColumn":18,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[494,497],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[494,497],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":15,"column":10,"nodeType":null,"messageId":"unusedVar","endLine":15,"endColumn":15},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'auth' is assigned a value but never used. Allowed unused vars must match /^_/u.","line":22,"column":11,"nodeType":null,"messageId":"unusedVar","endLine":22,"endColumn":15},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":23,"column":12,"nodeType":null,"messageId":"unusedVar","endLine":23,"endColumn":17},{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":97,"column":46,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":97,"endColumn":49,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[2860,2863],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[2860,2863],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":111,"column":12,"nodeType":null,"messageId":"unusedVar","endLine":111,"endColumn":17}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":6,"fixableErrorCount":0,"fixableWarningCount":0,"source":"import type { APIRoute } from 'astro';\nimport { createClient } from '@supabase/supabase-js';\nimport { logAPIRequest } from '../../../lib/logger';\nimport { requireAdmin } from '../../../lib/auth';\n\n// Handle missing environment variables gracefully\nconst supabaseUrl = process.env.SUPABASE_URL || import.meta.env.SUPABASE_URL || 'https://zctjaivtfyfxokfaemek.supabase.co';\nconst supabaseServiceKey = process.env.SUPABASE_SERVICE_KEY || import.meta.env.SUPABASE_SERVICE_KEY || '';\n\nlet supabase: any = null;\ntry {\n if (supabaseUrl && supabaseServiceKey) {\n supabase = createClient(supabaseUrl, supabaseServiceKey);\n }\n} catch (error) {\n // Silently handle Supabase initialization errors\n}\n\nexport const GET: APIRoute = async ({ request, url }) => {\n try {\n // Server-side admin authentication check\n const auth = await requireAdmin(request);\n } catch (error) {\n return new Response(JSON.stringify({ error: 'Admin access required' }), {\n status: 403,\n headers: { 'Content-Type': 'application/json' }\n });\n }\n \n const startTime = Date.now();\n const clientIP = request.headers.get('x-forwarded-for') || request.headers.get('x-real-ip') || 'unknown';\n const userAgent = request.headers.get('user-agent') || 'unknown';\n \n try {\n if (!supabase) {\n return new Response(JSON.stringify({\n success: false,\n error: 'Database not available'\n }), {\n status: 500,\n headers: { 'Content-Type': 'application/json' }\n });\n }\n\n // Get all events with organization info (admin view)\n const { data: events, error } = await supabase\n .from('events')\n .select(`\n id,\n title,\n description,\n venue,\n start_time,\n end_time,\n image_url,\n slug,\n category,\n is_featured,\n is_public,\n is_published,\n external_source,\n organization_id,\n created_at\n `)\n .order('created_at', { ascending: false });\n\n if (error) {\n return new Response(JSON.stringify({\n success: false,\n error: error.message\n }), {\n status: 500,\n headers: { 'Content-Type': 'application/json' }\n });\n }\n\n const responseTime = Date.now() - startTime;\n\n logAPIRequest({\n method: 'GET',\n url: url.pathname + url.search,\n statusCode: 200,\n responseTime,\n ipAddress: clientIP,\n userAgent\n });\n\n return new Response(JSON.stringify({\n success: true,\n events: events || [],\n total: events?.length || 0,\n summary: {\n total: events?.length || 0,\n featured: events?.filter(e => e.is_featured).length || 0,\n public: events?.filter(e => e.is_public).length || 0,\n firebase: events?.filter(e => e.external_source === 'firebase').length || 0,\n byOrganization: events?.reduce((acc: any, event) => {\n const orgId = event.organization_id || 'no-org';\n acc[orgId] = (acc[orgId] || 0) + 1;\n return acc;\n }, {}) || {}\n }\n }), {\n status: 200,\n headers: {\n 'Content-Type': 'application/json',\n 'Cache-Control': 'no-cache'\n }\n });\n\n } catch (error) {\n const responseTime = Date.now() - startTime;\n \n logAPIRequest({\n method: 'GET',\n url: url.pathname + url.search,\n statusCode: 500,\n responseTime,\n ipAddress: clientIP,\n userAgent\n });\n\n return new Response(JSON.stringify({\n success: false,\n error: 'Internal server error'\n }), {\n status: 500,\n headers: { 'Content-Type': 'application/json' }\n });\n }\n};","usedDeprecatedRules":[]},{"filePath":"/home/tyler/apps/bct-whitelabel/src/pages/api/admin/reject-organization.ts","messages":[{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'sendRejectionNotificationEmail' is defined but never used. Allowed unused vars must match /^_/u.","line":7,"column":10,"nodeType":null,"messageId":"unusedVar","endLine":7,"endColumn":40},{"ruleId":"no-empty","severity":1,"message":"Empty block statement.","line":91,"column":21,"nodeType":"BlockStatement","messageId":"unexpected","endLine":93,"endColumn":6,"suggestions":[{"messageId":"suggestComment","data":{"type":"block"},"fix":{"range":[2818,2824],"text":" /* empty */ "},"desc":"Add comment inside empty block statement."}]},{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":103,"column":40,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":103,"endColumn":43,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[3110,3113],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[3110,3113],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":114,"column":38,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":114,"endColumn":41,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[3429,3432],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[3429,3432],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":115,"column":37,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":115,"endColumn":40,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[3477,3480],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[3477,3480],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":115,"column":68,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":115,"endColumn":71,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[3508,3511],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[3508,3511],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"no-undef","severity":2,"message":"'URL' is not defined.","line":118,"column":28,"nodeType":"Identifier","messageId":"undef","endLine":118,"endColumn":31},{"ruleId":"no-empty","severity":1,"message":"Empty block statement.","line":126,"column":23,"nodeType":"BlockStatement","messageId":"unexpected","endLine":128,"endColumn":8,"suggestions":[{"messageId":"suggestComment","data":{"type":"block"},"fix":{"range":[3937,3945],"text":" /* empty */ "},"desc":"Add comment inside empty block statement."}]},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'emailError' is defined but never used.","line":129,"column":14,"nodeType":null,"messageId":"unusedVar","endLine":129,"endColumn":24}],"suppressedMessages":[],"errorCount":1,"fatalErrorCount":0,"warningCount":8,"fixableErrorCount":0,"fixableWarningCount":0,"source":"export const prerender = false;\n\nimport type { APIRoute } from 'astro';\nimport { supabase } from '../../../lib/supabase';\nimport { verifyAuth } from '../../../lib/auth';\nimport { logUserActivity } from '../../../lib/logger';\nimport { sendRejectionNotificationEmail } from '../../../lib/email';\n\nexport const POST: APIRoute = async ({ request }) => {\n try {\n // Verify authentication and admin role\n const authContext = await verifyAuth(request);\n if (!authContext || !authContext.isAdmin) {\n return new Response(JSON.stringify({ error: 'Admin access required' }), { \n status: 403,\n headers: { 'Content-Type': 'application/json' }\n });\n }\n\n const { user } = authContext;\n const { organization_id, rejection_reason } = await request.json();\n\n if (!organization_id || !rejection_reason) {\n return new Response(JSON.stringify({ error: 'Organization ID and rejection reason are required' }), { \n status: 400,\n headers: { 'Content-Type': 'application/json' }\n });\n }\n\n // Get organization and user data\n const { data: orgData, error: orgError } = await supabase\n .from('organizations')\n .select(`\n *,\n users!inner(id, email, name)\n `)\n .eq('id', organization_id)\n .eq('users.role', 'organizer')\n .single();\n\n if (orgError || !orgData) {\n return new Response(JSON.stringify({ error: 'Organization not found' }), { \n status: 404,\n headers: { 'Content-Type': 'application/json' }\n });\n }\n\n // Check if organization is pending approval\n if (orgData.account_status !== 'pending_approval') {\n return new Response(JSON.stringify({ error: 'Organization is not pending approval' }), { \n status: 400,\n headers: { 'Content-Type': 'application/json' }\n });\n }\n\n // Update organization status\n const { error: updateError } = await supabase\n .from('organizations')\n .update({\n account_status: 'rejected',\n approved_at: new Date().toISOString(),\n approved_by: user.id,\n approval_reason: rejection_reason\n })\n .eq('id', organization_id);\n\n if (updateError) {\n\n return new Response(JSON.stringify({ error: 'Failed to reject organization' }), { \n status: 500,\n headers: { 'Content-Type': 'application/json' }\n });\n }\n\n // Log the rejection\n const { error: auditError } = await supabase\n .from('approval_audit_log')\n .insert({\n organization_id,\n action: 'rejected',\n actor_id: user.id,\n reason: rejection_reason,\n previous_status: 'pending_approval',\n new_status: 'rejected',\n metadata: {\n rejection_reason,\n approval_score: orgData.approval_score\n }\n });\n\n if (auditError) {\n\n }\n\n // Log user activity\n await logUserActivity({\n userId: user.id,\n action: 'organization_rejected',\n resourceType: 'organization',\n resourceId: organization_id,\n details: {\n organization_name: orgData.name,\n owner_email: (orgData.users as any).email,\n approval_score: orgData.approval_score,\n rejection_reason\n }\n });\n\n // Send rejection notification email\n try {\n // Create a rejection email function if it doesn't exist\n const emailData = {\n organizationName: orgData.name,\n userEmail: (orgData.users as any).email,\n userName: (orgData.users as any).name || (orgData.users as any).email,\n rejectionReason: rejection_reason,\n supportEmail: 'support@blackcanyontickets.com',\n reapplyUrl: `${new URL(request.url).origin}/onboarding/organization`\n };\n\n // For now, send a basic rejection email using the existing email system\n const { error: emailError } = await supabase.functions.invoke('send-rejection-email', {\n body: emailData\n });\n\n if (emailError) {\n\n }\n } catch (emailError) {\n\n // Don't fail the request for email errors\n }\n\n return new Response(JSON.stringify({\n success: true,\n message: 'Organization rejected successfully',\n organization_id\n }), {\n status: 200,\n headers: { 'Content-Type': 'application/json' }\n });\n\n } catch (error) {\n\n return new Response(JSON.stringify({ \n error: 'Failed to reject organization',\n details: error instanceof Error ? error.message : 'Unknown error'\n }), { \n status: 500,\n headers: { 'Content-Type': 'application/json' }\n });\n }\n};","usedDeprecatedRules":[]},{"filePath":"/home/tyler/apps/bct-whitelabel/src/pages/api/admin/scraper.ts","messages":[{"ruleId":"no-case-declarations","severity":2,"message":"Unexpected lexical declaration in case block.","line":39,"column":9,"nodeType":"VariableDeclaration","messageId":"unexpected","endLine":39,"endColumn":67,"suggestions":[{"messageId":"addBrackets","fix":{"range":[1354,1603],"text":"{ const initialized = await initializeScraperOrganization();\n result = {\n success: initialized,\n message: initialized ? 'Scraper organization initialized' : 'Failed to initialize scraper organization'\n };\n break; }"},"desc":"Add {} brackets around the case block."}]},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'url' is defined but never used. Allowed unused args must match /^_/u.","line":106,"column":48,"nodeType":null,"messageId":"unusedVar","endLine":106,"endColumn":51},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":151,"column":12,"nodeType":null,"messageId":"unusedVar","endLine":151,"endColumn":17}],"suppressedMessages":[],"errorCount":1,"fatalErrorCount":0,"warningCount":2,"fixableErrorCount":0,"fixableWarningCount":0,"source":"import type { APIRoute } from 'astro';\nimport { runFirebaseEventScraper, initializeScraperOrganization } from '../../../lib/firebaseEventScraper';\nimport { logAPIRequest, logSecurityEvent } from '../../../lib/logger';\nimport { checkRateLimit } from '../../../lib/auth';\n\nexport const POST: APIRoute = async ({ request }) => {\n const startTime = Date.now();\n const clientIP = request.headers.get('x-forwarded-for') || request.headers.get('x-real-ip') || 'unknown';\n const userAgent = request.headers.get('user-agent') || 'unknown';\n \n try {\n // Rate limiting - only 50 requests per hour per IP (increased for testing)\n if (!checkRateLimit(clientIP, 50, 3600000)) {\n logSecurityEvent({\n type: 'rate_limit',\n ipAddress: clientIP,\n userAgent,\n severity: 'medium',\n details: { endpoint: '/api/admin/scraper', limit: 5 }\n });\n\n return new Response(JSON.stringify({ \n error: 'Rate limit exceeded. Please try again later.' \n }), {\n status: 429,\n headers: { 'Content-Type': 'application/json' }\n });\n }\n\n // Parse request body for action\n const body = await request.json().catch(() => ({ action: 'run' }));\n const action = body.action || 'run';\n\n let result;\n \n switch (action) {\n case 'init':\n // Initialize scraper organization\n const initialized = await initializeScraperOrganization();\n result = {\n success: initialized,\n message: initialized ? 'Scraper organization initialized' : 'Failed to initialize scraper organization'\n };\n break;\n \n case 'run':\n default:\n // Run the Firebase scraper\n result = await runFirebaseEventScraper();\n break;\n }\n\n const responseTime = Date.now() - startTime;\n\n // Log API request\n logAPIRequest({\n method: 'POST',\n url: '/api/admin/scraper',\n statusCode: 200,\n responseTime,\n ipAddress: clientIP,\n userAgent\n });\n\n return new Response(JSON.stringify(result), {\n status: 200,\n headers: {\n 'Content-Type': 'application/json',\n 'Cache-Control': 'no-cache'\n }\n });\n\n } catch (error) {\n const responseTime = Date.now() - startTime;\n \n logAPIRequest({\n method: 'POST',\n url: '/api/admin/scraper',\n statusCode: 500,\n responseTime,\n ipAddress: clientIP,\n userAgent\n });\n\n logSecurityEvent({\n type: 'api_error',\n ipAddress: clientIP,\n userAgent,\n severity: 'high',\n details: { \n endpoint: '/api/admin/scraper', \n error: error instanceof Error ? error.message : 'Unknown error' \n }\n });\n\n return new Response(JSON.stringify({\n success: false,\n message: 'Internal server error'\n }), {\n status: 500,\n headers: { 'Content-Type': 'application/json' }\n });\n }\n};\n\nexport const GET: APIRoute = async ({ request, url }) => {\n const startTime = Date.now();\n const clientIP = request.headers.get('x-forwarded-for') || request.headers.get('x-real-ip') || 'unknown';\n const userAgent = request.headers.get('user-agent') || 'unknown';\n \n try {\n // Rate limiting - only 10 requests per hour per IP for status checks\n if (!checkRateLimit(clientIP, 10, 3600000)) {\n return new Response(JSON.stringify({ \n error: 'Rate limit exceeded. Please try again later.' \n }), {\n status: 429,\n headers: { 'Content-Type': 'application/json' }\n });\n }\n\n // Return scraper status\n const responseTime = Date.now() - startTime;\n\n logAPIRequest({\n method: 'GET',\n url: '/api/admin/scraper',\n statusCode: 200,\n responseTime,\n ipAddress: clientIP,\n userAgent\n });\n\n return new Response(JSON.stringify({\n success: true,\n message: 'Event scraper is operational',\n endpoints: {\n run: 'POST /api/admin/scraper with {\"action\": \"run\"}',\n init: 'POST /api/admin/scraper with {\"action\": \"init\"}',\n status: 'GET /api/admin/scraper'\n },\n rateLimit: '5 requests per hour for POST, 10 for GET'\n }), {\n status: 200,\n headers: {\n 'Content-Type': 'application/json',\n 'Cache-Control': 'no-cache'\n }\n });\n\n } catch (error) {\n return new Response(JSON.stringify({\n success: false,\n message: 'Internal server error'\n }), {\n status: 500,\n headers: { 'Content-Type': 'application/json' }\n });\n }\n};","usedDeprecatedRules":[]},{"filePath":"/home/tyler/apps/bct-whitelabel/src/pages/api/admin/setup-super-admin.ts","messages":[{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'auth' is assigned a value but never used. Allowed unused vars must match /^_/u.","line":8,"column":11,"nodeType":null,"messageId":"unusedVar","endLine":8,"endColumn":15},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":68,"column":12,"nodeType":null,"messageId":"unusedVar","endLine":68,"endColumn":17}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":2,"fixableErrorCount":0,"fixableWarningCount":0,"source":"import type { APIRoute } from 'astro';\nimport { requireAdmin } from '../../../lib/auth';\nimport { supabase } from '../../../lib/supabase';\n\nexport const POST: APIRoute = async ({ request }) => {\n try {\n // Verify admin authentication\n const auth = await requireAdmin(request);\n \n const { email } = await request.json();\n \n if (!email) {\n return new Response(JSON.stringify({\n success: false,\n error: 'Email is required'\n }), {\n status: 400,\n headers: { 'Content-Type': 'application/json' }\n });\n }\n\n // Check if user exists\n const { data: existingUser } = await supabase\n .from('users')\n .select('id, email, role')\n .eq('email', email)\n .single();\n\n if (!existingUser) {\n return new Response(JSON.stringify({\n success: false,\n error: 'User not found. User must be registered first.'\n }), {\n status: 404,\n headers: { 'Content-Type': 'application/json' }\n });\n }\n\n // Make user admin using the database function\n const { error } = await supabase.rpc('make_user_admin', {\n user_email: email\n });\n\n if (error) {\n\n return new Response(JSON.stringify({\n success: false,\n error: 'Failed to make user admin'\n }), {\n status: 500,\n headers: { 'Content-Type': 'application/json' }\n });\n }\n\n return new Response(JSON.stringify({\n success: true,\n message: `Successfully made ${email} an admin`,\n user: {\n id: existingUser.id,\n email: existingUser.email,\n role: 'admin'\n }\n }), {\n status: 200,\n headers: { 'Content-Type': 'application/json' }\n });\n\n } catch (error) {\n\n return new Response(JSON.stringify({\n success: false,\n error: 'Access denied or server error'\n }), {\n status: 500,\n headers: { 'Content-Type': 'application/json' }\n });\n }\n};","usedDeprecatedRules":[]},{"filePath":"/home/tyler/apps/bct-whitelabel/src/pages/api/admin/subscriptions.ts","messages":[{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'request' is defined but never used. Allowed unused args must match /^_/u.","line":11,"column":39,"nodeType":null,"messageId":"unusedVar","endLine":11,"endColumn":46},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":205,"column":20,"nodeType":null,"messageId":"unusedVar","endLine":205,"endColumn":25}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":2,"fixableErrorCount":0,"fixableWarningCount":0,"source":"export const prerender = false;\n\nimport type { APIRoute } from 'astro';\nimport { supabase } from '../../../lib/supabase';\nimport Stripe from 'stripe';\n\nconst stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, {\n apiVersion: '2024-06-20',\n});\n\nexport const GET: APIRoute = async ({ request, url }) => {\n try {\n // Get current user\n const { data: { user }, error: userError } = await supabase.auth.getUser();\n if (userError || !user) {\n return new Response(JSON.stringify({ error: 'Unauthorized' }), {\n status: 401,\n headers: { 'Content-Type': 'application/json' }\n });\n }\n\n // Check if user is admin\n const { data: userRole } = await supabase\n .from('user_roles')\n .select('role')\n .eq('user_id', user.id)\n .eq('role', 'admin')\n .single();\n\n if (!userRole) {\n return new Response(JSON.stringify({ error: 'Admin access required' }), {\n status: 403,\n headers: { 'Content-Type': 'application/json' }\n });\n }\n\n // Get query parameters\n const searchParams = url.searchParams;\n const page = parseInt(searchParams.get('page') || '1');\n const limit = parseInt(searchParams.get('limit') || '25');\n const status = searchParams.get('status');\n\n // Get organizations with their subscription info\n let query = supabase\n .from('organizations')\n .select(`\n *,\n users (\n id,\n email,\n name\n )\n `)\n .order('created_at', { ascending: false });\n\n // Apply pagination\n const offset = (page - 1) * limit;\n query = query.range(offset, offset + limit - 1);\n\n const { data: organizations, error: orgsError } = await query;\n\n if (orgsError) {\n throw orgsError;\n }\n\n // Get Stripe subscription info for each organization\n const organizationsWithSubscriptions = await Promise.all(\n organizations.map(async (org) => {\n let subscriptionInfo = null;\n \n if (org.stripe_account_id) {\n try {\n // Get Stripe account info\n const account = await stripe.accounts.retrieve(org.stripe_account_id);\n \n // Check if there are any subscriptions (this would be custom logic)\n // For now, we'll just return account status\n subscriptionInfo = {\n stripe_account_id: org.stripe_account_id,\n account_status: account.charges_enabled ? 'active' : 'inactive',\n details_submitted: account.details_submitted,\n payouts_enabled: account.payouts_enabled,\n country: account.country,\n created: account.created\n };\n } catch (stripeError) {\n\n subscriptionInfo = {\n stripe_account_id: org.stripe_account_id,\n account_status: 'error',\n error: stripeError.message\n };\n }\n }\n\n return {\n ...org,\n subscription: subscriptionInfo\n };\n })\n );\n\n // Filter by status if provided\n const filteredOrgs = status \n ? organizationsWithSubscriptions.filter(org => \n org.subscription?.account_status === status\n )\n : organizationsWithSubscriptions;\n\n // Get total count\n const { count, error: countError } = await supabase\n .from('organizations')\n .select('*', { count: 'exact', head: true });\n\n if (countError) {\n throw countError;\n }\n\n return new Response(JSON.stringify({\n organizations: filteredOrgs,\n pagination: {\n page,\n limit,\n total: count || 0,\n pages: Math.ceil((count || 0) / limit)\n }\n }), {\n status: 200,\n headers: { 'Content-Type': 'application/json' }\n });\n\n } catch (error) {\n\n return new Response(JSON.stringify({ \n error: 'Failed to fetch subscriptions',\n details: error.message \n }), {\n status: 500,\n headers: { 'Content-Type': 'application/json' }\n });\n }\n};\n\nexport const POST: APIRoute = async ({ request }) => {\n try {\n const body = await request.json();\n const { action, organization_id, ...data } = body;\n\n // Get current user\n const { data: { user }, error: userError } = await supabase.auth.getUser();\n if (userError || !user) {\n return new Response(JSON.stringify({ error: 'Unauthorized' }), {\n status: 401,\n headers: { 'Content-Type': 'application/json' }\n });\n }\n\n // Check if user is admin\n const { data: userRole } = await supabase\n .from('user_roles')\n .select('role')\n .eq('user_id', user.id)\n .eq('role', 'admin')\n .single();\n\n if (!userRole) {\n return new Response(JSON.stringify({ error: 'Admin access required' }), {\n status: 403,\n headers: { 'Content-Type': 'application/json' }\n });\n }\n\n // Get organization\n const { data: organization, error: orgError } = await supabase\n .from('organizations')\n .select('*')\n .eq('id', organization_id)\n .single();\n\n if (orgError || !organization) {\n return new Response(JSON.stringify({ error: 'Organization not found' }), {\n status: 404,\n headers: { 'Content-Type': 'application/json' }\n });\n }\n\n let result;\n \n switch (action) {\n case 'suspend_account':\n if (organization.stripe_account_id) {\n try {\n // In a real scenario, you'd implement custom suspension logic\n // For now, we'll just update our database\n result = await supabase\n .from('organizations')\n .update({ \n status: 'suspended',\n suspended_at: new Date().toISOString(),\n suspended_by: user.id\n })\n .eq('id', organization_id)\n .select()\n .single();\n } catch (error) {\n throw new Error('Failed to suspend account');\n }\n }\n break;\n \n case 'reactivate_account':\n result = await supabase\n .from('organizations')\n .update({ \n status: 'active',\n suspended_at: null,\n suspended_by: null\n })\n .eq('id', organization_id)\n .select()\n .single();\n break;\n \n case 'update_billing':\n // This would typically involve updating Stripe subscription\n // For now, just update organization metadata\n result = await supabase\n .from('organizations')\n .update(data)\n .eq('id', organization_id)\n .select()\n .single();\n break;\n \n default:\n return new Response(JSON.stringify({ error: 'Invalid action' }), {\n status: 400,\n headers: { 'Content-Type': 'application/json' }\n });\n }\n\n if (result && result.error) {\n throw result.error;\n }\n\n return new Response(JSON.stringify({\n success: true,\n organization: result?.data || { message: 'Action completed' }\n }), {\n status: 200,\n headers: { 'Content-Type': 'application/json' }\n });\n\n } catch (error) {\n\n return new Response(JSON.stringify({ \n error: 'Failed to manage subscription',\n details: error.message \n }), {\n status: 500,\n headers: { 'Content-Type': 'application/json' }\n });\n }\n};","usedDeprecatedRules":[]},{"filePath":"/home/tyler/apps/bct-whitelabel/src/pages/api/admin/super-analytics.ts","messages":[{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":10,"column":15,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":10,"endColumn":18,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[451,454],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[451,454],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":20,"column":10,"nodeType":null,"messageId":"unusedVar","endLine":20,"endColumn":15},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'url' is defined but never used. Allowed unused args must match /^_/u.","line":24,"column":48,"nodeType":null,"messageId":"unusedVar","endLine":24,"endColumn":51},{"ruleId":"no-undef","severity":2,"message":"'URL' is not defined.","line":96,"column":30,"nodeType":"Identifier","messageId":"undef","endLine":96,"endColumn":33},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":136,"column":12,"nodeType":null,"messageId":"unusedVar","endLine":136,"endColumn":17},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":332,"column":12,"nodeType":null,"messageId":"unusedVar","endLine":332,"endColumn":17},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'whereClause' is defined but never used. Allowed unused args must match /^_/u.","line":343,"column":36,"nodeType":null,"messageId":"unusedVar","endLine":343,"endColumn":47},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'filterType' is defined but never used. Allowed unused args must match /^_/u.","line":343,"column":57,"nodeType":null,"messageId":"unusedVar","endLine":343,"endColumn":67},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'filterValue' is defined but never used. Allowed unused args must match /^_/u.","line":343,"column":78,"nodeType":null,"messageId":"unusedVar","endLine":343,"endColumn":89},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":418,"column":12,"nodeType":null,"messageId":"unusedVar","endLine":418,"endColumn":17},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'whereClause' is defined but never used. Allowed unused args must match /^_/u.","line":429,"column":40,"nodeType":null,"messageId":"unusedVar","endLine":429,"endColumn":51},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'filterType' is defined but never used. Allowed unused args must match /^_/u.","line":429,"column":61,"nodeType":null,"messageId":"unusedVar","endLine":429,"endColumn":71},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'filterValue' is defined but never used. Allowed unused args must match /^_/u.","line":429,"column":82,"nodeType":null,"messageId":"unusedVar","endLine":429,"endColumn":93},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":498,"column":12,"nodeType":null,"messageId":"unusedVar","endLine":498,"endColumn":17},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'whereClause' is defined but never used. Allowed unused args must match /^_/u.","line":509,"column":34,"nodeType":null,"messageId":"unusedVar","endLine":509,"endColumn":45},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'filterType' is defined but never used. Allowed unused args must match /^_/u.","line":509,"column":55,"nodeType":null,"messageId":"unusedVar","endLine":509,"endColumn":65},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'filterValue' is defined but never used. Allowed unused args must match /^_/u.","line":509,"column":76,"nodeType":null,"messageId":"unusedVar","endLine":509,"endColumn":87},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":568,"column":12,"nodeType":null,"messageId":"unusedVar","endLine":568,"endColumn":17},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'whereClause' is defined but never used. Allowed unused args must match /^_/u.","line":579,"column":31,"nodeType":null,"messageId":"unusedVar","endLine":579,"endColumn":42},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'filterType' is defined but never used. Allowed unused args must match /^_/u.","line":579,"column":52,"nodeType":null,"messageId":"unusedVar","endLine":579,"endColumn":62},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'filterValue' is defined but never used. Allowed unused args must match /^_/u.","line":579,"column":73,"nodeType":null,"messageId":"unusedVar","endLine":579,"endColumn":84},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":606,"column":12,"nodeType":null,"messageId":"unusedVar","endLine":606,"endColumn":17},{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":617,"column":38,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":617,"endColumn":41,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[21849,21852],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[21849,21852],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'whereClause' is defined but never used. Allowed unused args must match /^_/u.","line":646,"column":36,"nodeType":null,"messageId":"unusedVar","endLine":646,"endColumn":47},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'filterType' is defined but never used. Allowed unused args must match /^_/u.","line":646,"column":57,"nodeType":null,"messageId":"unusedVar","endLine":646,"endColumn":67},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'filterValue' is defined but never used. Allowed unused args must match /^_/u.","line":646,"column":78,"nodeType":null,"messageId":"unusedVar","endLine":646,"endColumn":89},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":730,"column":12,"nodeType":null,"messageId":"unusedVar","endLine":730,"endColumn":17},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'whereClause' is defined but never used. Allowed unused args must match /^_/u.","line":741,"column":37,"nodeType":null,"messageId":"unusedVar","endLine":741,"endColumn":48},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'filterType' is defined but never used. Allowed unused args must match /^_/u.","line":741,"column":58,"nodeType":null,"messageId":"unusedVar","endLine":741,"endColumn":68},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'filterValue' is defined but never used. Allowed unused args must match /^_/u.","line":741,"column":79,"nodeType":null,"messageId":"unusedVar","endLine":741,"endColumn":90},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":863,"column":12,"nodeType":null,"messageId":"unusedVar","endLine":863,"endColumn":17},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'search' is assigned a value but never used. Allowed unused vars must match /^_/u.","line":881,"column":11,"nodeType":null,"messageId":"unusedVar","endLine":881,"endColumn":17},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":1060,"column":12,"nodeType":null,"messageId":"unusedVar","endLine":1060,"endColumn":17}],"suppressedMessages":[],"errorCount":1,"fatalErrorCount":0,"warningCount":32,"fixableErrorCount":0,"fixableWarningCount":0,"source":"import type { APIRoute } from 'astro';\nimport { createClient } from '@supabase/supabase-js';\n\nexport const prerender = false;\n\n// Initialize Supabase client with service key for admin access\nconst supabaseUrl = process.env.PUBLIC_SUPABASE_URL || import.meta.env.PUBLIC_SUPABASE_URL || 'https://zctjaivtfyfxokfaemek.supabase.co';\nconst supabaseServiceKey = process.env.SUPABASE_SERVICE_KEY || import.meta.env.SUPABASE_SERVICE_KEY || '';\n\nlet supabase: any = null;\ntry {\n if (supabaseUrl && supabaseServiceKey) {\n supabase = createClient(supabaseUrl, supabaseServiceKey, {\n auth: {\n autoRefreshToken: false,\n persistSession: false\n }\n });\n }\n} catch (error) {\n // Supabase initialization failed\n}\n\nexport const GET: APIRoute = async ({ request, url }) => {\n try {\n if (!supabase) {\n return new Response(JSON.stringify({\n success: false,\n error: 'Database not available'\n }), {\n status: 500,\n headers: { 'Content-Type': 'application/json' }\n });\n }\n\n // Verify user authentication and admin role\n const authHeader = request.headers.get('Authorization');\n if (!authHeader || !authHeader.startsWith('Bearer ')) {\n return new Response(JSON.stringify({\n success: false,\n error: 'Authentication required'\n }), {\n status: 401,\n headers: { 'Content-Type': 'application/json' }\n });\n }\n\n const token = authHeader.substring(7);\n \n // Create a client with the anon key to validate the user token\n const supabaseAnonKey = process.env.PUBLIC_SUPABASE_ANON_KEY || import.meta.env.PUBLIC_SUPABASE_ANON_KEY || '';\n \n if (!supabaseAnonKey) {\n return new Response(JSON.stringify({\n success: false,\n error: 'Authentication configuration error'\n }), {\n status: 500,\n headers: { 'Content-Type': 'application/json' }\n });\n }\n \n const supabaseUser = createClient(supabaseUrl, supabaseAnonKey);\n \n const { data: { user }, error: authError } = await supabaseUser.auth.getUser(token);\n \n if (authError || !user) {\n return new Response(JSON.stringify({\n success: false,\n error: 'Invalid authentication'\n }), {\n status: 401,\n headers: { 'Content-Type': 'application/json' }\n });\n }\n\n // Check if user is admin using service key\n const { data: userRecord, error: userError } = await supabase\n .from('users')\n .select('role')\n .eq('id', user.id)\n .single();\n\n if (userError || !userRecord || userRecord.role !== 'admin') {\n return new Response(JSON.stringify({\n success: false,\n error: 'Admin access required'\n }), {\n status: 403,\n headers: { 'Content-Type': 'application/json' }\n });\n }\n\n // NOTE: Using service key bypasses RLS, so we have admin access to all data\n \n const searchParams = new URL(request.url).searchParams;\n const metric = searchParams.get('metric');\n const startDate = searchParams.get('start_date');\n const endDate = searchParams.get('end_date');\n const filterType = searchParams.get('filter_type');\n const filterValue = searchParams.get('filter_value');\n\n let whereClause = '';\n if (startDate && endDate) {\n whereClause = `AND created_at >= '${startDate}' AND created_at <= '${endDate}'`;\n }\n\n switch (metric) {\n case 'platform_overview':\n return await getPlatformOverview();\n \n case 'revenue_breakdown':\n return await getRevenueBreakdown(whereClause, filterType, filterValue);\n \n case 'organizer_performance':\n return await getOrganizerPerformance(whereClause, filterType, filterValue);\n \n case 'event_analytics':\n return await getEventAnalytics(whereClause, filterType, filterValue);\n \n case 'sales_trends':\n return await getSalesTrends(whereClause, filterType, filterValue);\n \n case 'traffic_analytics':\n return await getTrafficAnalytics(whereClause, filterType, filterValue);\n \n case 'location_analytics':\n return await getLocationAnalytics(whereClause, filterType, filterValue);\n \n case 'ticket_analytics':\n return await getTicketAnalytics(whereClause, filterType, filterValue);\n \n default:\n return await getPlatformOverview();\n }\n } catch (error) {\n return new Response(JSON.stringify({\n success: false,\n error: 'Access denied or server error'\n }), {\n status: 500,\n headers: { 'Content-Type': 'application/json' }\n });\n }\n};\n\nasync function getPlatformOverview() {\n try {\n const [\n organizationsResult,\n eventsResult,\n ticketsResult,\n purchasesResult,\n usersResult\n ] = await Promise.all([\n supabase.from('organizations').select('id, name, created_at, platform_fee_percentage, platform_fee_fixed'),\n supabase.from('events').select('id, title, created_at, organization_id, is_published'),\n supabase.from('tickets').select('id, price, created_at, event_id'),\n supabase.from('purchase_attempts').select('id, total_amount, platform_fee, created_at, completed_at, event_id, referral_source, utm_source, utm_campaign, utm_medium, country_code, country_name, region, city, ip_address').not('completed_at', 'is', null),\n supabase.from('users').select('id, created_at, role, organization_id')\n ]);\n\n const organizations = organizationsResult.data || [];\n const events = eventsResult.data || [];\n const tickets = ticketsResult.data || [];\n const purchases = purchasesResult.data || [];\n const users = usersResult.data || [];\n\n // Calculate key metrics\n const totalRevenue = purchases.reduce((sum, p) => sum + (p.total_amount || 0), 0);\n const totalPlatformFees = purchases.reduce((sum, p) => sum + (p.platform_fee || 0), 0);\n const totalNetToOrganizers = totalRevenue - totalPlatformFees;\n \n // Month-over-month calculations\n const now = new Date();\n const thisMonth = new Date(now.getFullYear(), now.getMonth(), 1);\n const lastMonth = new Date(now.getFullYear(), now.getMonth() - 1, 1);\n \n const thisMonthPurchases = purchases.filter(p => new Date(p.completed_at) >= thisMonth);\n const lastMonthPurchases = purchases.filter(p => {\n const date = new Date(p.completed_at);\n return date >= lastMonth && date < thisMonth;\n });\n\n const thisMonthRevenue = thisMonthPurchases.reduce((sum, p) => sum + (p.total_amount || 0), 0);\n const lastMonthRevenue = lastMonthPurchases.reduce((sum, p) => sum + (p.total_amount || 0), 0);\n \n const revenueGrowth = lastMonthRevenue > 0 ? ((thisMonthRevenue - lastMonthRevenue) / lastMonthRevenue) * 100 : 0;\n\n // Top performing organizers\n const organizerPerformance = {};\n purchases.forEach(purchase => {\n const event = events.find(e => e.id === purchase.event_id);\n if (event) {\n const orgId = event.organization_id;\n if (!organizerPerformance[orgId]) {\n const org = organizations.find(o => o.id === orgId);\n organizerPerformance[orgId] = {\n name: org?.name || 'Unknown',\n revenue: 0,\n fees: 0,\n events: 0,\n tickets: 0\n };\n }\n organizerPerformance[orgId].revenue += purchase.total_amount || 0;\n organizerPerformance[orgId].fees += purchase.platform_fee || 0;\n }\n });\n\n // Count events and tickets per organizer\n Object.keys(organizerPerformance).forEach(orgId => {\n organizerPerformance[orgId].events = events.filter(e => e.organization_id === orgId).length;\n organizerPerformance[orgId].tickets = tickets.filter(t => {\n const event = events.find(e => e.id === t.event_id);\n return event?.organization_id === orgId;\n }).length;\n });\n\n const topOrganizers = Object.values(organizerPerformance)\n .sort((a, b) => b.revenue - a.revenue)\n .slice(0, 10);\n\n // Traffic source analytics\n const trafficSources = {};\n const geoData = {};\n \n purchases.forEach(purchase => {\n // Referral source tracking\n const source = purchase.referral_source || purchase.utm_source || 'direct';\n if (!trafficSources[source]) {\n trafficSources[source] = {\n source: source,\n revenue: 0,\n count: 0,\n campaign: purchase.utm_campaign || null,\n medium: purchase.utm_medium || null\n };\n }\n trafficSources[source].revenue += purchase.total_amount || 0;\n trafficSources[source].count++;\n \n // Geographic data tracking\n if (purchase.country_code) {\n const country = purchase.country_name || purchase.country_code;\n if (!geoData[country]) {\n geoData[country] = {\n country: country,\n code: purchase.country_code,\n revenue: 0,\n count: 0,\n cities: new Set()\n };\n }\n geoData[country].revenue += purchase.total_amount || 0;\n geoData[country].count++;\n if (purchase.city) {\n geoData[country].cities.add(purchase.city);\n }\n }\n });\n\n // Convert traffic sources to array and sort\n const topTrafficSources = Object.values(trafficSources)\n .sort((a, b) => b.revenue - a.revenue)\n .slice(0, 10)\n .map(source => ({\n ...source,\n revenue: source.revenue / 100,\n avgOrderValue: source.count > 0 ? (source.revenue / source.count) / 100 : 0\n }));\n\n // Convert geo data to array and sort\n const topCountries = Object.values(geoData)\n .sort((a, b) => b.revenue - a.revenue)\n .slice(0, 10)\n .map(geo => ({\n ...geo,\n revenue: geo.revenue / 100,\n avgOrderValue: geo.count > 0 ? (geo.revenue / geo.count) / 100 : 0,\n cityCount: geo.cities.size\n }));\n\n // Recent activity\n const recentActivity = [\n ...purchases.slice(-5).map(p => ({\n type: 'purchase',\n amount: p.total_amount,\n date: p.completed_at,\n event_id: p.event_id\n })),\n ...events.slice(-5).map(e => ({\n type: 'event',\n title: e.title,\n date: e.created_at,\n organization_id: e.organization_id\n })),\n ...organizations.slice(-3).map(o => ({\n type: 'organization',\n name: o.name,\n date: o.created_at\n }))\n ].sort((a, b) => new Date(b.date) - new Date(a.date)).slice(0, 10);\n\n return new Response(JSON.stringify({\n success: true,\n data: {\n summary: {\n totalRevenue: totalRevenue / 100,\n totalPlatformFees: totalPlatformFees / 100,\n totalNetToOrganizers: totalNetToOrganizers / 100,\n activeOrganizers: organizations.length,\n totalEvents: events.length,\n publishedEvents: events.filter(e => e.is_published).length,\n totalTickets: tickets.length,\n totalUsers: users.length,\n adminUsers: users.filter(u => u.role === 'admin').length,\n revenueGrowth: revenueGrowth,\n thisMonthRevenue: thisMonthRevenue / 100,\n lastMonthRevenue: lastMonthRevenue / 100\n },\n topOrganizers,\n recentActivity,\n monthlyTrends: getMonthlyTrends(purchases, 6),\n trafficSources: topTrafficSources,\n geoData: topCountries\n }\n }), {\n status: 200,\n headers: { 'Content-Type': 'application/json' }\n });\n } catch (error) {\n return new Response(JSON.stringify({\n success: false,\n error: 'Failed to load platform overview'\n }), {\n status: 500,\n headers: { 'Content-Type': 'application/json' }\n });\n }\n}\n\nasync function getRevenueBreakdown(whereClause: string, filterType?: string, filterValue?: string) {\n try {\n const { data: purchases } = await supabase\n .from('purchase_attempts')\n .select('total_amount, platform_fee, completed_at, event_id')\n .not('completed_at', 'is', null);\n\n const { data: events } = await supabase\n .from('events')\n .select('id, organization_id, title, start_time');\n\n const { data: organizations } = await supabase\n .from('organizations')\n .select('id, name');\n\n // Group by organization\n const organizationBreakdown = {};\n \n purchases?.forEach(purchase => {\n const event = events?.find(e => e.id === purchase.event_id);\n if (event) {\n const orgId = event.organization_id;\n const org = organizations?.find(o => o.id === orgId);\n \n if (!organizationBreakdown[orgId]) {\n organizationBreakdown[orgId] = {\n name: org?.name || 'Unknown',\n grossRevenue: 0,\n platformFees: 0,\n netRevenue: 0,\n transactionCount: 0,\n events: new Set()\n };\n }\n \n organizationBreakdown[orgId].grossRevenue += purchase.total_amount || 0;\n organizationBreakdown[orgId].platformFees += purchase.platform_fee || 0;\n organizationBreakdown[orgId].netRevenue += (purchase.total_amount || 0) - (purchase.platform_fee || 0);\n organizationBreakdown[orgId].transactionCount++;\n organizationBreakdown[orgId].events.add(event.id);\n }\n });\n\n // Convert Set to count\n Object.values(organizationBreakdown).forEach(org => {\n org.eventCount = org.events.size;\n delete org.events;\n });\n\n const breakdown = Object.values(organizationBreakdown)\n .sort((a, b) => b.grossRevenue - a.grossRevenue)\n .map(org => ({\n ...org,\n grossRevenue: org.grossRevenue / 100,\n platformFees: org.platformFees / 100,\n netRevenue: org.netRevenue / 100,\n averageTransactionValue: org.transactionCount > 0 ? (org.grossRevenue / org.transactionCount) / 100 : 0\n }));\n\n return new Response(JSON.stringify({\n success: true,\n data: {\n breakdown,\n totals: {\n grossRevenue: breakdown.reduce((sum, org) => sum + org.grossRevenue, 0),\n platformFees: breakdown.reduce((sum, org) => sum + org.platformFees, 0),\n netRevenue: breakdown.reduce((sum, org) => sum + org.netRevenue, 0),\n transactionCount: breakdown.reduce((sum, org) => sum + org.transactionCount, 0),\n eventCount: breakdown.reduce((sum, org) => sum + org.eventCount, 0)\n }\n }\n }), {\n status: 200,\n headers: { 'Content-Type': 'application/json' }\n });\n } catch (error) {\n return new Response(JSON.stringify({\n success: false,\n error: 'Failed to load revenue breakdown'\n }), {\n status: 500,\n headers: { 'Content-Type': 'application/json' }\n });\n }\n}\n\nasync function getOrganizerPerformance(whereClause: string, filterType?: string, filterValue?: string) {\n try {\n const { data: organizations } = await supabase\n .from('organizations')\n .select('id, name, created_at, platform_fee_percentage, platform_fee_fixed');\n\n const { data: events } = await supabase\n .from('events')\n .select('id, title, organization_id, created_at, is_published');\n\n const { data: tickets } = await supabase\n .from('tickets')\n .select('id, event_id, price, created_at');\n\n const { data: purchases } = await supabase\n .from('purchase_attempts')\n .select('id, event_id, total_amount, platform_fee, completed_at')\n .not('completed_at', 'is', null);\n\n const performance = organizations?.map(org => {\n const orgEvents = events?.filter(e => e.organization_id === org.id) || [];\n const orgTickets = tickets?.filter(t => {\n const event = events?.find(e => e.id === t.event_id);\n return event?.organization_id === org.id;\n }) || [];\n const orgPurchases = purchases?.filter(p => {\n const event = events?.find(e => e.id === p.event_id);\n return event?.organization_id === org.id;\n }) || [];\n\n const totalRevenue = orgPurchases.reduce((sum, p) => sum + (p.total_amount || 0), 0);\n const totalFees = orgPurchases.reduce((sum, p) => sum + (p.platform_fee || 0), 0);\n const avgTicketPrice = orgTickets.length > 0 ? \n orgTickets.reduce((sum, t) => sum + (t.price || 0), 0) / orgTickets.length : 0;\n\n return {\n id: org.id,\n name: org.name,\n joinDate: org.created_at,\n eventCount: orgEvents.length,\n publishedEvents: orgEvents.filter(e => e.is_published).length,\n ticketsSold: orgTickets.length,\n totalRevenue: totalRevenue / 100,\n platformFees: totalFees / 100,\n netRevenue: (totalRevenue - totalFees) / 100,\n avgTicketPrice,\n avgRevenuePerEvent: orgEvents.length > 0 ? (totalRevenue / orgEvents.length) / 100 : 0,\n customFeePercentage: org.platform_fee_percentage,\n customFeeFixed: org.platform_fee_fixed\n };\n }) || [];\n\n return new Response(JSON.stringify({\n success: true,\n data: {\n organizers: performance.sort((a, b) => b.totalRevenue - a.totalRevenue),\n summary: {\n totalOrganizers: performance.length,\n averageEventsPerOrganizer: performance.length > 0 ? \n performance.reduce((sum, o) => sum + o.eventCount, 0) / performance.length : 0,\n averageRevenuePerOrganizer: performance.length > 0 ? \n performance.reduce((sum, o) => sum + o.totalRevenue, 0) / performance.length : 0,\n topPerformerRevenue: performance.length > 0 ? performance[0].totalRevenue : 0\n }\n }\n }), {\n status: 200,\n headers: { 'Content-Type': 'application/json' }\n });\n } catch (error) {\n return new Response(JSON.stringify({\n success: false,\n error: 'Failed to load organizer performance'\n }), {\n status: 500,\n headers: { 'Content-Type': 'application/json' }\n });\n }\n}\n\nasync function getEventAnalytics(whereClause: string, filterType?: string, filterValue?: string) {\n try {\n const { data: events } = await supabase\n .from('events')\n .select('id, title, organization_id, created_at, start_time, is_published, category');\n\n const { data: tickets } = await supabase\n .from('tickets')\n .select('id, event_id, price, created_at');\n\n const { data: ticketTypes } = await supabase\n .from('ticket_types')\n .select('id, event_id, name, price, quantity_available, quantity_sold');\n\n const eventAnalytics = events?.map(event => {\n const eventTickets = tickets?.filter(t => t.event_id === event.id) || [];\n const eventTicketTypes = ticketTypes?.filter(tt => tt.event_id === event.id) || [];\n\n const totalRevenue = eventTickets.reduce((sum, t) => sum + (t.price || 0), 0);\n const totalCapacity = eventTicketTypes.reduce((sum, tt) => sum + (tt.quantity_available || 0), 0);\n const soldTickets = eventTicketTypes.reduce((sum, tt) => sum + (tt.quantity_sold || 0), 0);\n\n return {\n id: event.id,\n title: event.title,\n organizationId: event.organization_id,\n createdAt: event.created_at,\n startTime: event.start_time,\n isPublished: event.is_published,\n category: event.category,\n ticketsSold: eventTickets.length,\n totalRevenue,\n totalCapacity,\n soldTickets,\n sellThroughRate: totalCapacity > 0 ? (soldTickets / totalCapacity) * 100 : 0,\n avgTicketPrice: eventTickets.length > 0 ? totalRevenue / eventTickets.length : 0,\n ticketTypeCount: eventTicketTypes.length\n };\n }) || [];\n\n return new Response(JSON.stringify({\n success: true,\n data: {\n events: eventAnalytics.sort((a, b) => b.totalRevenue - a.totalRevenue),\n summary: {\n totalEvents: eventAnalytics.length,\n publishedEvents: eventAnalytics.filter(e => e.isPublished).length,\n totalTicketsSold: eventAnalytics.reduce((sum, e) => sum + e.ticketsSold, 0),\n totalRevenue: eventAnalytics.reduce((sum, e) => sum + e.totalRevenue, 0),\n avgSellThroughRate: eventAnalytics.length > 0 ? \n eventAnalytics.reduce((sum, e) => sum + e.sellThroughRate, 0) / eventAnalytics.length : 0,\n avgTicketPrice: eventAnalytics.length > 0 ? \n eventAnalytics.reduce((sum, e) => sum + e.avgTicketPrice, 0) / eventAnalytics.length : 0\n }\n }\n }), {\n status: 200,\n headers: { 'Content-Type': 'application/json' }\n });\n } catch (error) {\n return new Response(JSON.stringify({\n success: false,\n error: 'Failed to load event analytics'\n }), {\n status: 500,\n headers: { 'Content-Type': 'application/json' }\n });\n }\n}\n\nasync function getSalesTrends(whereClause: string, filterType?: string, filterValue?: string) {\n try {\n const { data: purchases } = await supabase\n .from('purchase_attempts')\n .select('total_amount, platform_fee, completed_at')\n .not('completed_at', 'is', null);\n\n const trends = getMonthlyTrends(purchases || [], 12);\n\n return new Response(JSON.stringify({\n success: true,\n data: {\n trends,\n summary: {\n totalPeriods: trends.length,\n averageMonthlyRevenue: trends.length > 0 ? \n trends.reduce((sum, t) => sum + t.revenue, 0) / trends.length : 0,\n averageMonthlyFees: trends.length > 0 ? \n trends.reduce((sum, t) => sum + t.fees, 0) / trends.length : 0,\n growth: trends.length > 1 ? \n ((trends[trends.length - 1].revenue - trends[0].revenue) / trends[0].revenue) * 100 : 0\n }\n }\n }), {\n status: 200,\n headers: { 'Content-Type': 'application/json' }\n });\n } catch (error) {\n return new Response(JSON.stringify({\n success: false,\n error: 'Failed to load sales trends'\n }), {\n status: 500,\n headers: { 'Content-Type': 'application/json' }\n });\n }\n}\n\nfunction getMonthlyTrends(purchases: any[], monthCount: number) {\n const months = [];\n const now = new Date();\n \n for (let i = monthCount - 1; i >= 0; i--) {\n const date = new Date(now.getFullYear(), now.getMonth() - i, 1);\n months.push(date);\n }\n\n return months.map(month => {\n const nextMonth = new Date(month.getFullYear(), month.getMonth() + 1, 1);\n const monthPurchases = purchases.filter(p => {\n const purchaseDate = new Date(p.completed_at);\n return purchaseDate >= month && purchaseDate < nextMonth;\n });\n\n const revenue = monthPurchases.reduce((sum, p) => sum + (p.total_amount || 0), 0) / 100;\n const fees = monthPurchases.reduce((sum, p) => sum + (p.platform_fee || 0), 0) / 100;\n\n return {\n month: month.toLocaleDateString('en-US', { month: 'short', year: 'numeric' }),\n revenue,\n fees,\n transactions: monthPurchases.length,\n avgTransactionValue: monthPurchases.length > 0 ? revenue / monthPurchases.length : 0\n };\n });\n}\n\nasync function getTrafficAnalytics(whereClause: string, filterType?: string, filterValue?: string) {\n try {\n const { data: purchases } = await supabase\n .from('purchase_attempts')\n .select('total_amount, platform_fee, completed_at, referral_source, utm_source, utm_campaign, utm_medium, utm_term, utm_content')\n .not('completed_at', 'is', null);\n\n const trafficAnalytics = {};\n const campaignAnalytics = {};\n \n purchases?.forEach(purchase => {\n const source = purchase.referral_source || purchase.utm_source || 'direct';\n const campaign = purchase.utm_campaign || 'none';\n const medium = purchase.utm_medium || 'none';\n \n // Traffic source analytics\n if (!trafficAnalytics[source]) {\n trafficAnalytics[source] = {\n source: source,\n revenue: 0,\n transactions: 0,\n mediums: new Set(),\n campaigns: new Set()\n };\n }\n \n trafficAnalytics[source].revenue += purchase.total_amount || 0;\n trafficAnalytics[source].transactions++;\n trafficAnalytics[source].mediums.add(medium);\n trafficAnalytics[source].campaigns.add(campaign);\n \n // Campaign analytics\n if (purchase.utm_campaign) {\n const campaignKey = `${source}_${campaign}`;\n if (!campaignAnalytics[campaignKey]) {\n campaignAnalytics[campaignKey] = {\n source: source,\n campaign: campaign,\n medium: medium,\n revenue: 0,\n transactions: 0\n };\n }\n campaignAnalytics[campaignKey].revenue += purchase.total_amount || 0;\n campaignAnalytics[campaignKey].transactions++;\n }\n });\n\n // Convert to arrays and calculate metrics\n const topSources = Object.values(trafficAnalytics)\n .sort((a, b) => b.revenue - a.revenue)\n .map(source => ({\n ...source,\n revenue: source.revenue / 100,\n avgOrderValue: source.transactions > 0 ? (source.revenue / source.transactions) / 100 : 0,\n mediumCount: source.mediums.size,\n campaignCount: source.campaigns.size\n }));\n\n const topCampaigns = Object.values(campaignAnalytics)\n .sort((a, b) => b.revenue - a.revenue)\n .slice(0, 20)\n .map(campaign => ({\n ...campaign,\n revenue: campaign.revenue / 100,\n avgOrderValue: campaign.transactions > 0 ? (campaign.revenue / campaign.transactions) / 100 : 0\n }));\n\n return new Response(JSON.stringify({\n success: true,\n data: {\n sources: topSources,\n campaigns: topCampaigns,\n summary: {\n totalSources: topSources.length,\n totalCampaigns: topCampaigns.length,\n topSourceRevenue: topSources.length > 0 ? topSources[0].revenue : 0,\n topCampaignRevenue: topCampaigns.length > 0 ? topCampaigns[0].revenue : 0\n }\n }\n }), {\n status: 200,\n headers: { 'Content-Type': 'application/json' }\n });\n } catch (error) {\n return new Response(JSON.stringify({\n success: false,\n error: 'Failed to load traffic analytics'\n }), {\n status: 500,\n headers: { 'Content-Type': 'application/json' }\n });\n }\n}\n\nasync function getLocationAnalytics(whereClause: string, filterType?: string, filterValue?: string) {\n try {\n const { data: purchases } = await supabase\n .from('purchase_attempts')\n .select('total_amount, platform_fee, completed_at, ip_address, country_code, country_name, region, city, latitude, longitude')\n .not('completed_at', 'is', null);\n\n const countryAnalytics = {};\n const cityAnalytics = {};\n const regionAnalytics = {};\n \n purchases?.forEach(purchase => {\n if (purchase.country_code) {\n const countryKey = purchase.country_code;\n const country = purchase.country_name || purchase.country_code;\n \n // Country analytics\n if (!countryAnalytics[countryKey]) {\n countryAnalytics[countryKey] = {\n code: purchase.country_code,\n name: country,\n revenue: 0,\n transactions: 0,\n cities: new Set(),\n regions: new Set()\n };\n }\n \n countryAnalytics[countryKey].revenue += purchase.total_amount || 0;\n countryAnalytics[countryKey].transactions++;\n \n if (purchase.city) {\n countryAnalytics[countryKey].cities.add(purchase.city);\n \n // City analytics\n const cityKey = `${purchase.country_code}_${purchase.city}`;\n if (!cityAnalytics[cityKey]) {\n cityAnalytics[cityKey] = {\n country: country,\n countryCode: purchase.country_code,\n city: purchase.city,\n region: purchase.region,\n revenue: 0,\n transactions: 0\n };\n }\n cityAnalytics[cityKey].revenue += purchase.total_amount || 0;\n cityAnalytics[cityKey].transactions++;\n }\n \n if (purchase.region) {\n countryAnalytics[countryKey].regions.add(purchase.region);\n \n // Region analytics\n const regionKey = `${purchase.country_code}_${purchase.region}`;\n if (!regionAnalytics[regionKey]) {\n regionAnalytics[regionKey] = {\n country: country,\n countryCode: purchase.country_code,\n region: purchase.region,\n revenue: 0,\n transactions: 0,\n cities: new Set()\n };\n }\n regionAnalytics[regionKey].revenue += purchase.total_amount || 0;\n regionAnalytics[regionKey].transactions++;\n if (purchase.city) {\n regionAnalytics[regionKey].cities.add(purchase.city);\n }\n }\n }\n });\n\n // Convert to arrays and calculate metrics\n const topCountries = Object.values(countryAnalytics)\n .sort((a, b) => b.revenue - a.revenue)\n .map(country => ({\n ...country,\n revenue: country.revenue / 100,\n avgOrderValue: country.transactions > 0 ? (country.revenue / country.transactions) / 100 : 0,\n cityCount: country.cities.size,\n regionCount: country.regions.size\n }));\n\n const topCities = Object.values(cityAnalytics)\n .sort((a, b) => b.revenue - a.revenue)\n .slice(0, 50)\n .map(city => ({\n ...city,\n revenue: city.revenue / 100,\n avgOrderValue: city.transactions > 0 ? (city.revenue / city.transactions) / 100 : 0\n }));\n\n const topRegions = Object.values(regionAnalytics)\n .sort((a, b) => b.revenue - a.revenue)\n .slice(0, 30)\n .map(region => ({\n ...region,\n revenue: region.revenue / 100,\n avgOrderValue: region.transactions > 0 ? (region.revenue / region.transactions) / 100 : 0,\n cityCount: region.cities.size\n }));\n\n return new Response(JSON.stringify({\n success: true,\n data: {\n countries: topCountries,\n cities: topCities,\n regions: topRegions,\n summary: {\n totalCountries: topCountries.length,\n totalCities: topCities.length,\n totalRegions: topRegions.length,\n topCountryRevenue: topCountries.length > 0 ? topCountries[0].revenue : 0,\n topCityRevenue: topCities.length > 0 ? topCities[0].revenue : 0\n }\n }\n }), {\n status: 200,\n headers: { 'Content-Type': 'application/json' }\n });\n } catch (error) {\n return new Response(JSON.stringify({\n success: false,\n error: 'Failed to load location analytics'\n }), {\n status: 500,\n headers: { 'Content-Type': 'application/json' }\n });\n }\n}\n\nasync function getTicketAnalytics(whereClause: string, filterType?: string, filterValue?: string) {\n try {\n const searchParams = new URLSearchParams(whereClause);\n const startDate = searchParams.get('start_date');\n const endDate = searchParams.get('end_date');\n const minPrice = searchParams.get('min_price');\n const maxPrice = searchParams.get('max_price');\n const search = searchParams.get('search');\n const page = parseInt(searchParams.get('page') || '1');\n const limit = parseInt(searchParams.get('limit') || '50');\n const offset = (page - 1) * limit;\n\n // Build where conditions\n const ticketConditions = [];\n if (startDate) ticketConditions.push(`tickets.created_at >= '${startDate}'`);\n if (endDate) ticketConditions.push(`tickets.created_at <= '${endDate}'`);\n if (minPrice) ticketConditions.push(`tickets.price >= ${parseInt(minPrice) * 100}`);\n if (maxPrice) ticketConditions.push(`tickets.price <= ${parseInt(maxPrice) * 100}`);\n\n // Get tickets with related data\n let ticketsQuery = supabase\n .from('tickets')\n .select(`\n id,\n price,\n created_at,\n used_at,\n refunded_at,\n cancelled_at,\n customer_email,\n customer_name,\n seat_number,\n seat_row,\n qr_code,\n event_id,\n ticket_type_id,\n events (\n id,\n title,\n start_time,\n organization_id,\n organizations (\n id,\n name\n )\n ),\n ticket_types (\n id,\n name,\n description\n )\n `)\n .order('created_at', { ascending: false });\n\n // Apply filters\n if (filterType === 'event' && filterValue) {\n ticketsQuery = ticketsQuery.eq('event_id', filterValue);\n }\n if (filterType === 'organizer' && filterValue) {\n ticketsQuery = ticketsQuery.eq('events.organization_id', filterValue);\n }\n if (filterType === 'status' && filterValue) {\n switch (filterValue) {\n case 'active':\n ticketsQuery = ticketsQuery.is('used_at', null).is('refunded_at', null).is('cancelled_at', null);\n break;\n case 'used':\n ticketsQuery = ticketsQuery.not('used_at', 'is', null);\n break;\n case 'refunded':\n ticketsQuery = ticketsQuery.not('refunded_at', 'is', null);\n break;\n case 'cancelled':\n ticketsQuery = ticketsQuery.not('cancelled_at', 'is', null);\n break;\n }\n }\n\n // Get total count for pagination\n const { count: totalCount } = await ticketsQuery;\n\n // Get paginated results\n const { data: tickets } = await ticketsQuery\n .range(offset, offset + limit - 1);\n\n // Calculate statistics\n const stats = {\n total: totalCount || 0,\n used: 0,\n active: 0,\n refunded: 0,\n cancelled: 0,\n totalRevenue: 0,\n averagePrice: 0\n };\n\n // Get all tickets for statistics (not just the page)\n const { data: allTickets } = await supabase\n .from('tickets')\n .select('price, used_at, refunded_at, cancelled_at');\n\n allTickets?.forEach(ticket => {\n stats.totalRevenue += ticket.price || 0;\n \n if (ticket.used_at) {\n stats.used++;\n } else if (ticket.refunded_at) {\n stats.refunded++;\n } else if (ticket.cancelled_at) {\n stats.cancelled++;\n } else {\n stats.active++;\n }\n });\n\n stats.averagePrice = stats.total > 0 ? stats.totalRevenue / stats.total : 0;\n stats.totalRevenue = stats.totalRevenue / 100; // Convert to dollars\n\n // Format tickets for display\n const formattedTickets = tickets?.map(ticket => ({\n id: ticket.id,\n price: ticket.price / 100,\n createdAt: ticket.created_at,\n usedAt: ticket.used_at,\n refundedAt: ticket.refunded_at,\n cancelledAt: ticket.cancelled_at,\n customerEmail: ticket.customer_email,\n customerName: ticket.customer_name,\n seatNumber: ticket.seat_number,\n seatRow: ticket.seat_row,\n qrCode: ticket.qr_code,\n status: ticket.used_at ? 'used' : \n ticket.refunded_at ? 'refunded' : \n ticket.cancelled_at ? 'cancelled' : 'active',\n event: {\n id: ticket.events?.id,\n title: ticket.events?.title,\n startTime: ticket.events?.start_time,\n organizationId: ticket.events?.organization_id,\n organizationName: ticket.events?.organizations?.name\n },\n ticketType: {\n id: ticket.ticket_types?.id,\n name: ticket.ticket_types?.name,\n description: ticket.ticket_types?.description\n }\n })) || [];\n\n // Get unique events and organizers for filters\n const { data: events } = await supabase\n .from('events')\n .select('id, title, organization_id, organizations(name)')\n .order('title');\n\n const { data: organizations } = await supabase\n .from('organizations')\n .select('id, name')\n .order('name');\n\n return new Response(JSON.stringify({\n success: true,\n data: {\n tickets: formattedTickets,\n stats,\n pagination: {\n page,\n limit,\n total: totalCount || 0,\n pages: Math.ceil((totalCount || 0) / limit)\n },\n filters: {\n events: events?.map(e => ({\n id: e.id,\n title: e.title,\n organizationName: e.organizations?.name\n })) || [],\n organizations: organizations?.map(o => ({\n id: o.id,\n name: o.name\n })) || []\n }\n }\n }), {\n status: 200,\n headers: { 'Content-Type': 'application/json' }\n });\n } catch (error) {\n return new Response(JSON.stringify({\n success: false,\n error: 'Failed to load ticket analytics'\n }), {\n status: 500,\n headers: { 'Content-Type': 'application/json' }\n });\n }\n}","usedDeprecatedRules":[]},{"filePath":"/home/tyler/apps/bct-whitelabel/src/pages/api/admin/territory-manager/applications.ts","messages":[{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'request' is defined but never used. Allowed unused args must match /^_/u.","line":4,"column":39,"nodeType":null,"messageId":"unusedVar","endLine":4,"endColumn":46},{"ruleId":"no-case-declarations","severity":2,"message":"Unexpected lexical declaration in case block.","line":42,"column":11,"nodeType":"VariableDeclaration","messageId":"unexpected","endLine":42,"endColumn":67,"suggestions":[{"messageId":"addBrackets","fix":{"range":[1508,1650],"text":"{ const quarterStart = Math.floor(now.getMonth() / 3) * 3;\n cutoffDate = new Date(now.getFullYear(), quarterStart, 1);\n break; }"},"desc":"Add {} brackets around the case block."}]},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":78,"column":12,"nodeType":null,"messageId":"unusedVar","endLine":78,"endColumn":17}],"suppressedMessages":[],"errorCount":1,"fatalErrorCount":0,"warningCount":2,"fixableErrorCount":0,"fixableWarningCount":0,"source":"import type { APIRoute } from 'astro';\nimport { territoryManagerAPI } from '../../../../lib/territory-manager-api';\n\nexport const GET: APIRoute = async ({ request, url }) => {\n try {\n // Check admin permissions\n // This would be implemented with proper auth middleware\n // For now, we'll assume the user is authenticated and has admin role\n \n const searchParams = url.searchParams;\n const page = parseInt(searchParams.get('page') || '1');\n const limit = parseInt(searchParams.get('limit') || '10');\n const status = searchParams.get('status');\n const territory = searchParams.get('territory');\n const date = searchParams.get('date');\n const search = searchParams.get('search');\n\n // Get applications from database\n let applications = await territoryManagerAPI.getApplications(status || undefined);\n\n // Filter by territory if specified\n if (territory) {\n applications = applications.filter(app => app.desired_territory === territory);\n }\n\n // Filter by date if specified\n if (date) {\n const now = new Date();\n let cutoffDate: Date;\n \n switch (date) {\n case 'today':\n cutoffDate = new Date(now.getFullYear(), now.getMonth(), now.getDate());\n break;\n case 'week':\n cutoffDate = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000);\n break;\n case 'month':\n cutoffDate = new Date(now.getFullYear(), now.getMonth(), 1);\n break;\n case 'quarter':\n const quarterStart = Math.floor(now.getMonth() / 3) * 3;\n cutoffDate = new Date(now.getFullYear(), quarterStart, 1);\n break;\n default:\n cutoffDate = new Date(0);\n }\n \n applications = applications.filter(app => new Date(app.created_at) >= cutoffDate);\n }\n\n // Filter by search term if specified\n if (search) {\n const searchTerm = search.toLowerCase();\n applications = applications.filter(app => \n app.full_name.toLowerCase().includes(searchTerm) ||\n app.email.toLowerCase().includes(searchTerm)\n );\n }\n\n // Calculate pagination\n const total = applications.length;\n const startIndex = (page - 1) * limit;\n const endIndex = startIndex + limit;\n const paginatedApplications = applications.slice(startIndex, endIndex);\n\n return new Response(JSON.stringify({\n applications: paginatedApplications,\n total,\n page,\n limit,\n totalPages: Math.ceil(total / limit)\n }), {\n status: 200,\n headers: { 'Content-Type': 'application/json' }\n });\n\n } catch (error) {\n\n return new Response(JSON.stringify({ error: 'Internal server error' }), {\n status: 500,\n headers: { 'Content-Type': 'application/json' }\n });\n }\n};","usedDeprecatedRules":[]},{"filePath":"/home/tyler/apps/bct-whitelabel/src/pages/api/admin/territory-manager/applications/[id]/approve.ts","messages":[{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'request' is defined but never used. Allowed unused args must match /^_/u.","line":4,"column":48,"nodeType":null,"messageId":"unusedVar","endLine":4,"endColumn":55},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":35,"column":12,"nodeType":null,"messageId":"unusedVar","endLine":35,"endColumn":17},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'emailContent' is assigned a value but never used. Allowed unused vars must match /^_/u.","line":59,"column":11,"nodeType":null,"messageId":"unusedVar","endLine":59,"endColumn":23},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":93,"column":12,"nodeType":null,"messageId":"unusedVar","endLine":93,"endColumn":17},{"ruleId":"no-empty","severity":1,"message":"Empty block statement.","line":93,"column":19,"nodeType":"BlockStatement","messageId":"unexpected","endLine":95,"endColumn":4,"suggestions":[{"messageId":"suggestComment","data":{"type":"block"},"fix":{"range":[3237,3241],"text":" /* empty */ "},"desc":"Add comment inside empty block statement."}]}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":5,"fixableErrorCount":0,"fixableWarningCount":0,"source":"import type { APIRoute } from 'astro';\nimport { territoryManagerAPI } from '../../../../../lib/territory-manager-api';\n\nexport const POST: APIRoute = async ({ params, request }) => {\n try {\n const { id } = params;\n \n if (!id) {\n return new Response(JSON.stringify({ error: 'Application ID is required' }), {\n status: 400,\n headers: { 'Content-Type': 'application/json' }\n });\n }\n\n // Check admin permissions\n // This would be implemented with proper auth middleware\n // For now, we'll assume the user is authenticated and has admin role\n\n // Approve the application\n await territoryManagerAPI.reviewApplication(id, 'approve');\n\n // Send approval email to applicant\n await sendApprovalEmail(id);\n\n // Log the approval action\n\n return new Response(JSON.stringify({ \n success: true, \n message: 'Application approved successfully' \n }), {\n status: 200,\n headers: { 'Content-Type': 'application/json' }\n });\n\n } catch (error) {\n\n return new Response(JSON.stringify({ error: 'Internal server error' }), {\n status: 500,\n headers: { 'Content-Type': 'application/json' }\n });\n }\n};\n\nasync function sendApprovalEmail(applicationId: string) {\n // TODO: Implement email sending logic\n // This would integrate with your email service (Resend, SendGrid, etc.)\n \n try {\n // Get application details\n const applications = await territoryManagerAPI.getApplications();\n const application = applications.find(app => app.id === applicationId);\n \n if (!application) {\n\n return;\n }\n\n // Email content\n const emailContent = {\n to: application.email,\n subject: 'Welcome to the Black Canyon Tickets Territory Manager Program!',\n html: `\n <h2>Congratulations! Your application has been approved!</h2>\n <p>Hi ${application.full_name},</p>\n <p>We're excited to welcome you to the Black Canyon Tickets Territory Manager program!</p>\n \n <h3>What's Next?</h3>\n <ol>\n <li><strong>Background Check:</strong> You'll receive a separate email with instructions to complete your background check.</li>\n <li><strong>Territory Assignment:</strong> Once your background check is complete, you'll be assigned to your preferred territory.</li>\n <li><strong>Training Access:</strong> You'll receive access to our comprehensive training program.</li>\n <li><strong>Portal Access:</strong> Your Territory Manager portal will be activated within 24 hours.</li>\n </ol>\n \n <h3>Getting Started</h3>\n <p>In the meantime, you can:</p>\n <ul>\n <li>Review our Territory Manager handbook (attached)</li>\n <li>Join our Territory Manager community forum</li>\n <li>Start thinking about local events in your area</li>\n </ul>\n \n <p>We're looking forward to having you on our team and helping you succeed!</p>\n \n <p>Best regards,<br>\n The Black Canyon Tickets Team<br>\n <a href=\"mailto:support@blackcanyontickets.com\">support@blackcanyontickets.com</a></p>\n `\n };\n\n // TODO: Actually send the email using your email service\n\n } catch (error) {\n\n }\n}","usedDeprecatedRules":[]},{"filePath":"/home/tyler/apps/bct-whitelabel/src/pages/api/admin/territory-manager/applications/[id]/reject.ts","messages":[{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":39,"column":12,"nodeType":null,"messageId":"unusedVar","endLine":39,"endColumn":17},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'emailContent' is assigned a value but never used. Allowed unused vars must match /^_/u.","line":63,"column":11,"nodeType":null,"messageId":"unusedVar","endLine":63,"endColumn":23},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":94,"column":12,"nodeType":null,"messageId":"unusedVar","endLine":94,"endColumn":17},{"ruleId":"no-empty","severity":1,"message":"Empty block statement.","line":94,"column":19,"nodeType":"BlockStatement","messageId":"unexpected","endLine":96,"endColumn":4,"suggestions":[{"messageId":"suggestComment","data":{"type":"block"},"fix":{"range":[3111,3115],"text":" /* empty */ "},"desc":"Add comment inside empty block statement."}]}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":4,"fixableErrorCount":0,"fixableWarningCount":0,"source":"import type { APIRoute } from 'astro';\nimport { territoryManagerAPI } from '../../../../../lib/territory-manager-api';\n\nexport const POST: APIRoute = async ({ params, request }) => {\n try {\n const { id } = params;\n \n if (!id) {\n return new Response(JSON.stringify({ error: 'Application ID is required' }), {\n status: 400,\n headers: { 'Content-Type': 'application/json' }\n });\n }\n\n // Check admin permissions\n // This would be implemented with proper auth middleware\n // For now, we'll assume the user is authenticated and has admin role\n\n // Get request body\n const body = await request.json();\n const { reason } = body;\n\n // Reject the application\n await territoryManagerAPI.reviewApplication(id, 'reject', reason);\n\n // Send rejection email to applicant\n await sendRejectionEmail(id, reason);\n\n // Log the rejection action\n\n return new Response(JSON.stringify({ \n success: true, \n message: 'Application rejected successfully' \n }), {\n status: 200,\n headers: { 'Content-Type': 'application/json' }\n });\n\n } catch (error) {\n\n return new Response(JSON.stringify({ error: 'Internal server error' }), {\n status: 500,\n headers: { 'Content-Type': 'application/json' }\n });\n }\n};\n\nasync function sendRejectionEmail(applicationId: string, reason?: string) {\n // TODO: Implement email sending logic\n // This would integrate with your email service (Resend, SendGrid, etc.)\n \n try {\n // Get application details\n const applications = await territoryManagerAPI.getApplications();\n const application = applications.find(app => app.id === applicationId);\n \n if (!application) {\n\n return;\n }\n\n // Email content\n const emailContent = {\n to: application.email,\n subject: 'Territory Manager Application Update - Black Canyon Tickets',\n html: `\n <h2>Thank you for your interest in the Territory Manager program</h2>\n <p>Hi ${application.full_name},</p>\n <p>Thank you for taking the time to apply for the Black Canyon Tickets Territory Manager program. After careful review, we have decided not to move forward with your application at this time.</p>\n \n ${reason ? `\n <h3>Feedback</h3>\n <p>${reason}</p>\n ` : ''}\n \n <h3>What's Next?</h3>\n <p>While we won't be moving forward with your application at this time, we encourage you to:</p>\n <ul>\n <li>Gain more experience in event management or sales</li>\n <li>Build connections in your local event community</li>\n <li>Consider reapplying in the future when you have more relevant experience</li>\n </ul>\n \n <p>We appreciate your interest in Black Canyon Tickets and wish you the best in your future endeavors.</p>\n \n <p>Best regards,<br>\n The Black Canyon Tickets Team<br>\n <a href=\"mailto:support@blackcanyontickets.com\">support@blackcanyontickets.com</a></p>\n `\n };\n\n // TODO: Actually send the email using your email service\n\n } catch (error) {\n\n }\n}","usedDeprecatedRules":[]},{"filePath":"/home/tyler/apps/bct-whitelabel/src/pages/api/admin/tickets.ts","messages":[{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'request' is defined but never used. Allowed unused args must match /^_/u.","line":6,"column":39,"nodeType":null,"messageId":"unusedVar","endLine":6,"endColumn":46}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":1,"fixableErrorCount":0,"fixableWarningCount":0,"source":"export const prerender = false;\n\nimport type { APIRoute } from 'astro';\nimport { supabase } from '../../../lib/supabase';\n\nexport const GET: APIRoute = async ({ request, url }) => {\n try {\n // Get current user\n const { data: { user }, error: userError } = await supabase.auth.getUser();\n if (userError || !user) {\n return new Response(JSON.stringify({ error: 'Unauthorized' }), {\n status: 401,\n headers: { 'Content-Type': 'application/json' }\n });\n }\n\n // Check if user is admin\n const { data: userRole } = await supabase\n .from('user_roles')\n .select('role')\n .eq('user_id', user.id)\n .eq('role', 'admin')\n .single();\n\n if (!userRole) {\n return new Response(JSON.stringify({ error: 'Admin access required' }), {\n status: 403,\n headers: { 'Content-Type': 'application/json' }\n });\n }\n\n // Get query parameters\n const searchParams = url.searchParams;\n const page = parseInt(searchParams.get('page') || '1');\n const limit = parseInt(searchParams.get('limit') || '50');\n const status = searchParams.get('status');\n const eventId = searchParams.get('event_id');\n const email = searchParams.get('email');\n const refundStatus = searchParams.get('refund_status');\n\n // Build query\n let query = supabase\n .from('tickets')\n .select(`\n *,\n events (\n id,\n title,\n venue,\n start_time,\n organizations (\n id,\n name\n )\n ),\n ticket_types (\n id,\n name,\n price\n ),\n purchase_attempts (\n id,\n total_amount,\n purchaser_email,\n purchaser_name,\n status,\n created_at\n )\n `)\n .order('created_at', { ascending: false });\n\n // Apply filters\n if (status) {\n query = query.eq('checked_in', status === 'checked_in');\n }\n if (eventId) {\n query = query.eq('event_id', eventId);\n }\n if (email) {\n query = query.ilike('purchaser_email', `%${email}%`);\n }\n if (refundStatus) {\n query = query.eq('refund_status', refundStatus);\n }\n\n // Apply pagination\n const offset = (page - 1) * limit;\n query = query.range(offset, offset + limit - 1);\n\n const { data: tickets, error: ticketsError } = await query;\n\n if (ticketsError) {\n throw ticketsError;\n }\n\n // Get total count for pagination\n let countQuery = supabase\n .from('tickets')\n .select('*', { count: 'exact', head: true });\n\n if (status) {\n countQuery = countQuery.eq('checked_in', status === 'checked_in');\n }\n if (eventId) {\n countQuery = countQuery.eq('event_id', eventId);\n }\n if (email) {\n countQuery = countQuery.ilike('purchaser_email', `%${email}%`);\n }\n if (refundStatus) {\n countQuery = countQuery.eq('refund_status', refundStatus);\n }\n\n const { count, error: countError } = await countQuery;\n\n if (countError) {\n throw countError;\n }\n\n return new Response(JSON.stringify({\n tickets,\n pagination: {\n page,\n limit,\n total: count || 0,\n pages: Math.ceil((count || 0) / limit)\n }\n }), {\n status: 200,\n headers: { 'Content-Type': 'application/json' }\n });\n\n } catch (error) {\n\n return new Response(JSON.stringify({ \n error: 'Failed to fetch tickets',\n details: error.message \n }), {\n status: 500,\n headers: { 'Content-Type': 'application/json' }\n });\n }\n};\n\nexport const POST: APIRoute = async ({ request }) => {\n try {\n const body = await request.json();\n const { action, ticket_id, ...data } = body;\n\n // Get current user\n const { data: { user }, error: userError } = await supabase.auth.getUser();\n if (userError || !user) {\n return new Response(JSON.stringify({ error: 'Unauthorized' }), {\n status: 401,\n headers: { 'Content-Type': 'application/json' }\n });\n }\n\n // Check if user is admin\n const { data: userRole } = await supabase\n .from('user_roles')\n .select('role')\n .eq('user_id', user.id)\n .eq('role', 'admin')\n .single();\n\n if (!userRole) {\n return new Response(JSON.stringify({ error: 'Admin access required' }), {\n status: 403,\n headers: { 'Content-Type': 'application/json' }\n });\n }\n\n let result;\n \n switch (action) {\n case 'update_ticket':\n result = await supabase\n .from('tickets')\n .update(data)\n .eq('id', ticket_id)\n .select()\n .single();\n break;\n \n case 'check_in':\n result = await supabase\n .from('tickets')\n .update({ \n checked_in: true, \n scanned_at: new Date().toISOString() \n })\n .eq('id', ticket_id)\n .select()\n .single();\n break;\n \n case 'cancel_ticket':\n result = await supabase\n .from('tickets')\n .update({ \n refund_status: 'cancelled',\n refund_requested_at: new Date().toISOString(),\n refund_reason: 'Admin cancelled',\n refunded_by: user.id\n })\n .eq('id', ticket_id)\n .select()\n .single();\n break;\n \n default:\n return new Response(JSON.stringify({ error: 'Invalid action' }), {\n status: 400,\n headers: { 'Content-Type': 'application/json' }\n });\n }\n\n if (result.error) {\n throw result.error;\n }\n\n return new Response(JSON.stringify({\n success: true,\n ticket: result.data\n }), {\n status: 200,\n headers: { 'Content-Type': 'application/json' }\n });\n\n } catch (error) {\n\n return new Response(JSON.stringify({ \n error: 'Failed to manage ticket',\n details: error.message \n }), {\n status: 500,\n headers: { 'Content-Type': 'application/json' }\n });\n }\n};","usedDeprecatedRules":[]},{"filePath":"/home/tyler/apps/bct-whitelabel/src/pages/api/analytics/track.ts","messages":[{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'supabase' is defined but never used. Allowed unused vars must match /^_/u.","line":3,"column":10,"nodeType":null,"messageId":"unusedVar","endLine":3,"endColumn":18},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":57,"column":12,"nodeType":null,"messageId":"unusedVar","endLine":57,"endColumn":17}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":2,"fixableErrorCount":0,"fixableWarningCount":0,"source":"import type { APIRoute } from 'astro';\nimport { trendingAnalyticsService } from '../../../lib/analytics';\nimport { supabase } from '../../../lib/supabase';\n\nexport const POST: APIRoute = async ({ request }) => {\n try {\n const body = await request.json();\n const { eventId, metricType, sessionId, userId, locationData, metadata } = body;\n \n if (!eventId || !metricType) {\n return new Response(JSON.stringify({\n success: false,\n error: 'eventId and metricType are required'\n }), {\n status: 400,\n headers: {\n 'Content-Type': 'application/json'\n }\n });\n }\n \n // Get client information\n const clientIP = request.headers.get('x-forwarded-for') || \n request.headers.get('x-real-ip') || \n 'unknown';\n const userAgent = request.headers.get('user-agent') || 'unknown';\n const referrer = request.headers.get('referer') || undefined;\n \n // Track the event\n await trendingAnalyticsService.trackEvent({\n eventId,\n metricType,\n sessionId,\n userId,\n ipAddress: clientIP,\n userAgent,\n referrer,\n locationData,\n metadata\n });\n \n // Update popularity score if this is a significant event\n if (metricType === 'page_view' || metricType === 'checkout_complete') {\n // Don't await this to avoid slowing down the response\n trendingAnalyticsService.updateEventPopularityScore(eventId);\n }\n \n return new Response(JSON.stringify({\n success: true,\n message: 'Event tracked successfully'\n }), {\n status: 200,\n headers: {\n 'Content-Type': 'application/json'\n }\n });\n } catch (error) {\n\n return new Response(JSON.stringify({\n success: false,\n error: 'Failed to track event'\n }), {\n status: 500,\n headers: {\n 'Content-Type': 'application/json'\n }\n });\n }\n};","usedDeprecatedRules":[]},{"filePath":"/home/tyler/apps/bct-whitelabel/src/pages/api/auth/login.ts","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/home/tyler/apps/bct-whitelabel/src/pages/api/auth/logout.ts","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/home/tyler/apps/bct-whitelabel/src/pages/api/auth/session.ts","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/home/tyler/apps/bct-whitelabel/src/pages/api/chat.ts","messages":[{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":101,"column":12,"nodeType":null,"messageId":"unusedVar","endLine":101,"endColumn":17}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":1,"fixableErrorCount":0,"fixableWarningCount":0,"source":"import type { APIRoute } from 'astro';\n\nconst OPENAI_API_KEY = process.env.OPENAI_API_KEY;\n\n// Fallback responses when OpenAI is not available\nconst getFallbackResponse = (message: string): string => {\n const lowerMessage = message.toLowerCase();\n \n if (lowerMessage.includes('create') && lowerMessage.includes('event')) {\n return \"To create your first event:\\n\\n1. Complete your account setup\\n2. Connect your Stripe account\\n3. Click 'Create Event' in your dashboard\\n4. Fill in event details and ticket types\\n5. Publish your event\\n\\nFor detailed steps, check our Getting Started guide at /docs/getting-started/first-event\";\n }\n \n if (lowerMessage.includes('stripe') || lowerMessage.includes('payment')) {\n return \"To set up payments:\\n\\n1. Go to Settings → Payment Settings\\n2. Click 'Connect Stripe Account'\\n3. Complete the verification process\\n4. Start accepting payments!\\n\\nOur platform fee is 2.5% + $1.50 per ticket. For detailed setup instructions, visit /docs/getting-started/stripe-connect\";\n }\n \n if (lowerMessage.includes('scan') || lowerMessage.includes('qr')) {\n return \"QR code scanning is simple:\\n\\n1. Go to portal.blackcanyontickets.com/scan on any mobile device\\n2. Log in with your organizer account\\n3. Select your event\\n4. Allow camera access\\n5. Start scanning tickets!\\n\\nNo apps required - works in any browser. Check out our scanning guide at /docs/scanning/setup\";\n }\n \n if (lowerMessage.includes('fee') || lowerMessage.includes('cost')) {\n return \"Our transparent pricing is 2.5% + $1.50 per ticket.\\n\\nThis includes:\\n• Payment processing through Stripe\\n• QR code generation and scanning\\n• Event management tools\\n• Customer support\\n• Real-time analytics\\n\\nFees are automatically deducted before payouts.\";\n }\n \n if (lowerMessage.includes('payout') || lowerMessage.includes('paid')) {\n return \"Payments are processed automatically through Stripe Connect:\\n\\n• Automatic processing after each sale\\n• Platform fees deducted automatically\\n• Typical payout time: 2-7 business days\\n• Direct deposit to your bank account\\n• Real-time tracking in your dashboard\\n\\nView detailed payout info in your Stripe dashboard.\";\n }\n \n return \"I'm here to help with Black Canyon Tickets! You can ask me about:\\n\\n• Creating and managing events\\n• Setting up Stripe payments\\n• QR code scanning\\n• Platform fees and payouts\\n• Technical troubleshooting\\n\\nFor detailed documentation, visit /docs or email support@blackcanyontickets.com for personal assistance.\";\n};\n\nconst SYSTEM_PROMPT = `You are a helpful customer support assistant for Black Canyon Tickets, a premium ticketing platform for upscale venues. \n\nKey information about our platform:\n- We serve upscale venues and premium events\n- Features include QR code scanning, Stripe payment processing, event management\n- No mobile apps required - everything works in web browsers\n- Platform fee is 2.5% + $1.50 per ticket\n- Automatic payouts through Stripe Connect\n- Events are accessed at portal.blackcanyontickets.com/e/[event-slug]\n- QR scanning is available at /scan\n- Mobile-friendly design for all features\n\nCommon topics:\n- Account setup and verification\n- Creating events and ticket types\n- Payment processing and payouts\n- QR code ticket scanning\n- Embedding events on websites\n- Troubleshooting checkout issues\n\nBe helpful, professional, and concise. If you don't know something specific, direct them to support@blackcanyontickets.com.\nKeep responses under 200 words unless asked for detailed explanations.`;\n\nexport const POST: APIRoute = async ({ request }) => {\n try {\n const { message } = await request.json();\n\n if (!OPENAI_API_KEY) {\n // Use fallback responses when OpenAI is not configured\n const fallbackResponse = getFallbackResponse(message);\n return new Response(JSON.stringify({ \n message: fallbackResponse\n }), {\n status: 200,\n headers: { 'Content-Type': 'application/json' }\n });\n }\n\n const response = await fetch('https://api.openai.com/v1/chat/completions', {\n method: 'POST',\n headers: {\n 'Authorization': `Bearer ${OPENAI_API_KEY}`,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({\n model: 'gpt-3.5-turbo',\n messages: [\n { role: 'system', content: SYSTEM_PROMPT },\n { role: 'user', content: message }\n ],\n max_tokens: 300,\n temperature: 0.7,\n }),\n });\n\n if (!response.ok) {\n throw new Error(`OpenAI API error: ${response.status}`);\n }\n\n const data = await response.json();\n const assistantMessage = data.choices[0].message.content;\n\n return new Response(JSON.stringify({ \n message: assistantMessage \n }), {\n status: 200,\n headers: { 'Content-Type': 'application/json' }\n });\n\n } catch (error) {\n\n return new Response(JSON.stringify({ \n error: 'Failed to process chat message' \n }), {\n status: 500,\n headers: { 'Content-Type': 'application/json' }\n });\n }\n};","usedDeprecatedRules":[]},{"filePath":"/home/tyler/apps/bct-whitelabel/src/pages/api/checkin-barcode.ts","messages":[{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":112,"column":12,"nodeType":null,"messageId":"unusedVar","endLine":112,"endColumn":17}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":1,"fixableErrorCount":0,"fixableWarningCount":0,"source":"import type { APIRoute } from 'astro';\nimport { supabase } from '../../lib/supabase';\n\nexport const POST: APIRoute = async ({ request }) => {\n try {\n const { barcode_number, event_id, scanned_by } = await request.json();\n\n if (!barcode_number || !event_id || !scanned_by) {\n return new Response(JSON.stringify({ \n success: false, \n error: 'Missing required parameters' \n }), { status: 400 });\n }\n\n // Log the scan attempt\n const logScanAttempt = async (result: string, errorMessage?: string) => {\n await supabase.from('scan_attempts').insert({\n barcode_number,\n event_id,\n scanned_by,\n result,\n error_message: errorMessage\n });\n };\n\n // 1. Lookup ticket by barcode\n const { data: ticket, error: ticketError } = await supabase\n .from('printed_tickets')\n .select(`\n *,\n ticket_types (\n name,\n price\n ),\n events (\n title,\n organization_id\n )\n `)\n .eq('barcode_number', barcode_number)\n .single();\n\n // 2. Check if barcode exists\n if (ticketError || !ticket) {\n await logScanAttempt('INVALID_BARCODE', 'Barcode not found');\n return new Response(JSON.stringify({ \n success: false, \n error: 'Invalid barcode' \n }), { status: 404 });\n }\n\n // 3. Check if event matches\n if (ticket.event_id !== event_id) {\n await logScanAttempt('WRONG_EVENT', 'Barcode not valid for this event');\n return new Response(JSON.stringify({ \n success: false, \n error: 'Barcode not valid for this event' \n }), { status: 400 });\n }\n\n // 4. Check if already used\n if (ticket.status === 'used') {\n await logScanAttempt('ALREADY_USED', `Ticket already used at ${ticket.checked_in_at}`);\n return new Response(JSON.stringify({ \n success: false, \n error: `Ticket already used at ${new Date(ticket.checked_in_at).toLocaleString()}` \n }), { status: 400 });\n }\n\n // 5. Check if status is valid\n if (ticket.status !== 'valid') {\n await logScanAttempt('NOT_VALID', 'Ticket is not valid');\n return new Response(JSON.stringify({ \n success: false, \n error: 'Ticket is not valid' \n }), { status: 400 });\n }\n\n // 6. Mark as used\n const { error: updateError } = await supabase\n .from('printed_tickets')\n .update({ \n status: 'used',\n checked_in_at: new Date().toISOString(),\n scanned_by: scanned_by\n })\n .eq('id', ticket.id);\n\n if (updateError) {\n await logScanAttempt('ERROR', 'Failed to update ticket status');\n return new Response(JSON.stringify({ \n success: false, \n error: 'Failed to update ticket status' \n }), { status: 500 });\n }\n\n // 7. Log successful scan\n await logScanAttempt('SUCCESS', 'Check-in successful');\n\n return new Response(JSON.stringify({ \n success: true, \n message: 'Check-in successful',\n ticket: {\n barcode_number: ticket.barcode_number,\n ticket_type: ticket.ticket_types?.name,\n price: ticket.ticket_types?.price,\n event: ticket.events?.title,\n checked_in_at: new Date().toISOString()\n }\n }), { status: 200 });\n\n } catch (error) {\n\n return new Response(JSON.stringify({ \n success: false, \n error: 'Internal server error' \n }), { status: 500 });\n }\n};","usedDeprecatedRules":[]},{"filePath":"/home/tyler/apps/bct-whitelabel/src/pages/api/codereadr/scan.ts","messages":[{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":54,"column":14,"nodeType":null,"messageId":"unusedVar","endLine":54,"endColumn":19},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":198,"column":12,"nodeType":null,"messageId":"unusedVar","endLine":198,"endColumn":17},{"ruleId":"no-empty","severity":1,"message":"Empty block statement.","line":198,"column":19,"nodeType":"BlockStatement","messageId":"unexpected","endLine":200,"endColumn":4,"suggestions":[{"messageId":"suggestComment","data":{"type":"block"},"fix":{"range":[5817,5821],"text":" /* empty */ "},"desc":"Add comment inside empty block statement."}]}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":3,"fixableErrorCount":0,"fixableWarningCount":0,"source":"import type { APIRoute } from 'astro';\nimport { supabase } from '../../../lib/supabase';\nimport { CodereadrApi } from '../../../lib/codereadr-api';\n\nexport const POST: APIRoute = async ({ request }) => {\n try {\n const { ticketUuid, eventId, scannedBy } = await request.json();\n\n if (!ticketUuid || !eventId || !scannedBy) {\n return new Response(JSON.stringify({ \n success: false, \n error: 'Missing required parameters' \n }), { status: 400 });\n }\n\n // Get CodeREADr configuration for this event\n const { data: config, error: configError } = await supabase\n .from('codereadr_configs')\n .select('*')\n .eq('event_id', eventId)\n .eq('status', 'active')\n .single();\n\n if (configError || !config) {\n return new Response(JSON.stringify({ \n success: false, \n error: 'CodeREADr not configured for this event' \n }), { status: 404 });\n }\n\n // Initialize CodeREADr API\n const codereadrApi = new CodereadrApi();\n\n // Validate barcode against CodeREADr database\n const validationResult = await codereadrApi.validateBarcode(\n config.database_id,\n ticketUuid\n );\n\n if (!validationResult || !validationResult.valid) {\n // Log failed scan attempt\n await logScanAttempt(eventId, ticketUuid, scannedBy, 'INVALID_BARCODE', 'Barcode not found in CodeREADr');\n \n return new Response(JSON.stringify({ \n success: false, \n error: 'Invalid barcode' \n }), { status: 404 });\n }\n\n // Parse the response data\n let ticketData;\n try {\n ticketData = JSON.parse(validationResult.response);\n } catch (error) {\n ticketData = { message: validationResult.response };\n }\n\n // Simulate scan in CodeREADr\n const scanResult = await codereadrApi.simulateScan(\n config.service_id,\n config.user_id,\n ticketUuid\n );\n\n // Check if ticket exists in our database\n const { data: ticket, error: ticketError } = await supabase\n .from('tickets')\n .select(`\n *,\n events (\n title,\n venue,\n start_time,\n organization_id\n ),\n ticket_types (\n name,\n price\n )\n `)\n .eq('uuid', ticketUuid)\n .single();\n\n if (ticketError || !ticket) {\n await logScanAttempt(eventId, ticketUuid, scannedBy, 'TICKET_NOT_FOUND', 'Ticket not found in local database');\n \n return new Response(JSON.stringify({ \n success: false, \n error: 'Ticket not found in local database' \n }), { status: 404 });\n }\n\n // Check if ticket is already checked in\n if (ticket.checked_in) {\n await logScanAttempt(eventId, ticketUuid, scannedBy, 'ALREADY_CHECKED_IN', 'Ticket already checked in');\n \n return new Response(JSON.stringify({ \n success: false, \n error: `Ticket already checked in at ${new Date(ticket.scanned_at).toLocaleString()}`,\n ticket_data: {\n purchaser_name: ticket.purchaser_name,\n purchaser_email: ticket.purchaser_email,\n event_title: ticket.events?.title,\n checked_in_at: ticket.scanned_at\n }\n }), { status: 400 });\n }\n\n // Check if ticket belongs to this event\n if (ticket.event_id !== eventId) {\n await logScanAttempt(eventId, ticketUuid, scannedBy, 'WRONG_EVENT', 'Ticket not valid for this event');\n \n return new Response(JSON.stringify({ \n success: false, \n error: 'Ticket not valid for this event' \n }), { status: 400 });\n }\n\n // Update ticket status in our database\n const { error: updateError } = await supabase\n .from('tickets')\n .update({\n checked_in: true,\n scanned_at: new Date().toISOString(),\n scanned_by: scannedBy,\n scan_method: 'codereadr'\n })\n .eq('uuid', ticketUuid);\n\n if (updateError) {\n await logScanAttempt(eventId, ticketUuid, scannedBy, 'UPDATE_FAILED', 'Failed to update ticket status');\n \n return new Response(JSON.stringify({ \n success: false, \n error: 'Failed to update ticket status' \n }), { status: 500 });\n }\n\n // Log successful scan\n await logScanAttempt(eventId, ticketUuid, scannedBy, 'SUCCESS', 'Ticket successfully checked in via CodeREADr');\n\n // Return success response\n return new Response(JSON.stringify({\n success: true,\n message: 'Ticket successfully checked in via CodeREADr',\n scan_result: scanResult,\n ticket_data: {\n uuid: ticket.uuid,\n purchaser_name: ticket.purchaser_name,\n purchaser_email: ticket.purchaser_email,\n ticket_type: ticket.ticket_types?.name || 'General',\n price: ticket.ticket_types?.price || 0,\n event_title: ticket.events?.title,\n checked_in_at: new Date().toISOString()\n },\n codereadr_data: ticketData\n }), { status: 200 });\n\n } catch (error) {\n\n // Log error\n if (request.body) {\n const body = await request.json();\n await logScanAttempt(\n body.eventId, \n body.ticketUuid, \n body.scannedBy, \n 'ERROR', \n error.message || 'Unknown error'\n );\n }\n \n return new Response(JSON.stringify({ \n success: false, \n error: error.message || 'Internal server error' \n }), { status: 500 });\n }\n};\n\n// Helper function to log scan attempts\nasync function logScanAttempt(\n eventId: string,\n ticketUuid: string,\n scannedBy: string,\n result: string,\n errorMessage?: string\n) {\n try {\n await supabase.from('scan_attempts').insert({\n event_id: eventId,\n ticket_uuid: ticketUuid,\n scanned_by: scannedBy,\n result,\n error_message: errorMessage,\n scan_method: 'codereadr',\n timestamp: new Date().toISOString()\n });\n } catch (error) {\n\n }\n}","usedDeprecatedRules":[]},{"filePath":"/home/tyler/apps/bct-whitelabel/src/pages/api/codereadr/setup-guide.ts","messages":[{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'request' is defined but never used. Allowed unused args must match /^_/u.","line":3,"column":39,"nodeType":null,"messageId":"unusedVar","endLine":3,"endColumn":46}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":1,"fixableErrorCount":0,"fixableWarningCount":0,"source":"import type { APIRoute } from 'astro';\n\nexport const GET: APIRoute = async ({ request }) => {\n const setupGuide = {\n title: \"CodeREADr Integration Setup Guide\",\n description: \"Complete guide to setting up CodeREADr as a backup scanner for Black Canyon Tickets\",\n \n overview: {\n purpose: \"CodeREADr serves as a backup scanning system when the primary QR scanner fails\",\n benefits: [\n \"Redundancy: Ensures scanning works even if primary system fails\",\n \"Mobile apps: CodeREADr provides dedicated mobile scanning apps\",\n \"Offline capability: CodeREADr apps can work offline and sync later\",\n \"Real-time reporting: Advanced analytics and reporting features\"\n ]\n },\n\n prerequisites: {\n codereadr_account: \"Active CodeREADr account with API access\",\n api_key: \"CodeREADr API key (currently: 3bcb2250e2c9cf4adf4e807f912f907e)\",\n database_migration: \"Run the CodeREADr database migration\",\n event_setup: \"Event must be created in BCT system first\"\n },\n\n setup_steps: [\n {\n step: 1,\n title: \"Apply Database Migration\",\n description: \"Run the CodeREADr integration migration\",\n command: \"supabase migration up 20250109_codereadr_integration.sql\",\n notes: \"This creates the necessary tables for CodeREADr integration\"\n },\n {\n step: 2,\n title: \"Setup CodeREADr for Event\",\n description: \"Initialize CodeREADr configuration for an event\",\n endpoint: \"POST /api/codereadr/setup\",\n request_body: {\n eventId: \"uuid-of-event\",\n organizationId: \"uuid-of-organization\"\n },\n response: {\n success: true,\n message: \"CodeREADr integration setup successfully\",\n config: {\n database_id: \"codereadr-database-id\",\n service_id: \"codereadr-service-id\",\n user_id: \"codereadr-user-id\"\n }\n }\n },\n {\n step: 3,\n title: \"Test Backup Scanning\",\n description: \"Test the backup scanning functionality\",\n endpoint: \"POST /api/codereadr/scan\",\n request_body: {\n ticketUuid: \"ticket-uuid-to-scan\",\n eventId: \"uuid-of-event\",\n scannedBy: \"user-id-scanning\"\n }\n },\n {\n step: 4,\n title: \"Configure Webhooks (Optional)\",\n description: \"Set up real-time webhook notifications\",\n webhook_url: \"https://your-domain.com/api/codereadr/webhook\",\n notes: \"Configure in CodeREADr dashboard to receive real-time scan notifications\"\n }\n ],\n\n usage: {\n automatic_backup: {\n description: \"Backup scanning happens automatically when primary scanner fails\",\n trigger: \"When checkInTicket() fails, it automatically tries CodeREADr\",\n indicator: \"Shows yellow 'Trying backup scanner...' notification\"\n },\n manual_backup: {\n description: \"Users can manually trigger backup scanning\",\n button: \"Yellow 'Backup' button in manual entry section\",\n usage: \"Enter ticket UUID and click 'Backup' button\"\n },\n sync_scans: {\n description: \"Sync scans from CodeREADr to local database\",\n endpoint: \"POST /api/codereadr/sync\",\n frequency: \"Can be run manually or scheduled\"\n }\n },\n\n api_endpoints: {\n setup: {\n method: \"POST\",\n path: \"/api/codereadr/setup\",\n description: \"Initialize CodeREADr for an event\",\n parameters: [\"eventId\", \"organizationId\"]\n },\n scan: {\n method: \"POST\", \n path: \"/api/codereadr/scan\",\n description: \"Scan a ticket using CodeREADr\",\n parameters: [\"ticketUuid\", \"eventId\", \"scannedBy\"]\n },\n sync: {\n method: \"POST\",\n path: \"/api/codereadr/sync\", \n description: \"Sync scans from CodeREADr\",\n parameters: [\"eventId\", \"organizationId\"]\n },\n status: {\n method: \"GET\",\n path: \"/api/codereadr/sync?eventId=&organizationId=\",\n description: \"Get CodeREADr integration status\",\n parameters: [\"eventId\", \"organizationId\"]\n },\n webhook: {\n method: \"POST\",\n path: \"/api/codereadr/webhook\",\n description: \"Handle real-time scan notifications\",\n parameters: [\"scan_id\", \"service_id\", \"value\", \"timestamp\"]\n }\n },\n\n database_schema: {\n codereadr_configs: {\n description: \"Stores CodeREADr configuration per event\",\n key_fields: [\"event_id\", \"database_id\", \"service_id\", \"user_id\", \"status\"]\n },\n codereadr_scans: {\n description: \"Stores synchronized scans from CodeREADr\",\n key_fields: [\"codereadr_scan_id\", \"event_id\", \"ticket_uuid\", \"scan_timestamp\"]\n },\n tickets: {\n new_column: \"scan_method\",\n description: \"Tracks how ticket was scanned (qr, codereadr, manual, api)\"\n }\n },\n\n troubleshooting: {\n common_issues: [\n {\n issue: \"CodeREADr not configured for event\",\n solution: \"Run POST /api/codereadr/setup first\"\n },\n {\n issue: \"Backup scanning fails\",\n solution: \"Check API key, verify event configuration, check network connectivity\"\n },\n {\n issue: \"Duplicate scans\",\n solution: \"Sync process handles duplicates automatically\"\n },\n {\n issue: \"Webhook not receiving data\",\n solution: \"Verify webhook URL configuration in CodeREADr dashboard\"\n }\n ]\n },\n\n security_considerations: [\n \"API key is stored in application code - consider environment variables\",\n \"Webhook endpoint should validate requests\",\n \"Database access controlled by RLS policies\",\n \"Sync operations log all actions for audit trail\"\n ],\n\n next_steps: [\n \"Test the integration with a sample event\",\n \"Configure webhook notifications for real-time updates\",\n \"Set up automated sync scheduling\",\n \"Train staff on backup scanning procedures\",\n \"Monitor scan success rates and system performance\"\n ]\n };\n\n return new Response(JSON.stringify(setupGuide, null, 2), {\n status: 200,\n headers: {\n 'Content-Type': 'application/json'\n }\n });\n};","usedDeprecatedRules":[]},{"filePath":"/home/tyler/apps/bct-whitelabel/src/pages/api/codereadr/setup.ts","messages":[{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":44,"column":14,"nodeType":null,"messageId":"unusedVar","endLine":44,"endColumn":19},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":108,"column":14,"nodeType":null,"messageId":"unusedVar","endLine":108,"endColumn":19}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":2,"fixableErrorCount":0,"fixableWarningCount":0,"source":"import type { APIRoute } from 'astro';\nimport { supabase } from '../../../lib/supabase';\nimport { CodereadrApi } from '../../../lib/codereadr-api';\n\nexport const POST: APIRoute = async ({ request }) => {\n try {\n const { eventId, organizationId } = await request.json();\n\n if (!eventId || !organizationId) {\n return new Response(JSON.stringify({ \n success: false, \n error: 'Missing required parameters' \n }), { status: 400 });\n }\n\n // Get event details\n const { data: event, error: eventError } = await supabase\n .from('events')\n .select('*')\n .eq('id', eventId)\n .eq('organization_id', organizationId)\n .single();\n\n if (eventError || !event) {\n return new Response(JSON.stringify({ \n success: false, \n error: 'Event not found' \n }), { status: 404 });\n }\n\n // Initialize CodeREADr API\n const codereadrApi = new CodereadrApi();\n\n // Create or get database for this event\n const dbName = `BCT-${event.title.replace(/[^a-zA-Z0-9]/g, '-')}-${eventId}`;\n let database;\n \n try {\n // Try to create database\n database = await codereadrApi.createDatabase(\n dbName,\n `Database for ${event.title} event`\n );\n } catch (error) {\n // If database exists, get it\n const databases = await codereadrApi.getDatabases();\n database = databases.find(db => db.name === dbName);\n \n if (!database) {\n throw new Error('Failed to create or find database');\n }\n }\n\n // Get all tickets for this event and add them to the database\n const { data: tickets, error: ticketsError } = await supabase\n .from('tickets')\n .select(`\n *,\n ticket_types (\n name,\n price\n )\n `)\n .eq('event_id', eventId);\n\n if (ticketsError) {\n return new Response(JSON.stringify({ \n success: false, \n error: 'Failed to fetch tickets' \n }), { status: 500 });\n }\n\n // Add tickets to CodeREADr database\n const ticketPromises = tickets.map(ticket => {\n const response = JSON.stringify({\n valid: true,\n ticket_id: ticket.id,\n purchaser_name: ticket.purchaser_name,\n purchaser_email: ticket.purchaser_email,\n ticket_type: ticket.ticket_types?.name || 'General',\n price: ticket.ticket_types?.price || 0,\n event_title: event.title,\n message: `Valid ticket for ${event.title}`,\n action: 'checkin'\n });\n\n return codereadrApi.addDatabaseRecord(\n database.id,\n ticket.uuid,\n response\n );\n });\n\n await Promise.all(ticketPromises);\n\n // Create or get user for this organization\n const userName = `bct-org-${organizationId}`;\n const userEmail = `org-${organizationId}@blackcanyontickets.com`;\n let user;\n\n try {\n // Try to create user\n user = await codereadrApi.createUser(\n userName,\n userEmail,\n 'BCT-Scanner-2024!' // Default password\n );\n } catch (error) {\n // If user exists, get it\n const users = await codereadrApi.getUsers();\n user = users.find(u => u.username === userName);\n \n if (!user) {\n throw new Error('Failed to create or find user');\n }\n }\n\n // Create service for this event\n const serviceName = `BCT-Scanner-${event.title}-${eventId}`;\n const service = await codereadrApi.createService(\n serviceName,\n database.id,\n user.id\n );\n\n // Store CodeREADr configuration in our database\n const { error: configError } = await supabase\n .from('codereadr_configs')\n .upsert({\n event_id: eventId,\n organization_id: organizationId,\n database_id: database.id,\n service_id: service.id,\n user_id: user.id,\n database_name: dbName,\n service_name: serviceName,\n user_name: userName,\n status: 'active',\n created_at: new Date().toISOString(),\n updated_at: new Date().toISOString()\n });\n\n if (configError) {\n\n // Continue anyway, as the external setup was successful\n }\n\n return new Response(JSON.stringify({\n success: true,\n message: 'CodeREADr integration setup successfully',\n config: {\n database_id: database.id,\n service_id: service.id,\n user_id: user.id,\n database_name: dbName,\n service_name: serviceName,\n user_name: userName\n }\n }), { status: 200 });\n\n } catch (error) {\n\n return new Response(JSON.stringify({ \n success: false, \n error: error.message || 'Failed to setup CodeREADr integration' \n }), { status: 500 });\n }\n};","usedDeprecatedRules":[]},{"filePath":"/home/tyler/apps/bct-whitelabel/src/pages/api/codereadr/sync.ts","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/home/tyler/apps/bct-whitelabel/src/pages/api/codereadr/webhook.ts","messages":[{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'signature' is assigned a value but never used. Allowed unused vars must match /^_/u.","line":14,"column":11,"nodeType":null,"messageId":"unusedVar","endLine":14,"endColumn":20},{"ruleId":"no-empty","severity":1,"message":"Empty block statement.","line":117,"column":24,"nodeType":"BlockStatement","messageId":"unexpected","endLine":119,"endColumn":8,"suggestions":[{"messageId":"suggestComment","data":{"type":"block"},"fix":{"range":[3128,3136],"text":" /* empty */ "},"desc":"Add comment inside empty block statement."}]},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":169,"column":14,"nodeType":null,"messageId":"unusedVar","endLine":169,"endColumn":19},{"ruleId":"no-empty","severity":1,"message":"Empty block statement.","line":169,"column":21,"nodeType":"BlockStatement","messageId":"unexpected","endLine":171,"endColumn":6,"suggestions":[{"messageId":"suggestComment","data":{"type":"block"},"fix":{"range":[4602,4608],"text":" /* empty */ "},"desc":"Add comment inside empty block statement."}]}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":4,"fixableErrorCount":0,"fixableWarningCount":0,"source":"import type { APIRoute } from 'astro';\nimport { supabase } from '../../../lib/supabase';\n\n/**\n * CodeREADr Webhook Handler\n * Handles real-time scan notifications from CodeREADr\n */\nexport const POST: APIRoute = async ({ request }) => {\n try {\n const webhookData = await request.json();\n \n // Validate webhook signature if needed\n // CodeREADr may send a signature header for security\n const signature = request.headers.get('x-codereadr-signature');\n \n // Extract scan data from webhook\n const {\n scan_id,\n service_id,\n user_id,\n value: ticketUuid,\n timestamp,\n response,\n device_id,\n location\n } = webhookData;\n\n if (!scan_id || !service_id || !ticketUuid) {\n return new Response(JSON.stringify({ \n success: false, \n error: 'Invalid webhook data' \n }), { status: 400 });\n }\n\n // Find the event based on service_id\n const { data: config, error: configError } = await supabase\n .from('codereadr_configs')\n .select('*')\n .eq('service_id', service_id)\n .eq('status', 'active')\n .single();\n\n if (configError || !config) {\n\n return new Response(JSON.stringify({ \n success: false, \n error: 'Service configuration not found' \n }), { status: 404 });\n }\n\n // Check if we've already processed this scan\n const { data: existingScan } = await supabase\n .from('codereadr_scans')\n .select('id')\n .eq('codereadr_scan_id', scan_id)\n .single();\n\n if (existingScan) {\n return new Response(JSON.stringify({ \n success: true, \n message: 'Scan already processed' \n }), { status: 200 });\n }\n\n // Find the corresponding ticket\n const { data: ticket, error: ticketError } = await supabase\n .from('tickets')\n .select(`\n *,\n events (\n title,\n organization_id\n )\n `)\n .eq('uuid', ticketUuid)\n .eq('event_id', config.event_id)\n .single();\n\n if (ticketError || !ticket) {\n\n // Record the scan attempt anyway\n await supabase\n .from('codereadr_scans')\n .insert({\n codereadr_scan_id: scan_id,\n event_id: config.event_id,\n service_id: service_id,\n ticket_uuid: ticketUuid,\n scan_timestamp: timestamp,\n response: response,\n device_id: device_id,\n location: location,\n synced_at: new Date().toISOString(),\n webhook_processed: true,\n error_message: 'Ticket not found'\n });\n\n return new Response(JSON.stringify({ \n success: false, \n error: 'Ticket not found' \n }), { status: 404 });\n }\n\n // Update ticket status if not already checked in\n let ticketUpdated = false;\n if (!ticket.checked_in) {\n const { error: updateError } = await supabase\n .from('tickets')\n .update({\n checked_in: true,\n scanned_at: timestamp,\n scan_method: 'codereadr',\n scanned_by: user_id || 'codereadr-webhook'\n })\n .eq('uuid', ticketUuid);\n\n if (updateError) {\n\n } else {\n ticketUpdated = true;\n }\n }\n\n // Record the scan in our database\n await supabase\n .from('codereadr_scans')\n .insert({\n codereadr_scan_id: scan_id,\n event_id: config.event_id,\n service_id: service_id,\n ticket_uuid: ticketUuid,\n scan_timestamp: timestamp,\n response: response,\n device_id: device_id,\n location: location,\n synced_at: new Date().toISOString(),\n webhook_processed: true,\n ticket_updated: ticketUpdated\n });\n\n // Log the scan attempt\n await supabase\n .from('scan_attempts')\n .insert({\n event_id: config.event_id,\n ticket_uuid: ticketUuid,\n scanned_by: user_id || 'codereadr-webhook',\n result: ticketUpdated ? 'SUCCESS' : 'ALREADY_CHECKED_IN',\n error_message: ticketUpdated ? null : 'Ticket already checked in',\n scan_method: 'codereadr',\n timestamp: timestamp\n });\n\n // Send real-time notification to connected clients\n try {\n await supabase.channel('scan-updates')\n .send({\n type: 'broadcast',\n event: 'ticket-scanned',\n payload: {\n event_id: config.event_id,\n ticket_uuid: ticketUuid,\n scan_timestamp: timestamp,\n purchaser_name: ticket.purchaser_name,\n scan_method: 'codereadr',\n success: ticketUpdated\n }\n });\n } catch (error) {\n\n }\n\n return new Response(JSON.stringify({\n success: true,\n message: 'Webhook processed successfully',\n scan_processed: {\n scan_id: scan_id,\n ticket_uuid: ticketUuid,\n event_id: config.event_id,\n ticket_updated: ticketUpdated,\n timestamp: timestamp\n }\n }), { status: 200 });\n\n } catch (error) {\n\n return new Response(JSON.stringify({ \n success: false, \n error: error.message || 'Webhook processing failed' \n }), { status: 500 });\n }\n};","usedDeprecatedRules":[]},{"filePath":"/home/tyler/apps/bct-whitelabel/src/pages/api/cron/update-popularity.ts","messages":[{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":21,"column":12,"nodeType":null,"messageId":"unusedVar","endLine":21,"endColumn":17}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":1,"fixableErrorCount":0,"fixableWarningCount":0,"source":"import type { APIRoute } from 'astro';\nimport { trendingAnalyticsService } from '../../../lib/analytics';\n\nexport const GET: APIRoute = async () => {\n try {\n // This endpoint should be called by a cron job or background service\n // It updates popularity scores for all events\n\n await trendingAnalyticsService.batchUpdatePopularityScores();\n\n return new Response(JSON.stringify({\n success: true,\n message: 'Popularity scores updated successfully',\n timestamp: new Date().toISOString()\n }), {\n status: 200,\n headers: {\n 'Content-Type': 'application/json'\n }\n });\n } catch (error) {\n\n return new Response(JSON.stringify({\n success: false,\n error: 'Failed to update popularity scores',\n timestamp: new Date().toISOString()\n }), {\n status: 500,\n headers: {\n 'Content-Type': 'application/json'\n }\n });\n }\n};","usedDeprecatedRules":[]},{"filePath":"/home/tyler/apps/bct-whitelabel/src/pages/api/custom-pages/[id].ts","messages":[{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":74,"column":12,"nodeType":null,"messageId":"unusedVar","endLine":74,"endColumn":17},{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":138,"column":23,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":138,"endColumn":26,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[3316,3319],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[3316,3319],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":171,"column":12,"nodeType":null,"messageId":"unusedVar","endLine":171,"endColumn":17},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":240,"column":12,"nodeType":null,"messageId":"unusedVar","endLine":240,"endColumn":17}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":4,"fixableErrorCount":0,"fixableWarningCount":0,"source":"import type { APIRoute } from 'astro';\nimport { supabase } from '../../../lib/supabase';\nimport { createClient } from '@supabase/supabase-js';\n\nexport const prerender = false;\n\nexport const GET: APIRoute = async ({ params, request }) => {\n const { id } = params;\n\n if (!id) {\n return new Response(JSON.stringify({ \n success: false, \n error: 'Page ID is required' \n }), { status: 400 });\n }\n\n // Authentication\n const authHeader = request.headers.get('authorization');\n if (!authHeader) {\n return new Response(JSON.stringify({ \n success: false, \n error: 'Authorization header required' \n }), { status: 401 });\n }\n\n const token = authHeader.replace('Bearer ', '');\n const { data: { user }, error: authError } = await supabase.auth.getUser(token);\n\n if (authError || !user) {\n return new Response(JSON.stringify({ \n success: false, \n error: 'Invalid authentication token' \n }), { status: 401 });\n }\n\n // Create authenticated Supabase client\n const authenticatedSupabase = createClient(\n import.meta.env.PUBLIC_SUPABASE_URL,\n import.meta.env.PUBLIC_SUPABASE_ANON_KEY,\n {\n global: {\n headers: {\n Authorization: `Bearer ${token}`\n }\n }\n }\n );\n\n try {\n const { data: page, error } = await authenticatedSupabase\n .from('custom_sales_pages')\n .select(`\n *,\n template:custom_page_templates(*),\n event:events(title, organizations(name))\n `)\n .eq('id', id)\n .single();\n\n if (error) {\n\n return new Response(JSON.stringify({ \n success: false, \n error: 'Page not found' \n }), { status: 404 });\n }\n\n return new Response(JSON.stringify({ \n success: true, \n page,\n pageData: page.page_data || page.template?.page_data || {}\n }), { status: 200 });\n\n } catch (error) {\n\n return new Response(JSON.stringify({ \n success: false, \n error: 'Internal server error' \n }), { status: 500 });\n }\n};\n\nexport const PUT: APIRoute = async ({ params, request }) => {\n const { id } = params;\n\n if (!id) {\n return new Response(JSON.stringify({ \n success: false, \n error: 'Page ID is required' \n }), { status: 400 });\n }\n\n // Authentication\n const authHeader = request.headers.get('authorization');\n if (!authHeader) {\n return new Response(JSON.stringify({ \n success: false, \n error: 'Authorization header required' \n }), { status: 401 });\n }\n\n const token = authHeader.replace('Bearer ', '');\n const { data: { user }, error: authError } = await supabase.auth.getUser(token);\n\n if (authError || !user) {\n return new Response(JSON.stringify({ \n success: false, \n error: 'Invalid authentication token' \n }), { status: 401 });\n }\n\n // Create authenticated Supabase client\n const authenticatedSupabase = createClient(\n import.meta.env.PUBLIC_SUPABASE_URL,\n import.meta.env.PUBLIC_SUPABASE_ANON_KEY,\n {\n global: {\n headers: {\n Authorization: `Bearer ${token}`\n }\n }\n }\n );\n\n try {\n const body = await request.json();\n const { \n custom_slug, \n meta_title, \n meta_description, \n page_data, \n custom_css, \n is_active, \n is_default,\n updated_by \n } = body;\n\n const updateData: any = { \n updated_at: new Date().toISOString() \n };\n\n if (custom_slug !== undefined) updateData.custom_slug = custom_slug;\n if (meta_title !== undefined) updateData.meta_title = meta_title;\n if (meta_description !== undefined) updateData.meta_description = meta_description;\n if (page_data !== undefined) updateData.page_data = page_data;\n if (custom_css !== undefined) updateData.custom_css = custom_css;\n if (is_active !== undefined) updateData.is_active = is_active;\n if (is_default !== undefined) updateData.is_default = is_default;\n if (updated_by) updateData.updated_by = updated_by;\n\n const { data: page, error } = await authenticatedSupabase\n .from('custom_sales_pages')\n .update(updateData)\n .eq('id', id)\n .select()\n .single();\n\n if (error) {\n\n return new Response(JSON.stringify({ \n success: false, \n error: 'Failed to update page' \n }), { status: 500 });\n }\n\n return new Response(JSON.stringify({ \n success: true, \n page \n }), { status: 200 });\n\n } catch (error) {\n\n return new Response(JSON.stringify({ \n success: false, \n error: 'Internal server error' \n }), { status: 500 });\n }\n};\n\nexport const DELETE: APIRoute = async ({ params, request }) => {\n const { id } = params;\n\n if (!id) {\n return new Response(JSON.stringify({ \n success: false, \n error: 'Page ID is required' \n }), { status: 400 });\n }\n\n // Authentication\n const authHeader = request.headers.get('authorization');\n if (!authHeader) {\n return new Response(JSON.stringify({ \n success: false, \n error: 'Authorization header required' \n }), { status: 401 });\n }\n\n const token = authHeader.replace('Bearer ', '');\n const { data: { user }, error: authError } = await supabase.auth.getUser(token);\n\n if (authError || !user) {\n return new Response(JSON.stringify({ \n success: false, \n error: 'Invalid authentication token' \n }), { status: 401 });\n }\n\n // Create authenticated Supabase client\n const authenticatedSupabase = createClient(\n import.meta.env.PUBLIC_SUPABASE_URL,\n import.meta.env.PUBLIC_SUPABASE_ANON_KEY,\n {\n global: {\n headers: {\n Authorization: `Bearer ${token}`\n }\n }\n }\n );\n\n try {\n const { error } = await authenticatedSupabase\n .from('custom_sales_pages')\n .delete()\n .eq('id', id);\n\n if (error) {\n\n return new Response(JSON.stringify({ \n success: false, \n error: 'Failed to delete page' \n }), { status: 500 });\n }\n\n return new Response(JSON.stringify({ \n success: true \n }), { status: 200 });\n\n } catch (error) {\n\n return new Response(JSON.stringify({ \n success: false, \n error: 'Internal server error' \n }), { status: 500 });\n }\n};","usedDeprecatedRules":[]},{"filePath":"/home/tyler/apps/bct-whitelabel/src/pages/api/custom-pages/index.ts","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/home/tyler/apps/bct-whitelabel/src/pages/api/custom-pricing/overrides/[id].ts","messages":[{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":32,"column":12,"nodeType":null,"messageId":"unusedVar","endLine":32,"endColumn":17}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":1,"fixableErrorCount":0,"fixableWarningCount":0,"source":"import type { APIRoute } from 'astro';\nimport { supabase } from '../../../../lib/supabase';\n\nexport const DELETE: APIRoute = async ({ params }) => {\n const { id } = params;\n\n if (!id) {\n return new Response(JSON.stringify({ \n success: false, \n error: 'Override ID is required' \n }), { status: 400 });\n }\n\n try {\n const { error } = await supabase\n .from('event_pricing_overrides')\n .delete()\n .eq('id', id);\n\n if (error) {\n\n return new Response(JSON.stringify({ \n success: false, \n error: 'Failed to delete pricing override' \n }), { status: 500 });\n }\n\n return new Response(JSON.stringify({ \n success: true \n }), { status: 200 });\n\n } catch (error) {\n\n return new Response(JSON.stringify({ \n success: false, \n error: 'Internal server error' \n }), { status: 500 });\n }\n};","usedDeprecatedRules":[]},{"filePath":"/home/tyler/apps/bct-whitelabel/src/pages/api/custom-pricing/overrides/index.ts","messages":[{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'request' is defined but never used. Allowed unused args must match /^_/u.","line":4,"column":39,"nodeType":null,"messageId":"unusedVar","endLine":4,"endColumn":46},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":62,"column":12,"nodeType":null,"messageId":"unusedVar","endLine":62,"endColumn":17},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":118,"column":12,"nodeType":null,"messageId":"unusedVar","endLine":118,"endColumn":17}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":3,"fixableErrorCount":0,"fixableWarningCount":0,"source":"import type { APIRoute } from 'astro';\nimport { supabase } from '../../../../lib/supabase';\n\nexport const GET: APIRoute = async ({ request, url }) => {\n const userId = url.searchParams.get('user_id');\n \n if (!userId) {\n return new Response(JSON.stringify({ \n success: false, \n error: 'User ID is required' \n }), { status: 400 });\n }\n\n try {\n // First check if user is admin\n const { data: user, error: userError } = await supabase\n .from('users')\n .select('role')\n .eq('id', userId)\n .single();\n\n if (userError || user?.role !== 'admin') {\n return new Response(JSON.stringify({ \n success: false, \n error: 'Access denied. Admin privileges required.' \n }), { status: 403 });\n }\n\n // Get the user's pricing profile\n const { data: profile, error: profileError } = await supabase\n .from('custom_pricing_profiles')\n .select('id')\n .eq('user_id', userId)\n .single();\n\n if (profileError) {\n return new Response(JSON.stringify({ \n success: true, \n overrides: [] \n }), { status: 200 });\n }\n\n const { data: overrides, error } = await supabase\n .from('event_pricing_overrides')\n .select('*')\n .eq('custom_pricing_profile_id', profile.id)\n .order('created_at', { ascending: false });\n\n if (error) {\n\n return new Response(JSON.stringify({ \n success: false, \n error: 'Failed to fetch pricing overrides' \n }), { status: 500 });\n }\n\n return new Response(JSON.stringify({ \n success: true, \n overrides: overrides || [] \n }), { status: 200 });\n\n } catch (error) {\n\n return new Response(JSON.stringify({ \n success: false, \n error: 'Internal server error' \n }), { status: 500 });\n }\n};\n\nexport const POST: APIRoute = async ({ request }) => {\n try {\n const body = await request.json();\n const { \n event_id, \n custom_pricing_profile_id, \n use_custom_stripe_account, \n override_platform_fees,\n platform_fee_type,\n platform_fee_percentage,\n platform_fee_fixed\n } = body;\n\n if (!event_id || !custom_pricing_profile_id) {\n return new Response(JSON.stringify({ \n success: false, \n error: 'Event ID and pricing profile ID are required' \n }), { status: 400 });\n }\n\n const { data: override, error } = await supabase\n .from('event_pricing_overrides')\n .insert({\n event_id,\n custom_pricing_profile_id,\n use_custom_stripe_account: use_custom_stripe_account || false,\n override_platform_fees: override_platform_fees || false,\n platform_fee_type,\n platform_fee_percentage,\n platform_fee_fixed\n })\n .select()\n .single();\n\n if (error) {\n\n return new Response(JSON.stringify({ \n success: false, \n error: 'Failed to create pricing override' \n }), { status: 500 });\n }\n\n return new Response(JSON.stringify({ \n success: true, \n override \n }), { status: 201 });\n\n } catch (error) {\n\n return new Response(JSON.stringify({ \n success: false, \n error: 'Internal server error' \n }), { status: 500 });\n }\n};","usedDeprecatedRules":[]},{"filePath":"/home/tyler/apps/bct-whitelabel/src/pages/api/custom-pricing/profile/[userId].ts","messages":[{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":75,"column":12,"nodeType":null,"messageId":"unusedVar","endLine":75,"endColumn":17},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":135,"column":12,"nodeType":null,"messageId":"unusedVar","endLine":135,"endColumn":17}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":2,"fixableErrorCount":0,"fixableWarningCount":0,"source":"import type { APIRoute } from 'astro';\nimport { supabase } from '../../../../lib/supabase';\n\nexport const GET: APIRoute = async ({ params }) => {\n const { userId } = params;\n\n if (!userId) {\n return new Response(JSON.stringify({ \n success: false, \n error: 'User ID is required' \n }), { status: 400 });\n }\n\n try {\n // First check if user is admin\n const { data: user, error: userError } = await supabase\n .from('users')\n .select('role')\n .eq('id', userId)\n .single();\n\n if (userError || user?.role !== 'admin') {\n return new Response(JSON.stringify({ \n success: false, \n error: 'Access denied. Admin privileges required.' \n }), { status: 403 });\n }\n\n const { data: profile, error } = await supabase\n .from('custom_pricing_profiles')\n .select('*')\n .eq('user_id', userId)\n .single();\n\n if (error && error.code !== 'PGRST116') {\n\n return new Response(JSON.stringify({ \n success: false, \n error: 'Failed to fetch pricing profile' \n }), { status: 500 });\n }\n\n // If no profile exists, create one\n if (!profile) {\n const { data: newProfile, error: createError } = await supabase\n .from('custom_pricing_profiles')\n .insert({\n user_id: userId,\n can_override_pricing: true,\n can_set_custom_fees: true,\n use_personal_stripe: false\n })\n .select()\n .single();\n\n if (createError) {\n\n return new Response(JSON.stringify({ \n success: false, \n error: 'Failed to create pricing profile' \n }), { status: 500 });\n }\n\n return new Response(JSON.stringify({ \n success: true, \n profile: newProfile \n }), { status: 200 });\n }\n\n return new Response(JSON.stringify({ \n success: true, \n profile \n }), { status: 200 });\n\n } catch (error) {\n\n return new Response(JSON.stringify({ \n success: false, \n error: 'Internal server error' \n }), { status: 500 });\n }\n};\n\nexport const PUT: APIRoute = async ({ params, request }) => {\n const { userId } = params;\n\n if (!userId) {\n return new Response(JSON.stringify({ \n success: false, \n error: 'User ID is required' \n }), { status: 400 });\n }\n\n try {\n // First check if user is admin\n const { data: user, error: userError } = await supabase\n .from('users')\n .select('role')\n .eq('id', userId)\n .single();\n\n if (userError || user?.role !== 'admin') {\n return new Response(JSON.stringify({ \n success: false, \n error: 'Access denied. Admin privileges required.' \n }), { status: 403 });\n }\n\n const body = await request.json();\n const updateData = {\n ...body,\n updated_at: new Date().toISOString()\n };\n\n const { data: profile, error } = await supabase\n .from('custom_pricing_profiles')\n .update(updateData)\n .eq('user_id', userId)\n .select()\n .single();\n\n if (error) {\n\n return new Response(JSON.stringify({ \n success: false, \n error: 'Failed to update pricing profile' \n }), { status: 500 });\n }\n\n return new Response(JSON.stringify({ \n success: true, \n profile \n }), { status: 200 });\n\n } catch (error) {\n\n return new Response(JSON.stringify({ \n success: false, \n error: 'Internal server error' \n }), { status: 500 });\n }\n};","usedDeprecatedRules":[]},{"filePath":"/home/tyler/apps/bct-whitelabel/src/pages/api/emails/send-admin-notification.ts","messages":[{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":46,"column":35,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":46,"endColumn":38,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[1362,1365],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[1362,1365],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"no-empty","severity":1,"message":"Empty block statement.","line":52,"column":24,"nodeType":"BlockStatement","messageId":"unexpected","endLine":54,"endColumn":6,"suggestions":[{"messageId":"suggestComment","data":{"type":"block"},"fix":{"range":[1587,1593],"text":" /* empty */ "},"desc":"Add comment inside empty block statement."}]},{"ruleId":"no-undef","severity":2,"message":"'URL' is not defined.","line":64,"column":33,"nodeType":"Identifier","messageId":"undef","endLine":64,"endColumn":36}],"suppressedMessages":[],"errorCount":1,"fatalErrorCount":0,"warningCount":2,"fixableErrorCount":0,"fixableWarningCount":0,"source":"export const prerender = false;\n\nimport type { APIRoute } from 'astro';\nimport { supabase } from '../../../lib/supabase';\nimport { verifyAuth } from '../../../lib/auth';\nimport { sendAdminNotificationEmail } from '../../../lib/email';\n\nexport const POST: APIRoute = async ({ request }) => {\n try {\n // Verify authentication\n const authContext = await verifyAuth(request);\n if (!authContext) {\n return new Response(JSON.stringify({ error: 'Unauthorized' }), { \n status: 401,\n headers: { 'Content-Type': 'application/json' }\n });\n }\n\n const { organization_id } = await request.json();\n\n if (!organization_id) {\n return new Response(JSON.stringify({ error: 'Organization ID required' }), { \n status: 400,\n headers: { 'Content-Type': 'application/json' }\n });\n }\n\n // Get organization and user data\n const { data: orgData, error: orgError } = await supabase\n .from('organizations')\n .select(`\n *,\n users!inner(id, email, name)\n `)\n .eq('id', organization_id)\n .eq('users.role', 'organizer')\n .single();\n\n if (orgError || !orgData) {\n return new Response(JSON.stringify({ error: 'Organization not found' }), { \n status: 404,\n headers: { 'Content-Type': 'application/json' }\n });\n }\n\n const user = orgData.users as any;\n\n // Check if organization should be auto-approved\n const { data: shouldAutoApprove, error: approvalError } = await supabase\n .rpc('should_auto_approve', { org_id: organization_id });\n\n if (approvalError) {\n\n }\n\n // Send admin notification email\n await sendAdminNotificationEmail({\n organizationName: orgData.name,\n userEmail: user.email,\n userName: user.name || user.email,\n businessType: orgData.business_type || 'Not specified',\n approvalScore: orgData.approval_score || 0,\n requiresReview: !shouldAutoApprove,\n adminDashboardUrl: `${new URL(request.url).origin}/admin/pending-approvals`\n });\n\n return new Response(JSON.stringify({ \n success: true,\n message: 'Admin notification email sent successfully'\n }), {\n status: 200,\n headers: { 'Content-Type': 'application/json' }\n });\n\n } catch (error) {\n\n return new Response(JSON.stringify({ \n error: 'Failed to send admin notification email',\n details: error instanceof Error ? error.message : 'Unknown error'\n }), { \n status: 500,\n headers: { 'Content-Type': 'application/json' }\n });\n }\n};","usedDeprecatedRules":[]},{"filePath":"/home/tyler/apps/bct-whitelabel/src/pages/api/emails/send-application-received.ts","messages":[{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":46,"column":35,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":46,"endColumn":38,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[1364,1367],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[1364,1367],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":1,"fixableErrorCount":0,"fixableWarningCount":0,"source":"export const prerender = false;\n\nimport type { APIRoute } from 'astro';\nimport { supabase } from '../../../lib/supabase';\nimport { verifyAuth } from '../../../lib/auth';\nimport { sendApplicationReceivedEmail } from '../../../lib/email';\n\nexport const POST: APIRoute = async ({ request }) => {\n try {\n // Verify authentication\n const authContext = await verifyAuth(request);\n if (!authContext) {\n return new Response(JSON.stringify({ error: 'Unauthorized' }), { \n status: 401,\n headers: { 'Content-Type': 'application/json' }\n });\n }\n\n const { organization_id } = await request.json();\n\n if (!organization_id) {\n return new Response(JSON.stringify({ error: 'Organization ID required' }), { \n status: 400,\n headers: { 'Content-Type': 'application/json' }\n });\n }\n\n // Get organization and user data\n const { data: orgData, error: orgError } = await supabase\n .from('organizations')\n .select(`\n *,\n users!inner(id, email, name)\n `)\n .eq('id', organization_id)\n .eq('users.role', 'organizer')\n .single();\n\n if (orgError || !orgData) {\n return new Response(JSON.stringify({ error: 'Organization not found' }), { \n status: 404,\n headers: { 'Content-Type': 'application/json' }\n });\n }\n\n const user = orgData.users as any;\n\n // Send application received email\n await sendApplicationReceivedEmail({\n organizationName: orgData.name,\n userEmail: user.email,\n userName: user.name || user.email,\n expectedApprovalTime: '1-2 business days',\n referenceNumber: orgData.id.substring(0, 8).toUpperCase(),\n nextSteps: [\n 'Our team will review your application and business information',\n 'We may contact you for additional details if needed',\n 'You\\'ll receive an email notification once approved',\n 'After approval, you can complete secure Stripe payment setup'\n ]\n });\n\n return new Response(JSON.stringify({ \n success: true,\n message: 'Application received email sent successfully'\n }), {\n status: 200,\n headers: { 'Content-Type': 'application/json' }\n });\n\n } catch (error) {\n\n return new Response(JSON.stringify({ \n error: 'Failed to send application received email',\n details: error instanceof Error ? error.message : 'Unknown error'\n }), { \n status: 500,\n headers: { 'Content-Type': 'application/json' }\n });\n }\n};","usedDeprecatedRules":[]},{"filePath":"/home/tyler/apps/bct-whitelabel/src/pages/api/emails/send-approval-notification.ts","messages":[{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":46,"column":35,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":46,"endColumn":38,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[1388,1391],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[1388,1391],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"no-undef","severity":2,"message":"'URL' is not defined.","line":68,"column":35,"nodeType":"Identifier","messageId":"undef","endLine":68,"endColumn":38}],"suppressedMessages":[],"errorCount":1,"fatalErrorCount":0,"warningCount":1,"fixableErrorCount":0,"fixableWarningCount":0,"source":"export const prerender = false;\n\nimport type { APIRoute } from 'astro';\nimport { supabase } from '../../../lib/supabase';\nimport { verifyAuth } from '../../../lib/auth';\nimport { sendApprovalNotificationEmail } from '../../../lib/email';\n\nexport const POST: APIRoute = async ({ request }) => {\n try {\n // Verify authentication\n const authContext = await verifyAuth(request);\n if (!authContext) {\n return new Response(JSON.stringify({ error: 'Unauthorized' }), { \n status: 401,\n headers: { 'Content-Type': 'application/json' }\n });\n }\n\n const { organization_id, auto_approved = false } = await request.json();\n\n if (!organization_id) {\n return new Response(JSON.stringify({ error: 'Organization ID required' }), { \n status: 400,\n headers: { 'Content-Type': 'application/json' }\n });\n }\n\n // Get organization and user data\n const { data: orgData, error: orgError } = await supabase\n .from('organizations')\n .select(`\n *,\n users!inner(id, email, name)\n `)\n .eq('id', organization_id)\n .eq('users.role', 'organizer')\n .single();\n\n if (orgError || !orgData) {\n return new Response(JSON.stringify({ error: 'Organization not found' }), { \n status: 404,\n headers: { 'Content-Type': 'application/json' }\n });\n }\n\n const user = orgData.users as any;\n\n // Get approver info\n let approvedBy = 'Auto-approved';\n if (!auto_approved && orgData.approved_by) {\n const { data: approver } = await supabase\n .from('users')\n .select('name, email')\n .eq('id', orgData.approved_by)\n .single();\n \n if (approver) {\n approvedBy = approver.name || approver.email;\n }\n }\n\n // Send approval notification email\n await sendApprovalNotificationEmail({\n organizationName: orgData.name,\n userEmail: user.email,\n userName: user.name || user.email,\n approvedBy,\n stripeOnboardingUrl: `${new URL(request.url).origin}/onboarding/stripe`,\n nextSteps: [\n 'Complete secure Stripe Connect verification',\n 'Provide bank account details for automated payouts',\n 'Review and accept terms of service',\n 'Your account will be activated immediately after completion'\n ],\n welcomeMessage: auto_approved \n ? 'Your application met our auto-approval criteria - welcome to the platform!'\n : undefined\n });\n\n return new Response(JSON.stringify({ \n success: true,\n message: 'Approval notification email sent successfully'\n }), {\n status: 200,\n headers: { 'Content-Type': 'application/json' }\n });\n\n } catch (error) {\n\n return new Response(JSON.stringify({ \n error: 'Failed to send approval notification email',\n details: error instanceof Error ? error.message : 'Unknown error'\n }), { \n status: 500,\n headers: { 'Content-Type': 'application/json' }\n });\n }\n};","usedDeprecatedRules":[]},{"filePath":"/home/tyler/apps/bct-whitelabel/src/pages/api/events/[id]/marketing-kit.ts","messages":[{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":74,"column":12,"nodeType":null,"messageId":"unusedVar","endLine":74,"endColumn":17}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":1,"fixableErrorCount":0,"fixableWarningCount":0,"source":"import type { APIRoute } from 'astro';\nimport { supabase } from '../../../../lib/supabase';\n\nexport const POST: APIRoute = async ({ params, request }) => {\n try {\n const eventId = params.id;\n\n if (!eventId) {\n return new Response(JSON.stringify({\n success: false,\n error: 'Event ID is required'\n }), {\n status: 400,\n headers: { 'Content-Type': 'application/json' }\n });\n }\n\n // Get user session\n const authHeader = request.headers.get('Authorization');\n const token = authHeader?.replace('Bearer ', '');\n \n if (!token) {\n return new Response(JSON.stringify({\n success: false,\n error: 'Authentication required'\n }), {\n status: 401,\n headers: { 'Content-Type': 'application/json' }\n });\n }\n\n const { data: { user }, error: authError } = await supabase.auth.getUser(token);\n if (authError || !user) {\n return new Response(JSON.stringify({\n success: false,\n error: 'Invalid authentication'\n }), {\n status: 401,\n headers: { 'Content-Type': 'application/json' }\n });\n }\n\n // Get event details\n const { data: event, error: eventError } = await supabase\n .from('events')\n .select('id, title, description, venue, start_time')\n .eq('id', eventId)\n .single();\n\n if (eventError || !event) {\n return new Response(JSON.stringify({\n success: false,\n error: 'Event not found'\n }), {\n status: 404,\n headers: { 'Content-Type': 'application/json' }\n });\n }\n\n // Return a simple response since we don't have the marketing kit tables\n return new Response(JSON.stringify({\n success: true,\n data: {\n event,\n assets: [],\n generated_at: new Date().toISOString(),\n message: 'Marketing kit generation is not yet implemented'\n }\n }), {\n status: 200,\n headers: { 'Content-Type': 'application/json' }\n });\n\n } catch (error) {\n\n return new Response(JSON.stringify({\n success: false,\n error: 'Failed to process marketing kit request'\n }), {\n status: 500,\n headers: { 'Content-Type': 'application/json' }\n });\n }\n};","usedDeprecatedRules":[]},{"filePath":"/home/tyler/apps/bct-whitelabel/src/pages/api/events/[id]/marketing-kit/download.ts","messages":[{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":86,"column":12,"nodeType":null,"messageId":"unusedVar","endLine":86,"endColumn":17}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":1,"fixableErrorCount":0,"fixableWarningCount":0,"source":"import type { APIRoute } from 'astro';\nimport { supabase } from '../../../../../lib/supabase';\n\nexport const GET: APIRoute = async ({ params, request }) => {\n try {\n const eventId = params.id;\n if (!eventId) {\n return new Response('Event ID is required', { status: 400 });\n }\n\n // Get user session\n const authHeader = request.headers.get('Authorization');\n const token = authHeader?.replace('Bearer ', '');\n \n if (!token) {\n return new Response('Authentication required', { status: 401 });\n }\n\n const { data: { user }, error: authError } = await supabase.auth.getUser(token);\n if (authError || !user) {\n return new Response('Invalid authentication', { status: 401 });\n }\n\n // Get user's organization\n const { data: userData, error: userError } = await supabase\n .from('users')\n .select('organization_id')\n .eq('id', user.id)\n .single();\n\n if (userError || !userData?.organization_id) {\n return new Response('User organization not found', { status: 403 });\n }\n\n // Get event details\n const { data: event, error: eventError } = await supabase\n .from('events')\n .select('*')\n .eq('id', eventId)\n .eq('organization_id', userData.organization_id)\n .single();\n\n if (eventError || !event) {\n return new Response('Event not found or access denied', { status: 404 });\n }\n\n // Get marketing kit assets\n const { data: assets, error: assetsError } = await supabase\n .from('marketing_kit_assets')\n .select('*')\n .eq('event_id', eventId)\n .eq('is_active', true)\n .order('generated_at', { ascending: false });\n\n if (assetsError || !assets || assets.length === 0) {\n return new Response('No marketing kit assets found', { status: 404 });\n }\n\n // Create a simple ZIP-like response for now\n // In production, you'd generate an actual ZIP file\n const zipContent = {\n event: {\n title: event.title,\n date: event.start_time,\n venue: event.venue\n },\n assets: assets.map(asset => ({\n type: asset.asset_type,\n title: asset.title,\n url: asset.image_url || asset.download_url,\n content: asset.content\n })),\n generated_at: new Date().toISOString()\n };\n\n // Return JSON for now - in production this would be a ZIP file\n return new Response(JSON.stringify(zipContent, null, 2), {\n status: 200,\n headers: {\n 'Content-Type': 'application/json',\n 'Content-Disposition': `attachment; filename=\"${event.slug}-marketing-kit.json\"`,\n 'Cache-Control': 'no-cache'\n }\n });\n\n } catch (error) {\n\n return new Response('Internal server error', { status: 500 });\n }\n};","usedDeprecatedRules":[]},{"filePath":"/home/tyler/apps/bct-whitelabel/src/pages/api/events/nearby.ts","messages":[{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'supabase' is defined but never used. Allowed unused vars must match /^_/u.","line":3,"column":10,"nodeType":null,"messageId":"unusedVar","endLine":3,"endColumn":18},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'url' is defined but never used. Allowed unused args must match /^_/u.","line":5,"column":48,"nodeType":null,"messageId":"unusedVar","endLine":5,"endColumn":51},{"ruleId":"no-undef","severity":2,"message":"'URL' is not defined.","line":7,"column":30,"nodeType":"Identifier","messageId":"undef","endLine":7,"endColumn":33},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":55,"column":12,"nodeType":null,"messageId":"unusedVar","endLine":55,"endColumn":17}],"suppressedMessages":[],"errorCount":1,"fatalErrorCount":0,"warningCount":3,"fixableErrorCount":0,"fixableWarningCount":0,"source":"import type { APIRoute } from 'astro';\nimport { trendingAnalyticsService } from '../../../lib/analytics';\nimport { supabase } from '../../../lib/supabase';\n\nexport const GET: APIRoute = async ({ request, url }) => {\n try {\n const searchParams = new URL(request.url).searchParams;\n \n // Get required location parameters\n const latitude = searchParams.get('lat');\n const longitude = searchParams.get('lng');\n \n if (!latitude || !longitude) {\n return new Response(JSON.stringify({\n success: false,\n error: 'Latitude and longitude are required'\n }), {\n status: 400,\n headers: {\n 'Content-Type': 'application/json'\n }\n });\n }\n \n const radiusMiles = parseInt(searchParams.get('radius') || '25');\n const limit = parseInt(searchParams.get('limit') || '10');\n \n // Get hot events in the area\n const nearbyEvents = await trendingAnalyticsService.getHotEventsInArea(\n parseFloat(latitude),\n parseFloat(longitude),\n radiusMiles,\n limit\n );\n \n return new Response(JSON.stringify({\n success: true,\n data: nearbyEvents,\n meta: {\n userLocation: {\n latitude: parseFloat(latitude),\n longitude: parseFloat(longitude),\n radius: radiusMiles\n },\n count: nearbyEvents.length,\n limit\n }\n }), {\n status: 200,\n headers: {\n 'Content-Type': 'application/json',\n 'Cache-Control': 'public, max-age=300' // 5 minutes\n }\n });\n } catch (error) {\n\n return new Response(JSON.stringify({\n success: false,\n error: 'Failed to fetch nearby events'\n }), {\n status: 500,\n headers: {\n 'Content-Type': 'application/json'\n }\n });\n }\n};","usedDeprecatedRules":[]},{"filePath":"/home/tyler/apps/bct-whitelabel/src/pages/api/events/trending.ts","messages":[{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'url' is defined but never used. Allowed unused args must match /^_/u.","line":5,"column":48,"nodeType":null,"messageId":"unusedVar","endLine":5,"endColumn":51},{"ruleId":"no-undef","severity":2,"message":"'URL' is not defined.","line":7,"column":30,"nodeType":"Identifier","messageId":"undef","endLine":7,"endColumn":33},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":54,"column":12,"nodeType":null,"messageId":"unusedVar","endLine":54,"endColumn":17}],"suppressedMessages":[],"errorCount":1,"fatalErrorCount":0,"warningCount":2,"fixableErrorCount":0,"fixableWarningCount":0,"source":"import type { APIRoute } from 'astro';\nimport { trendingAnalyticsService } from '../../../lib/analytics';\nimport { geolocationService } from '../../../lib/geolocation';\n\nexport const GET: APIRoute = async ({ request, url }) => {\n try {\n const searchParams = new URL(request.url).searchParams;\n \n // Get location parameters\n const latitude = searchParams.get('lat') ? parseFloat(searchParams.get('lat')!) : undefined;\n const longitude = searchParams.get('lng') ? parseFloat(searchParams.get('lng')!) : undefined;\n const radiusMiles = parseInt(searchParams.get('radius') || '50');\n const limit = parseInt(searchParams.get('limit') || '20');\n \n // Get user location from IP if not provided\n let userLat = latitude;\n let userLng = longitude;\n \n if (!userLat || !userLng) {\n const ipLocation = await geolocationService.getLocationFromIP();\n if (ipLocation) {\n userLat = ipLocation.latitude;\n userLng = ipLocation.longitude;\n }\n }\n \n // Get trending events\n const trendingEvents = await trendingAnalyticsService.getTrendingEvents(\n userLat,\n userLng,\n radiusMiles,\n limit\n );\n \n return new Response(JSON.stringify({\n success: true,\n data: trendingEvents,\n meta: {\n userLocation: userLat && userLng ? {\n latitude: userLat,\n longitude: userLng,\n radius: radiusMiles\n } : null,\n count: trendingEvents.length,\n limit\n }\n }), {\n status: 200,\n headers: {\n 'Content-Type': 'application/json',\n 'Cache-Control': 'public, max-age=300' // 5 minutes\n }\n });\n } catch (error) {\n\n return new Response(JSON.stringify({\n success: false,\n error: 'Failed to fetch trending events'\n }), {\n status: 500,\n headers: {\n 'Content-Type': 'application/json'\n }\n });\n }\n};","usedDeprecatedRules":[]},{"filePath":"/home/tyler/apps/bct-whitelabel/src/pages/api/gdpr/user-data.ts","messages":[{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'logSecurityEvent' is defined but never used. Allowed unused vars must match /^_/u.","line":7,"column":27,"nodeType":null,"messageId":"unusedVar","endLine":7,"endColumn":43},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":57,"column":12,"nodeType":null,"messageId":"unusedVar","endLine":57,"endColumn":17},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":154,"column":12,"nodeType":null,"messageId":"unusedVar","endLine":154,"endColumn":17},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":202,"column":12,"nodeType":null,"messageId":"unusedVar","endLine":202,"endColumn":17},{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":212,"column":19,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":212,"endColumn":22,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[6727,6730],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[6727,6730],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"no-useless-catch","severity":2,"message":"Unnecessary try/catch wrapper.","line":222,"column":3,"nodeType":"TryStatement","messageId":"unnecessaryCatch","endLine":271,"endColumn":4},{"ruleId":"no-useless-catch","severity":2,"message":"Unnecessary try/catch wrapper.","line":338,"column":3,"nodeType":"TryStatement","messageId":"unnecessaryCatch","endLine":410,"endColumn":4}],"suppressedMessages":[],"errorCount":2,"fatalErrorCount":0,"warningCount":5,"fixableErrorCount":0,"fixableWarningCount":0,"source":"export const prerender = false;\n\nimport type { APIRoute } from 'astro';\nimport { supabase } from '../../../lib/supabase';\nimport { requireAuth, getClientIP, checkRateLimit, createAuthResponse } from '../../../lib/auth';\nimport { validateRequest } from '../../../lib/validation';\nimport { logUserActivity, logSecurityEvent } from '../../../lib/logger';\nimport { z } from 'zod';\n\n// Validation schemas\nconst userDataRequestSchema = z.object({\n request_type: z.enum(['export', 'delete', 'portability']),\n user_email: z.string().email().optional(),\n confirmation: z.boolean().optional()\n});\n\n// User data export endpoint\nexport const GET: APIRoute = async ({ request }) => {\n try {\n // Rate limiting\n const clientIP = getClientIP(request);\n if (!checkRateLimit(`gdpr-export:${clientIP}`, 2, 300000)) { // 2 requests per 5 minutes\n return createAuthResponse({ error: 'Rate limit exceeded for data export requests' }, 429);\n }\n\n // Require authentication\n const auth = await requireAuth(request);\n \n // Log data export request\n logUserActivity({\n action: 'gdpr_data_export_requested',\n userId: auth.user.id,\n ipAddress: clientIP,\n userAgent: request.headers.get('User-Agent') || undefined,\n details: { requestType: 'export' }\n });\n\n // Collect all user data\n const userData = await collectUserData(auth.user.id);\n \n // Log successful export\n logUserActivity({\n action: 'gdpr_data_export_completed',\n userId: auth.user.id,\n ipAddress: clientIP,\n details: { dataSize: JSON.stringify(userData).length }\n });\n\n return createAuthResponse({\n success: true,\n data: userData,\n exported_at: new Date().toISOString(),\n user_id: auth.user.id,\n notice: 'This export contains all personal data we have stored about you. You have the right to correct, update, or delete this information.'\n });\n\n } catch (error) {\n\n return createAuthResponse({ \n error: 'Failed to export user data'\n }, 500);\n }\n};\n\n// User data deletion endpoint\nexport const DELETE: APIRoute = async ({ request }) => {\n try {\n // Rate limiting\n const clientIP = getClientIP(request);\n if (!checkRateLimit(`gdpr-delete:${clientIP}`, 1, 86400000)) { // 1 request per day\n return createAuthResponse({ error: 'Rate limit exceeded for data deletion requests' }, 429);\n }\n\n // Require authentication\n const auth = await requireAuth(request);\n \n const body = await request.json();\n const validation = validateRequest(userDataRequestSchema, body);\n if (!validation.success) {\n return createAuthResponse({ \n error: 'Invalid request',\n details: validation.error\n }, 400);\n }\n\n const { confirmation } = validation.data;\n \n if (!confirmation) {\n return createAuthResponse({ \n error: 'Deletion confirmation required',\n notice: 'You must explicitly confirm that you want to delete all your data. This action cannot be undone.'\n }, 400);\n }\n\n // Log deletion request\n logUserActivity({\n action: 'gdpr_data_deletion_requested',\n userId: auth.user.id,\n ipAddress: clientIP,\n userAgent: request.headers.get('User-Agent') || undefined,\n details: { confirmation: true }\n });\n\n // Check for active events or pending transactions\n const { data: activeEvents } = await supabase\n .from('events')\n .select('id, title, start_time')\n .eq('created_by', auth.user.id)\n .gt('start_time', new Date().toISOString());\n\n const { data: pendingTickets } = await supabase\n .from('tickets')\n .select('id, event_id')\n .eq('purchaser_email', auth.user.email)\n .eq('status', 'valid')\n .neq('checked_in', true);\n\n if (activeEvents && activeEvents.length > 0) {\n return createAuthResponse({ \n error: 'Cannot delete account with active events',\n details: 'You have active events that are scheduled for the future. Please cancel or complete these events before deleting your account.',\n active_events: activeEvents\n }, 400);\n }\n\n if (pendingTickets && pendingTickets.length > 0) {\n return createAuthResponse({ \n error: 'Cannot delete account with valid tickets',\n details: 'You have valid tickets for upcoming events. Please use or transfer these tickets before deleting your account.',\n ticket_count: pendingTickets.length\n }, 400);\n }\n\n // Perform data deletion\n await deleteUserData(auth.user.id, auth.user.email!);\n \n // Log successful deletion\n logUserActivity({\n action: 'gdpr_data_deletion_completed',\n userId: auth.user.id,\n ipAddress: clientIP,\n details: { deletedAt: new Date().toISOString() }\n });\n\n // Sign out the user\n await supabase.auth.signOut();\n\n return createAuthResponse({\n success: true,\n message: 'Your account and all associated data have been permanently deleted.',\n deleted_at: new Date().toISOString()\n });\n\n } catch (error) {\n\n return createAuthResponse({ \n error: 'Failed to delete user data'\n }, 500);\n }\n};\n\n// Data portability endpoint (structured data for transfer)\nexport const POST: APIRoute = async ({ request }) => {\n try {\n // Rate limiting\n const clientIP = getClientIP(request);\n if (!checkRateLimit(`gdpr-portability:${clientIP}`, 3, 3600000)) { // 3 requests per hour\n return createAuthResponse({ error: 'Rate limit exceeded for data portability requests' }, 429);\n }\n\n // Require authentication\n const auth = await requireAuth(request);\n \n const body = await request.json();\n const validation = validateRequest(userDataRequestSchema, body);\n if (!validation.success) {\n return createAuthResponse({ \n error: 'Invalid request',\n details: validation.error\n }, 400);\n }\n\n // Log portability request\n logUserActivity({\n action: 'gdpr_data_portability_requested',\n userId: auth.user.id,\n ipAddress: clientIP,\n userAgent: request.headers.get('User-Agent') || undefined\n });\n\n // Collect structured data for portability\n const portableData = await collectPortableData(auth.user.id);\n \n return createAuthResponse({\n success: true,\n data: portableData,\n format: 'json',\n exported_at: new Date().toISOString(),\n notice: 'This data is formatted for easy import into other systems. The format complies with GDPR portability requirements.'\n });\n\n } catch (error) {\n\n return createAuthResponse({ \n error: 'Failed to create portable data'\n }, 500);\n }\n};\n\n// Helper function to collect all user data\nasync function collectUserData(userId: string) {\n const userData: any = {\n user_profile: null,\n organizations: [],\n events: [],\n tickets: [],\n purchase_attempts: [],\n audit_logs: [],\n collected_at: new Date().toISOString()\n };\n\n try {\n // Get user profile\n const { data: user } = await supabase\n .from('users')\n .select('*')\n .eq('id', userId)\n .single();\n userData.user_profile = user;\n\n // Get organizations\n const { data: organizations } = await supabase\n .from('organizations')\n .select('*')\n .eq('id', user?.organization_id);\n userData.organizations = organizations || [];\n\n // Get events created by user\n const { data: events } = await supabase\n .from('events')\n .select('*')\n .eq('created_by', userId);\n userData.events = events || [];\n\n // Get tickets purchased by user\n const { data: tickets } = await supabase\n .from('tickets')\n .select('*')\n .eq('purchaser_email', user?.email);\n userData.tickets = tickets || [];\n\n // Get purchase attempts\n const { data: purchases } = await supabase\n .from('purchase_attempts')\n .select('*')\n .eq('purchaser_email', user?.email);\n userData.purchase_attempts = purchases || [];\n\n // Get audit logs (admin actions by this user)\n const { data: auditLogs } = await supabase\n .from('audit_logs')\n .select('*')\n .eq('user_id', userId)\n .order('created_at', { ascending: false })\n .limit(100); // Limit to recent 100 entries\n userData.audit_logs = auditLogs || [];\n\n } catch (error) {\n\n throw error;\n }\n\n return userData;\n}\n\n// Helper function to collect portable data (structured for transfer)\nasync function collectPortableData(userId: string) {\n const { data: user } = await supabase\n .from('users')\n .select('*')\n .eq('id', userId)\n .single();\n\n const portableData = {\n profile: {\n name: user?.name,\n email: user?.email,\n created_at: user?.created_at,\n role: user?.role\n },\n events_created: [],\n tickets_purchased: [],\n purchase_history: []\n };\n\n // Get events in portable format\n const { data: events } = await supabase\n .from('events')\n .select('title, description, venue, start_time, end_time, created_at')\n .eq('created_by', userId);\n \n portableData.events_created = events?.map(event => ({\n title: event.title,\n description: event.description,\n venue: event.venue,\n start_time: event.start_time,\n end_time: event.end_time,\n created_at: event.created_at\n })) || [];\n\n // Get tickets in portable format\n const { data: tickets } = await supabase\n .from('tickets')\n .select(`\n price,\n status,\n checked_in,\n created_at,\n events (title, venue, start_time)\n `)\n .eq('purchaser_email', user?.email);\n \n portableData.tickets_purchased = tickets?.map(ticket => ({\n event_title: ticket.events?.title,\n event_venue: ticket.events?.venue,\n event_date: ticket.events?.start_time,\n price_paid: ticket.price,\n status: ticket.status,\n attended: ticket.checked_in,\n purchased_at: ticket.created_at\n })) || [];\n\n return portableData;\n}\n\n// Helper function to delete user data\nasync function deleteUserData(userId: string, userEmail: string) {\n try {\n // Note: Be careful with deletions - some data may need to be retained for legal/accounting purposes\n \n // Delete in reverse order of dependencies\n \n // Delete audit logs\n await supabase\n .from('audit_logs')\n .delete()\n .eq('user_id', userId);\n\n // Anonymize tickets instead of deleting (for event organizer records)\n await supabase\n .from('tickets')\n .update({\n purchaser_email: `deleted-user-${Date.now()}@anonymized.local`,\n purchaser_name: 'Deleted User'\n })\n .eq('purchaser_email', userEmail);\n\n // Anonymize purchase attempts\n await supabase\n .from('purchase_attempts')\n .update({\n purchaser_email: `deleted-user-${Date.now()}@anonymized.local`,\n purchaser_name: 'Deleted User'\n })\n .eq('purchaser_email', userEmail);\n\n // Delete events created by user (only if no tickets sold)\n const { data: userEvents } = await supabase\n .from('events')\n .select('id')\n .eq('created_by', userId);\n\n if (userEvents) {\n for (const event of userEvents) {\n const { data: eventTickets } = await supabase\n .from('tickets')\n .select('id')\n .eq('event_id', event.id)\n .limit(1);\n\n if (!eventTickets || eventTickets.length === 0) {\n // Safe to delete event with no tickets\n await supabase\n .from('events')\n .delete()\n .eq('id', event.id);\n } else {\n // Anonymize event creator\n await supabase\n .from('events')\n .update({ created_by: null })\n .eq('id', event.id);\n }\n }\n }\n\n // Delete user profile\n await supabase\n .from('users')\n .delete()\n .eq('id', userId);\n\n // Delete from Supabase Auth\n // Note: This would typically be done through the admin API\n // For now, we'll just sign out the user\n \n } catch (error) {\n\n throw error;\n }\n}","usedDeprecatedRules":[]},{"filePath":"/home/tyler/apps/bct-whitelabel/src/pages/api/inventory/availability/[ticketTypeId].ts","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/home/tyler/apps/bct-whitelabel/src/pages/api/inventory/complete-purchase.ts","messages":[{"ruleId":"no-undef","severity":2,"message":"'crypto' is not defined.","line":88,"column":17,"nodeType":"Identifier","messageId":"undef","endLine":88,"endColumn":23}],"suppressedMessages":[],"errorCount":1,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"source":"export const prerender = false;\n\nimport type { APIRoute } from 'astro';\nimport { supabase } from '../../../lib/supabase';\nimport { verifyAuth } from '../../../lib/auth';\n\nexport const POST: APIRoute = async ({ request }) => {\n try {\n // Server-side authentication check\n const auth = await verifyAuth(request);\n if (!auth) {\n return new Response(JSON.stringify({ error: 'Unauthorized' }), {\n status: 401,\n headers: { 'Content-Type': 'application/json' }\n });\n }\n const body = await request.json();\n const { \n purchase_attempt_id, \n payment_intent_id, \n session_id \n } = body;\n\n if (!purchase_attempt_id || !payment_intent_id || !session_id) {\n return new Response(JSON.stringify({ \n error: 'purchase_attempt_id, payment_intent_id, and session_id are required' \n }), {\n status: 400,\n headers: { 'Content-Type': 'application/json' }\n });\n }\n\n // Start a transaction to complete the purchase\n const { data: purchaseAttempt, error: purchaseError } = await supabase\n .from('purchase_attempts')\n .select(`\n *,\n purchase_attempt_items (\n *,\n ticket_types (\n event_id,\n name,\n price\n )\n )\n `)\n .eq('id', purchase_attempt_id)\n .eq('session_id', session_id)\n .eq('status', 'pending')\n .single();\n\n if (purchaseError || !purchaseAttempt) {\n return new Response(JSON.stringify({ \n error: 'Purchase attempt not found or already processed' \n }), {\n status: 404,\n headers: { 'Content-Type': 'application/json' }\n });\n }\n\n // Update purchase attempt to completed\n const { error: updateError } = await supabase\n .from('purchase_attempts')\n .update({ \n status: 'completed',\n stripe_payment_intent_id: payment_intent_id,\n completed_at: new Date().toISOString()\n })\n .eq('id', purchase_attempt_id);\n\n if (updateError) {\n throw updateError;\n }\n\n // Create actual tickets for each purchase item\n const ticketsToCreate = [];\n for (const item of purchaseAttempt.purchase_attempt_items) {\n for (let i = 0; i < item.quantity; i++) {\n ticketsToCreate.push({\n event_id: item.ticket_types.event_id,\n ticket_type_id: item.ticket_type_id,\n seat_id: item.seat_id,\n purchaser_email: purchaseAttempt.purchaser_email,\n purchaser_name: purchaseAttempt.purchaser_name,\n price: item.unit_price * 100, // Convert back to cents\n purchase_session_id: session_id,\n purchase_attempt_id: purchase_attempt_id,\n uuid: crypto.randomUUID() // Generate QR code UUID\n });\n }\n }\n\n const { data: createdTickets, error: ticketsError } = await supabase\n .from('tickets')\n .insert(ticketsToCreate)\n .select();\n\n if (ticketsError) {\n // Rollback purchase attempt\n await supabase\n .from('purchase_attempts')\n .update({ status: 'failed', failure_reason: 'Failed to create tickets' })\n .eq('id', purchase_attempt_id);\n \n throw ticketsError;\n }\n\n // Mark reservations as converted\n const { error: reservationsError } = await supabase\n .from('ticket_reservations')\n .update({ status: 'converted' })\n .eq('reserved_for_purchase_id', purchase_attempt_id);\n\n if (reservationsError) {\n\n // Don't fail the entire purchase for this\n }\n\n // Release any reserved seats that are now taken\n for (const item of purchaseAttempt.purchase_attempt_items) {\n if (item.seat_id) {\n await supabase\n .from('seats')\n .update({ \n is_available: false, \n reserved_until: null,\n last_reserved_by: null \n })\n .eq('id', item.seat_id);\n }\n }\n\n return new Response(JSON.stringify({\n success: true,\n purchase: {\n id: purchaseAttempt.id,\n total_amount: purchaseAttempt.total_amount,\n tickets_created: createdTickets.length,\n tickets: createdTickets.map(ticket => ({\n id: ticket.id,\n uuid: ticket.uuid,\n ticket_type_id: ticket.ticket_type_id\n }))\n }\n }), {\n status: 200,\n headers: { 'Content-Type': 'application/json' }\n });\n } catch (error) {\n\n return new Response(JSON.stringify({ \n error: 'Failed to complete purchase',\n details: error.message \n }), {\n status: 500,\n headers: { 'Content-Type': 'application/json' }\n });\n }\n};","usedDeprecatedRules":[]},{"filePath":"/home/tyler/apps/bct-whitelabel/src/pages/api/inventory/purchase-attempt.ts","messages":[{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":172,"column":12,"nodeType":null,"messageId":"unusedVar","endLine":172,"endColumn":17}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":1,"fixableErrorCount":0,"fixableWarningCount":0,"source":"export const prerender = false;\n\nimport type { APIRoute } from 'astro';\nimport { supabase } from '../../../lib/supabase';\nimport { validateRequest, sanitizeString, sanitizeEmail } from '../../../lib/validation';\nimport { getClientIP, checkRateLimit, createAuthResponse } from '../../../lib/auth';\nimport { z } from 'zod';\n\n// Validation schema for purchase attempt\nconst purchaseAttemptSchema = z.object({\n session_id: z.string().min(1).max(200),\n event_id: z.string().uuid(),\n purchaser_email: z.string().email(),\n purchaser_name: z.string().min(1).max(100),\n items: z.array(z.object({\n ticket_type_id: z.string().uuid(),\n quantity: z.number().int().positive().max(10),\n unit_price: z.number().int().nonnegative(),\n seat_id: z.string().uuid().optional()\n })).min(1).max(20),\n platform_fee: z.number().int().nonnegative().optional(),\n hold_minutes: z.number().int().min(5).max(120).optional()\n});\n\nexport const POST: APIRoute = async ({ request }) => {\n try {\n // Rate limiting\n const clientIP = getClientIP(request);\n if (!checkRateLimit(`purchase-attempt:${clientIP}`, 5, 60000)) { // 5 requests per minute\n return createAuthResponse({ error: 'Rate limit exceeded' }, 429);\n }\n\n const body = await request.json();\n \n // Validate input\n const validation = validateRequest(purchaseAttemptSchema, body);\n if (!validation.success) {\n return createAuthResponse({ \n error: 'Invalid request data',\n details: validation.error\n }, 400);\n }\n\n const { \n session_id,\n event_id,\n purchaser_email,\n purchaser_name,\n items,\n platform_fee,\n hold_minutes = 30 \n } = validation.data;\n\n // Sanitize inputs\n const sanitizedData = {\n session_id: sanitizeString(session_id),\n event_id,\n purchaser_email: sanitizeEmail(purchaser_email),\n purchaser_name: sanitizeString(purchaser_name),\n items,\n platform_fee: platform_fee || 0,\n hold_minutes\n };\n\n // Calculate total amount\n const total_amount = sanitizedData.items.reduce((sum, item) => sum + (item.quantity * item.unit_price), 0);\n const expires_at = new Date(Date.now() + (sanitizedData.hold_minutes * 60 * 1000)).toISOString();\n\n // Create purchase attempt\n const { data: purchaseAttempt, error: purchaseError } = await supabase\n .from('purchase_attempts')\n .insert({\n session_id: sanitizedData.session_id,\n event_id: sanitizedData.event_id,\n purchaser_email: sanitizedData.purchaser_email,\n purchaser_name: sanitizedData.purchaser_name,\n total_amount,\n platform_fee: sanitizedData.platform_fee,\n expires_at,\n status: 'pending'\n })\n .select()\n .single();\n\n if (purchaseError) {\n throw purchaseError;\n }\n\n // Reserve tickets for each item\n const reservations = [];\n const purchaseItems = [];\n\n for (const item of sanitizedData.items) {\n try {\n // Reserve tickets\n const { data: reservationId, error: reserveError } = await supabase\n .rpc('reserve_tickets', {\n p_ticket_type_id: item.ticket_type_id,\n p_quantity: item.quantity,\n p_reserved_by: sanitizedData.session_id,\n p_hold_minutes: sanitizedData.hold_minutes,\n p_seat_ids: item.seat_id ? [item.seat_id] : null\n });\n\n if (reserveError) {\n throw reserveError;\n }\n\n reservations.push(reservationId);\n\n // Create purchase attempt item\n const { data: purchaseItem, error: itemError } = await supabase\n .from('purchase_attempt_items')\n .insert({\n purchase_attempt_id: purchaseAttempt.id,\n ticket_type_id: item.ticket_type_id,\n seat_id: item.seat_id || null,\n quantity: item.quantity,\n unit_price: item.unit_price,\n total_price: item.quantity * item.unit_price\n })\n .select()\n .single();\n\n if (itemError) {\n throw itemError;\n }\n\n purchaseItems.push(purchaseItem);\n\n // Link reservation to purchase attempt\n await supabase\n .from('ticket_reservations')\n .update({ reserved_for_purchase_id: purchaseAttempt.id })\n .eq('id', reservationId);\n\n } catch (itemError) {\n // If any item fails, clean up previous reservations\n for (const prevReservationId of reservations) {\n await supabase\n .from('ticket_reservations')\n .update({ status: 'cancelled' })\n .eq('id', prevReservationId);\n }\n\n // Mark purchase attempt as failed\n await supabase\n .from('purchase_attempts')\n .update({ \n status: 'failed', \n failure_reason: `Failed to reserve tickets: ${itemError.message}` \n })\n .eq('id', purchaseAttempt.id);\n\n throw itemError;\n }\n }\n\n return createAuthResponse({\n success: true,\n purchase_attempt: {\n id: purchaseAttempt.id,\n session_id: purchaseAttempt.session_id,\n total_amount: purchaseAttempt.total_amount,\n platform_fee: purchaseAttempt.platform_fee,\n expires_at: purchaseAttempt.expires_at,\n status: purchaseAttempt.status,\n items: purchaseItems,\n reservations\n }\n });\n } catch (error) {\n\n return createAuthResponse({ \n error: 'Failed to create purchase attempt'\n // Don't expose internal error details in production\n }, 500);\n }\n};","usedDeprecatedRules":[]},{"filePath":"/home/tyler/apps/bct-whitelabel/src/pages/api/inventory/release.ts","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/home/tyler/apps/bct-whitelabel/src/pages/api/inventory/reserve.ts","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/home/tyler/apps/bct-whitelabel/src/pages/api/kiosk/create-payment-intent.ts","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/home/tyler/apps/bct-whitelabel/src/pages/api/kiosk/generate-pin.ts","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/home/tyler/apps/bct-whitelabel/src/pages/api/kiosk/process-payment.ts","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/home/tyler/apps/bct-whitelabel/src/pages/api/kiosk/purchase.ts","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/home/tyler/apps/bct-whitelabel/src/pages/api/kiosk/send-pin-email.ts","messages":[{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":116,"column":12,"nodeType":null,"messageId":"unusedVar","endLine":116,"endColumn":17}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":1,"fixableErrorCount":0,"fixableWarningCount":0,"source":"export const prerender = false;\n\nimport type { APIRoute } from 'astro';\nimport { Resend } from 'resend';\n\nconst resendApiKey = import.meta.env.RESEND_API_KEY;\nconst resend = resendApiKey ? new Resend(resendApiKey) : null;\n\nexport const POST: APIRoute = async ({ request }) => {\n try {\n const { event, pin, email } = await request.json();\n \n if (!event || !pin || !email) {\n return new Response(JSON.stringify({ error: 'Event, PIN, and email are required' }), {\n status: 400,\n headers: { 'Content-Type': 'application/json' }\n });\n }\n\n // Validate PIN format\n if (!/^\\d{4}$/.test(pin)) {\n return new Response(JSON.stringify({ error: 'PIN must be exactly 4 digits' }), {\n status: 400,\n headers: { 'Content-Type': 'application/json' }\n });\n }\n\n // Check if Resend is configured\n if (!resend) {\n\n return new Response(JSON.stringify({ error: 'Email service not configured' }), {\n status: 500,\n headers: { 'Content-Type': 'application/json' }\n });\n }\n\n // Send email with PIN\n const { data, error } = await resend.emails.send({\n from: 'Black Canyon Tickets <noreply@blackcanyontickets.com>',\n to: [email],\n subject: `Sales Kiosk PIN for ${event.title}`,\n html: `\n <div style=\"font-family: Arial, sans-serif; max-width: 600px; margin: 0 auto; padding: 20px;\">\n <div style=\"background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; padding: 30px; border-radius: 10px 10px 0 0; text-align: center;\">\n <h1 style=\"margin: 0; font-size: 24px;\">🔐 Sales Kiosk PIN</h1>\n <p style=\"margin: 10px 0 0 0; opacity: 0.9;\">Black Canyon Tickets</p>\n </div>\n \n <div style=\"background: #f8f9fa; padding: 30px; border-radius: 0 0 10px 10px; border: 1px solid #e9ecef;\">\n <h2 style=\"color: #333; margin-top: 0;\">Your Sales Kiosk PIN</h2>\n \n <div style=\"background: white; padding: 20px; border-radius: 8px; border: 1px solid #e9ecef; margin: 20px 0;\">\n <p style=\"margin: 0 0 10px 0; color: #666;\"><strong>Event:</strong> ${event.title}</p>\n <p style=\"margin: 0 0 20px 0; color: #666;\"><strong>Generated:</strong> ${new Date().toLocaleString()}</p>\n \n <div style=\"text-align: center; margin: 30px 0;\">\n <div style=\"background: #f1f3f4; padding: 20px; border-radius: 8px; display: inline-block;\">\n <p style=\"margin: 0 0 10px 0; color: #666; font-size: 14px;\">Your 4-digit PIN</p>\n <p style=\"margin: 0; font-size: 36px; font-weight: bold; color: #333; letter-spacing: 8px; font-family: monospace;\">${pin}</p>\n </div>\n </div>\n </div>\n \n <div style=\"background: #e3f2fd; padding: 15px; border-radius: 8px; border-left: 4px solid #2196f3; margin: 20px 0;\">\n <h3 style=\"margin: 0 0 10px 0; color: #1976d2; font-size: 16px;\">📱 How to Use</h3>\n <ol style=\"margin: 0; padding-left: 20px; color: #666;\">\n <li>Navigate to the sales kiosk URL</li>\n <li>Enter this 4-digit PIN when prompted</li>\n <li>Access the touchscreen sales interface</li>\n <li>Lock the kiosk when finished</li>\n </ol>\n </div>\n \n <div style=\"background: #fff3e0; padding: 15px; border-radius: 8px; border-left: 4px solid #ff9800; margin: 20px 0;\">\n <h3 style=\"margin: 0 0 10px 0; color: #f57c00; font-size: 16px;\">⚠️ Important Notes</h3>\n <ul style=\"margin: 0; padding-left: 20px; color: #666;\">\n <li><strong>Security:</strong> This PIN expires in 24 hours</li>\n <li><strong>Access:</strong> Only share with authorized staff</li>\n <li><strong>Locking:</strong> Always lock the kiosk when not in use</li>\n <li><strong>Support:</strong> Generate a new PIN if needed</li>\n </ul>\n </div>\n \n <div style=\"text-align: center; margin: 30px 0;\">\n <a href=\"https://portal.blackcanyontickets.com/events/${event.id}/manage\" \n style=\"background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; padding: 12px 30px; text-decoration: none; border-radius: 6px; font-weight: bold; display: inline-block;\">\n Manage Event\n </a>\n </div>\n \n <p style=\"color: #666; font-size: 12px; text-align: center; margin: 30px 0 0 0;\">\n This PIN was generated automatically by Black Canyon Tickets.<br>\n If you did not request this PIN, please contact support immediately.\n </p>\n </div>\n </div>\n `\n });\n\n if (error) {\n\n return new Response(JSON.stringify({ error: 'Failed to send PIN email' }), {\n status: 500,\n headers: { 'Content-Type': 'application/json' }\n });\n }\n\n return new Response(JSON.stringify({ \n success: true, \n emailId: data?.id \n }), {\n status: 200,\n headers: { 'Content-Type': 'application/json' }\n });\n\n } catch (error) {\n\n return new Response(JSON.stringify({ error: 'Internal server error' }), {\n status: 500,\n headers: { 'Content-Type': 'application/json' }\n });\n }\n};","usedDeprecatedRules":[]},{"filePath":"/home/tyler/apps/bct-whitelabel/src/pages/api/kiosk/verify-pin.ts","messages":[{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":102,"column":12,"nodeType":null,"messageId":"unusedVar","endLine":102,"endColumn":17}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":1,"fixableErrorCount":0,"fixableWarningCount":0,"source":"export const prerender = false;\n\nimport type { APIRoute } from 'astro';\nimport { getSupabaseAdmin } from '../../../lib/supabase-admin';\n\nexport const POST: APIRoute = async ({ request }) => {\n try {\n const { eventSlug, pin } = await request.json();\n \n if (!eventSlug || !pin) {\n return new Response(JSON.stringify({ error: 'Event slug and PIN are required' }), {\n status: 400,\n headers: { 'Content-Type': 'application/json' }\n });\n }\n\n // Validate PIN format\n if (!/^\\d{4}$/.test(pin)) {\n return new Response(JSON.stringify({ error: 'PIN must be exactly 4 digits' }), {\n status: 400,\n headers: { 'Content-Type': 'application/json' }\n });\n }\n\n // Get admin client\n const supabaseAdmin = getSupabaseAdmin();\n \n // Find event by slug and verify PIN using admin client\n const { data: event, error: eventError } = await supabaseAdmin\n .from('events')\n .select('id, title, slug, kiosk_pin, kiosk_pin_created_at')\n .eq('slug', eventSlug)\n .single();\n\n if (eventError || !event) {\n return new Response(JSON.stringify({ error: 'Event not found' }), {\n status: 404,\n headers: { 'Content-Type': 'application/json' }\n });\n }\n\n // Check if PIN exists\n if (!event.kiosk_pin) {\n return new Response(JSON.stringify({ error: 'No PIN set for this event' }), {\n status: 400,\n headers: { 'Content-Type': 'application/json' }\n });\n }\n\n // Check if PIN is expired (24 hours)\n if (event.kiosk_pin_created_at) {\n const pinCreatedAt = new Date(event.kiosk_pin_created_at);\n const expirationTime = new Date(pinCreatedAt.getTime() + 24 * 60 * 60 * 1000); // 24 hours\n \n if (new Date() > expirationTime) {\n return new Response(JSON.stringify({ error: 'PIN has expired. Please generate a new one.' }), {\n status: 400,\n headers: { 'Content-Type': 'application/json' }\n });\n }\n }\n\n // Verify PIN\n const isValid = event.kiosk_pin === pin;\n\n // Log the access attempt\n const clientIP = request.headers.get('cf-connecting-ip') || \n request.headers.get('x-forwarded-for') || \n request.headers.get('x-real-ip') || \n 'unknown';\n \n const userAgent = request.headers.get('user-agent') || 'unknown';\n\n await supabaseAdmin\n .from('kiosk_access_logs')\n .insert({\n event_id: event.id,\n ip_address: clientIP,\n user_agent: userAgent,\n success: isValid\n });\n\n if (!isValid) {\n return new Response(JSON.stringify({ error: 'Invalid PIN' }), {\n status: 401,\n headers: { 'Content-Type': 'application/json' }\n });\n }\n\n return new Response(JSON.stringify({ \n success: true,\n event: {\n id: event.id,\n title: event.title,\n slug: event.slug\n }\n }), {\n status: 200,\n headers: { 'Content-Type': 'application/json' }\n });\n\n } catch (error) {\n\n return new Response(JSON.stringify({ error: 'Internal server error' }), {\n status: 500,\n headers: { 'Content-Type': 'application/json' }\n });\n }\n};","usedDeprecatedRules":[]},{"filePath":"/home/tyler/apps/bct-whitelabel/src/pages/api/location/preferences.ts","messages":[{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'url' is defined but never used. Allowed unused args must match /^_/u.","line":4,"column":48,"nodeType":null,"messageId":"unusedVar","endLine":4,"endColumn":51},{"ruleId":"no-undef","severity":2,"message":"'URL' is not defined.","line":6,"column":30,"nodeType":"Identifier","messageId":"undef","endLine":6,"endColumn":33},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":33,"column":12,"nodeType":null,"messageId":"unusedVar","endLine":33,"endColumn":17},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":109,"column":12,"nodeType":null,"messageId":"unusedVar","endLine":109,"endColumn":17}],"suppressedMessages":[],"errorCount":1,"fatalErrorCount":0,"warningCount":3,"fixableErrorCount":0,"fixableWarningCount":0,"source":"import type { APIRoute } from 'astro';\nimport { geolocationService } from '../../../lib/geolocation';\n\nexport const GET: APIRoute = async ({ request, url }) => {\n try {\n const searchParams = new URL(request.url).searchParams;\n const userId = searchParams.get('userId');\n const sessionId = searchParams.get('sessionId');\n \n if (!userId && !sessionId) {\n return new Response(JSON.stringify({\n success: false,\n error: 'userId or sessionId is required'\n }), {\n status: 400,\n headers: {\n 'Content-Type': 'application/json'\n }\n });\n }\n \n const preferences = await geolocationService.getUserLocationPreference(userId || undefined, sessionId || undefined);\n \n return new Response(JSON.stringify({\n success: true,\n data: preferences\n }), {\n status: 200,\n headers: {\n 'Content-Type': 'application/json'\n }\n });\n } catch (error) {\n\n return new Response(JSON.stringify({\n success: false,\n error: 'Failed to get location preferences'\n }), {\n status: 500,\n headers: {\n 'Content-Type': 'application/json'\n }\n });\n }\n};\n\nexport const POST: APIRoute = async ({ request }) => {\n try {\n const body = await request.json();\n const {\n userId,\n sessionId,\n preferredLatitude,\n preferredLongitude,\n preferredCity,\n preferredState,\n preferredCountry,\n preferredZipCode,\n searchRadiusMiles,\n locationSource\n } = body;\n \n if (!sessionId) {\n return new Response(JSON.stringify({\n success: false,\n error: 'sessionId is required'\n }), {\n status: 400,\n headers: {\n 'Content-Type': 'application/json'\n }\n });\n }\n \n if (!preferredLatitude || !preferredLongitude) {\n return new Response(JSON.stringify({\n success: false,\n error: 'preferredLatitude and preferredLongitude are required'\n }), {\n status: 400,\n headers: {\n 'Content-Type': 'application/json'\n }\n });\n }\n \n await geolocationService.saveUserLocationPreference({\n userId,\n sessionId,\n preferredLatitude,\n preferredLongitude,\n preferredCity,\n preferredState,\n preferredCountry,\n preferredZipCode,\n searchRadiusMiles: searchRadiusMiles || 50,\n locationSource: locationSource || 'manual'\n });\n \n return new Response(JSON.stringify({\n success: true,\n message: 'Location preferences saved successfully'\n }), {\n status: 200,\n headers: {\n 'Content-Type': 'application/json'\n }\n });\n } catch (error) {\n\n return new Response(JSON.stringify({\n success: false,\n error: 'Failed to save location preferences'\n }), {\n status: 500,\n headers: {\n 'Content-Type': 'application/json'\n }\n });\n }\n};","usedDeprecatedRules":[]},{"filePath":"/home/tyler/apps/bct-whitelabel/src/pages/api/organizations/process-signup.ts","messages":[{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":81,"column":14,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":81,"endColumn":17,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[2620,2623],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[2620,2623],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"no-empty","severity":1,"message":"Empty block statement.","line":123,"column":21,"nodeType":"BlockStatement","messageId":"unexpected","endLine":125,"endColumn":6,"suggestions":[{"messageId":"suggestComment","data":{"type":"block"},"fix":{"range":[3871,3877],"text":" /* empty */ "},"desc":"Add comment inside empty block statement."}]},{"ruleId":"no-empty","severity":1,"message":"Empty block statement.","line":131,"column":28,"nodeType":"BlockStatement","messageId":"unexpected","endLine":133,"endColumn":6,"suggestions":[{"messageId":"suggestComment","data":{"type":"block"},"fix":{"range":[4086,4092],"text":" /* empty */ "},"desc":"Add comment inside empty block statement."}]},{"ruleId":"no-empty","severity":1,"message":"Empty block statement.","line":142,"column":23,"nodeType":"BlockStatement","messageId":"unexpected","endLine":144,"endColumn":6,"suggestions":[{"messageId":"suggestComment","data":{"type":"block"},"fix":{"range":[4339,4345],"text":" /* empty */ "},"desc":"Add comment inside empty block statement."}]},{"ruleId":"no-empty","severity":1,"message":"Empty block statement.","line":178,"column":27,"nodeType":"BlockStatement","messageId":"unexpected","endLine":180,"endColumn":10,"suggestions":[{"messageId":"suggestComment","data":{"type":"block"},"fix":{"range":[5405,5415],"text":" /* empty */ "},"desc":"Add comment inside empty block statement."}]},{"ruleId":"no-empty","severity":1,"message":"Empty block statement.","line":194,"column":27,"nodeType":"BlockStatement","messageId":"unexpected","endLine":196,"endColumn":10,"suggestions":[{"messageId":"suggestComment","data":{"type":"block"},"fix":{"range":[5905,5915],"text":" /* empty */ "},"desc":"Add comment inside empty block statement."}]},{"ruleId":"no-empty","severity":1,"message":"Empty block statement.","line":210,"column":32,"nodeType":"BlockStatement","messageId":"unexpected","endLine":212,"endColumn":10,"suggestions":[{"messageId":"suggestComment","data":{"type":"block"},"fix":{"range":[6397,6407],"text":" /* empty */ "},"desc":"Add comment inside empty block statement."}]},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'emailError' is defined but never used.","line":214,"column":14,"nodeType":null,"messageId":"unusedVar","endLine":214,"endColumn":24}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":8,"fixableErrorCount":0,"fixableWarningCount":0,"source":"export const prerender = false;\n\nimport type { APIRoute } from 'astro';\nimport { supabase } from '../../../lib/supabase';\nimport { verifyAuth } from '../../../lib/auth';\nimport { logUserActivity } from '../../../lib/logger';\nimport { z } from 'zod';\n\nconst signupSchema = z.object({\n organization_name: z.string().min(1).max(255),\n business_type: z.enum(['individual', 'company']).default('individual'),\n business_description: z.string().max(500).optional(),\n website_url: z.string().url().optional().or(z.literal('')),\n phone_number: z.string().max(20).optional(),\n address_line1: z.string().max(255).optional(),\n address_line2: z.string().max(255).optional(),\n city: z.string().max(100).optional(),\n state: z.string().max(50).optional(),\n postal_code: z.string().max(20).optional(),\n country: z.string().length(2).default('US')\n});\n\nexport const POST: APIRoute = async ({ request }) => {\n try {\n // Verify authentication\n const authContext = await verifyAuth(request);\n if (!authContext) {\n return new Response(JSON.stringify({ error: 'Unauthorized' }), { \n status: 401,\n headers: { 'Content-Type': 'application/json' }\n });\n }\n\n const { user } = authContext;\n \n // Check if user already has an organization\n const { data: existingUser, error: userError } = await supabase\n .from('users')\n .select('organization_id')\n .eq('id', user.id)\n .single();\n\n if (userError) {\n return new Response(JSON.stringify({ error: 'Failed to check user status' }), { \n status: 500,\n headers: { 'Content-Type': 'application/json' }\n });\n }\n\n if (existingUser?.organization_id) {\n return new Response(JSON.stringify({ error: 'User already has an organization' }), { \n status: 400,\n headers: { 'Content-Type': 'application/json' }\n });\n }\n\n // Parse and validate request body\n const body = await request.json();\n const validationResult = signupSchema.safeParse(body);\n\n if (!validationResult.success) {\n return new Response(JSON.stringify({ \n error: 'Invalid request data',\n details: validationResult.error.errors\n }), { \n status: 400,\n headers: { 'Content-Type': 'application/json' }\n });\n }\n\n const { organization_name, ...profileData } = validationResult.data;\n\n // Clean up empty strings to null for optional fields\n const cleanedData = Object.entries(profileData).reduce((acc, [key, value]) => {\n if (value === '') {\n acc[key] = null;\n } else {\n acc[key] = value;\n }\n return acc;\n }, {} as any);\n\n // Create organization\n const { data: organization, error: orgError } = await supabase\n .from('organizations')\n .insert({\n name: organization_name,\n ...cleanedData,\n account_status: 'pending_approval',\n approval_score: 0\n })\n .select()\n .single();\n\n if (orgError) {\n\n return new Response(JSON.stringify({ error: 'Failed to create organization' }), { \n status: 500,\n headers: { 'Content-Type': 'application/json' }\n });\n }\n\n // Update user with organization ID\n const { error: updateUserError } = await supabase\n .from('users')\n .update({ organization_id: organization.id })\n .eq('id', user.id);\n\n if (updateUserError) {\n\n // Try to cleanup the organization\n await supabase.from('organizations').delete().eq('id', organization.id);\n return new Response(JSON.stringify({ error: 'Failed to complete organization setup' }), { \n status: 500,\n headers: { 'Content-Type': 'application/json' }\n });\n }\n\n // Calculate approval score and process auto-approval\n const { error: scoreError } = await supabase\n .rpc('calculate_approval_score', { org_id: organization.id });\n\n if (scoreError) {\n\n }\n\n // Process auto-approval\n const { data: autoApprovalResult, error: autoApprovalError } = await supabase\n .rpc('process_auto_approval', { org_id: organization.id });\n\n if (autoApprovalError) {\n\n }\n\n // Get updated organization with approval status\n const { data: updatedOrg, error: refreshError } = await supabase\n .from('organizations')\n .select('*')\n .eq('id', organization.id)\n .single();\n\n if (refreshError) {\n\n }\n\n const finalOrg = updatedOrg || organization;\n\n // Log the signup\n await logUserActivity({\n userId: user.id,\n action: 'organization_created',\n resourceType: 'organization',\n resourceId: organization.id,\n details: { \n organization_name,\n business_type: cleanedData.business_type,\n auto_approved: autoApprovalResult || false,\n approval_score: finalOrg.approval_score\n }\n });\n\n // Send appropriate email notification\n try {\n if (finalOrg.account_status === 'approved') {\n // Send approval notification email\n const response = await fetch(`${request.url.replace(/\\/api\\/.*/, '')}/api/emails/send-approval-notification`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': request.headers.get('Authorization') || ''\n },\n body: JSON.stringify({\n organization_id: organization.id,\n auto_approved: true\n })\n });\n\n if (!response.ok) {\n\n }\n } else {\n // Send application received email\n const response = await fetch(`${request.url.replace(/\\/api\\/.*/, '')}/api/emails/send-application-received`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': request.headers.get('Authorization') || ''\n },\n body: JSON.stringify({\n organization_id: organization.id\n })\n });\n\n if (!response.ok) {\n\n }\n\n // Send admin notification email\n const adminResponse = await fetch(`${request.url.replace(/\\/api\\/.*/, '')}/api/emails/send-admin-notification`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': request.headers.get('Authorization') || ''\n },\n body: JSON.stringify({\n organization_id: organization.id\n })\n });\n\n if (!adminResponse.ok) {\n\n }\n }\n } catch (emailError) {\n\n // Don't fail the request for email errors\n }\n\n return new Response(JSON.stringify({\n success: true,\n organization: finalOrg,\n auto_approved: finalOrg.account_status === 'approved',\n message: finalOrg.account_status === 'approved' \n ? 'Organization created and auto-approved! You can now start Stripe onboarding.'\n : 'Organization created successfully. Your application is pending approval.'\n }), {\n status: 201,\n headers: { 'Content-Type': 'application/json' }\n });\n\n } catch (error) {\n\n return new Response(JSON.stringify({ \n error: 'Failed to process organization signup',\n details: error instanceof Error ? error.message : 'Unknown error'\n }), { \n status: 500,\n headers: { 'Content-Type': 'application/json' }\n });\n }\n};","usedDeprecatedRules":[]},{"filePath":"/home/tyler/apps/bct-whitelabel/src/pages/api/organizations/update-profile.ts","messages":[{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":88,"column":14,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":88,"endColumn":17,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[2908,2911],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[2908,2911],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"no-empty","severity":1,"message":"Empty block statement.","line":110,"column":21,"nodeType":"BlockStatement","messageId":"unexpected","endLine":112,"endColumn":6,"suggestions":[{"messageId":"suggestComment","data":{"type":"block"},"fix":{"range":[3573,3579],"text":" /* empty */ "},"desc":"Add comment inside empty block statement."}]},{"ruleId":"no-empty","severity":1,"message":"Empty block statement.","line":119,"column":30,"nodeType":"BlockStatement","messageId":"unexpected","endLine":121,"endColumn":8,"suggestions":[{"messageId":"suggestComment","data":{"type":"block"},"fix":{"range":[3894,3902],"text":" /* empty */ "},"desc":"Add comment inside empty block statement."}]},{"ruleId":"no-empty","severity":1,"message":"Empty block statement.","line":143,"column":23,"nodeType":"BlockStatement","messageId":"unexpected","endLine":145,"endColumn":6,"suggestions":[{"messageId":"suggestComment","data":{"type":"block"},"fix":{"range":[4515,4521],"text":" /* empty */ "},"desc":"Add comment inside empty block statement."}]}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":4,"fixableErrorCount":0,"fixableWarningCount":0,"source":"export const prerender = false;\n\nimport type { APIRoute } from 'astro';\nimport { supabase, supabaseAdmin } from '../../../lib/supabase';\nimport { verifyAuth } from '../../../lib/auth';\nimport { logUserActivity } from '../../../lib/logger';\nimport { z } from 'zod';\n\nconst updateProfileSchema = z.object({\n name: z.string().min(1).max(255),\n business_type: z.enum(['individual', 'company']).optional(),\n business_description: z.string().max(500).optional(),\n website_url: z.string().url().optional().or(z.literal('')),\n phone_number: z.string().max(20).optional(),\n address_line1: z.string().max(255).optional(),\n address_line2: z.string().max(255).optional(),\n city: z.string().max(100).optional(),\n state: z.string().max(50).optional(),\n postal_code: z.string().max(20).optional(),\n country: z.string().length(2).default('US')\n});\n\nexport const PUT: APIRoute = async ({ request }) => {\n try {\n // Verify authentication\n const authContext = await verifyAuth(request);\n if (!authContext) {\n return new Response(JSON.stringify({ error: 'Unauthorized' }), { \n status: 401,\n headers: { 'Content-Type': 'application/json' }\n });\n }\n\n const { user } = authContext;\n \n // Get user's organization ID first (using admin client to bypass RLS)\n const { data: userData, error: userError } = await (supabaseAdmin || supabase)\n .from('users')\n .select('organization_id')\n .eq('id', user.id)\n .single();\n\n if (userError || !userData?.organization_id) {\n return new Response(JSON.stringify({ error: 'Organization not found' }), { \n status: 404,\n headers: { 'Content-Type': 'application/json' }\n });\n }\n\n // Now get the organization details\n const { data: organization, error: orgError } = await (supabaseAdmin || supabase)\n .from('organizations')\n .select('*')\n .eq('id', userData.organization_id)\n .single();\n\n if (orgError || !organization) {\n return new Response(JSON.stringify({ error: 'Organization details not found' }), { \n status: 404,\n headers: { 'Content-Type': 'application/json' }\n });\n }\n\n // Parse and validate request body\n const body = await request.json();\n const validationResult = updateProfileSchema.safeParse(body);\n\n if (!validationResult.success) {\n return new Response(JSON.stringify({ \n error: 'Invalid request data',\n details: validationResult.error.errors\n }), { \n status: 400,\n headers: { 'Content-Type': 'application/json' }\n });\n }\n\n const updateData = validationResult.data;\n\n // Clean up empty strings to null for optional fields\n const cleanedData = Object.entries(updateData).reduce((acc, [key, value]) => {\n if (value === '' && key !== 'name') {\n acc[key] = null;\n } else {\n acc[key] = value;\n }\n return acc;\n }, {} as any);\n\n // Update organization\n const { data: updatedOrg, error: updateError } = await (supabaseAdmin || supabase)\n .from('organizations')\n .update(cleanedData)\n .eq('id', organization.id)\n .select()\n .single();\n\n if (updateError) {\n\n return new Response(JSON.stringify({ error: 'Failed to update organization' }), { \n status: 500,\n headers: { 'Content-Type': 'application/json' }\n });\n }\n\n // Recalculate approval score after profile update\n const { error: scoreError } = await (supabaseAdmin || supabase)\n .rpc('calculate_approval_score', { org_id: organization.id });\n\n if (scoreError) {\n\n }\n\n // Check if organization should be auto-approved after profile update\n if (organization.account_status === 'pending_approval') {\n const { error: autoApprovalError } = await (supabaseAdmin || supabase)\n .rpc('process_auto_approval', { org_id: organization.id });\n\n if (autoApprovalError) {\n\n }\n }\n\n // Log the profile update\n await logUserActivity({\n userId: user.id,\n action: 'organization_profile_updated',\n resourceType: 'organization',\n resourceId: organization.id,\n details: { \n updated_fields: Object.keys(cleanedData),\n previous_status: organization.account_status\n }\n });\n\n // Get updated organization with new approval score\n const { data: refreshedOrg, error: refreshError } = await (supabaseAdmin || supabase)\n .from('organizations')\n .select('*')\n .eq('id', organization.id)\n .single();\n\n if (refreshError) {\n\n }\n\n return new Response(JSON.stringify({\n success: true,\n organization: refreshedOrg || updatedOrg,\n message: 'Organization profile updated successfully'\n }), {\n status: 200,\n headers: { 'Content-Type': 'application/json' }\n });\n\n } catch (error) {\n\n return new Response(JSON.stringify({ \n error: 'Failed to update organization profile',\n details: error instanceof Error ? error.message : 'Unknown error'\n }), { \n status: 500,\n headers: { 'Content-Type': 'application/json' }\n });\n }\n};","usedDeprecatedRules":[]},{"filePath":"/home/tyler/apps/bct-whitelabel/src/pages/api/presale/validate.ts","messages":[{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'ticketTypesError' is assigned a value but never used. Allowed unused vars must match /^_/u.","line":46,"column":49,"nodeType":null,"messageId":"unusedVar","endLine":46,"endColumn":65}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":1,"fixableErrorCount":0,"fixableWarningCount":0,"source":"import type { APIRoute } from 'astro';\nimport { supabase } from '../../../lib/supabase';\n\nexport const prerender = false;\n\nexport const POST: APIRoute = async ({ request }) => {\n try {\n const body = await request.json();\n const { code, event_id, customer_email, customer_session } = body;\n\n if (!code || !event_id) {\n return new Response(JSON.stringify({ \n error: 'Code and event_id are required' \n }), {\n status: 400,\n headers: { 'Content-Type': 'application/json' }\n });\n }\n\n // Validate presale code using database function\n const { data, error } = await supabase\n .rpc('validate_presale_code', {\n p_code: code,\n p_event_id: event_id,\n p_customer_email: customer_email || null,\n p_customer_session: customer_session || null\n });\n\n if (error) {\n throw error;\n }\n\n const result = data[0];\n\n if (!result.is_valid) {\n return new Response(JSON.stringify({ \n success: false,\n error: result.error_message \n }), {\n status: 400,\n headers: { 'Content-Type': 'application/json' }\n });\n }\n\n // Get accessible ticket types for this presale code\n const { data: accessibleTicketTypes, error: ticketTypesError } = await supabase\n .from('presale_code_ticket_types')\n .select(`\n ticket_type_id,\n ticket_types (\n id,\n name,\n description,\n price,\n presale_start_time,\n presale_end_time\n )\n `)\n .eq('presale_code_id', result.presale_code_id);\n\n return new Response(JSON.stringify({\n success: true,\n presale_code: {\n id: result.presale_code_id,\n discount_type: result.discount_type,\n discount_value: result.discount_value,\n uses_remaining: result.uses_remaining,\n customer_uses_remaining: result.customer_uses_remaining\n },\n accessible_ticket_types: accessibleTicketTypes?.map(att => att.ticket_types) || []\n }), {\n status: 200,\n headers: { 'Content-Type': 'application/json' }\n });\n } catch (error) {\n\n return new Response(JSON.stringify({ \n error: 'Failed to validate presale code',\n details: error.message \n }), {\n status: 500,\n headers: { 'Content-Type': 'application/json' }\n });\n }\n};","usedDeprecatedRules":[]},{"filePath":"/home/tyler/apps/bct-whitelabel/src/pages/api/printed-tickets.ts","messages":[{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'request' is defined but never used. Allowed unused args must match /^_/u.","line":4,"column":44,"nodeType":null,"messageId":"unusedVar","endLine":4,"endColumn":51},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":59,"column":12,"nodeType":null,"messageId":"unusedVar","endLine":59,"endColumn":17},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":167,"column":12,"nodeType":null,"messageId":"unusedVar","endLine":167,"endColumn":17},{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":187,"column":23,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":187,"endColumn":26,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[5000,5003],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[5000,5003],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":208,"column":12,"nodeType":null,"messageId":"unusedVar","endLine":208,"endColumn":17}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":5,"fixableErrorCount":0,"fixableWarningCount":0,"source":"import type { APIRoute } from 'astro';\nimport { supabase } from '../../lib/supabase';\n\nexport const GET: APIRoute = async ({ url, request }) => {\n try {\n let eventId = url.searchParams.get('event_id');\n \n // Fallback: try to extract from URL path if query param doesn't work\n if (!eventId) {\n const urlParts = url.pathname.split('/');\n const eventIdIndex = urlParts.findIndex(part => part === 'api') + 1;\n if (eventIdIndex > 0 && urlParts[eventIdIndex] === 'printed-tickets' && urlParts[eventIdIndex + 1]) {\n eventId = urlParts[eventIdIndex + 1];\n }\n }\n \n // Debug: Log what we received\n\n if (!eventId) {\n return new Response(JSON.stringify({ \n success: false, \n error: 'Event ID is required',\n debug: {\n url: url.toString(),\n pathname: url.pathname,\n searchParams: url.searchParams.toString(),\n allParams: Object.fromEntries(url.searchParams.entries())\n }\n }), { status: 400 });\n }\n\n const { data: tickets, error } = await supabase\n .from('printed_tickets')\n .select(`\n *,\n ticket_types (\n name,\n price\n ),\n events (\n title\n )\n `)\n .eq('event_id', eventId)\n .order('created_at', { ascending: false });\n\n if (error) {\n return new Response(JSON.stringify({ \n success: false, \n error: 'Failed to fetch printed tickets' \n }), { status: 500 });\n }\n\n return new Response(JSON.stringify({ \n success: true, \n tickets: tickets || []\n }), { status: 200 });\n\n } catch (error) {\n\n return new Response(JSON.stringify({ \n success: false, \n error: 'Internal server error' \n }), { status: 500 });\n }\n};\n\nexport const POST: APIRoute = async ({ request }) => {\n try {\n const body = await request.json();\n \n // Handle fetch action (getting printed tickets)\n if (body.action === 'fetch') {\n const eventId = body.event_id;\n\n if (!eventId) {\n return new Response(JSON.stringify({ \n success: false, \n error: 'Event ID is required for fetch action' \n }), { status: 400 });\n }\n\n const { data: tickets, error } = await supabase\n .from('printed_tickets')\n .select(`\n *,\n ticket_types (\n name,\n price\n ),\n events (\n title\n )\n `)\n .eq('event_id', eventId)\n .order('created_at', { ascending: false });\n\n if (error) {\n\n return new Response(JSON.stringify({ \n success: false, \n error: 'Failed to fetch printed tickets' \n }), { status: 500 });\n }\n\n return new Response(JSON.stringify({ \n success: true, \n tickets: tickets || []\n }), { status: 200 });\n }\n \n // Handle add action (adding new printed tickets)\n const { barcodes, event_id, ticket_type_id, batch_number, notes, issued_by } = body;\n\n if (!barcodes || !Array.isArray(barcodes) || barcodes.length === 0) {\n return new Response(JSON.stringify({ \n success: false, \n error: 'Barcodes array is required' \n }), { status: 400 });\n }\n\n if (!event_id || !ticket_type_id) {\n return new Response(JSON.stringify({ \n success: false, \n error: 'Event ID and ticket type ID are required' \n }), { status: 400 });\n }\n\n // Prepare tickets for insertion\n const ticketsToInsert = barcodes.map(barcode => ({\n barcode_number: barcode.trim(),\n event_id,\n ticket_type_id,\n batch_number: batch_number || null,\n notes: notes || null,\n issued_by: issued_by || null,\n status: 'valid'\n }));\n\n // Insert tickets\n const { data: insertedTickets, error: insertError } = await supabase\n .from('printed_tickets')\n .insert(ticketsToInsert)\n .select();\n\n if (insertError) {\n // Handle duplicate barcode error\n if (insertError.code === '23505') {\n return new Response(JSON.stringify({ \n success: false, \n error: 'One or more barcodes already exist' \n }), { status: 409 });\n }\n \n return new Response(JSON.stringify({ \n success: false, \n error: 'Failed to insert printed tickets' \n }), { status: 500 });\n }\n\n return new Response(JSON.stringify({ \n success: true, \n message: `Successfully added ${insertedTickets.length} printed tickets`,\n tickets: insertedTickets\n }), { status: 201 });\n\n } catch (error) {\n\n return new Response(JSON.stringify({ \n success: false, \n error: 'Internal server error' \n }), { status: 500 });\n }\n};\n\nexport const PUT: APIRoute = async ({ request }) => {\n try {\n const { id, status, notes } = await request.json();\n\n if (!id) {\n return new Response(JSON.stringify({ \n success: false, \n error: 'Ticket ID is required' \n }), { status: 400 });\n }\n\n const updateData: any = {};\n if (status) updateData.status = status;\n if (notes !== undefined) updateData.notes = notes;\n\n const { error } = await supabase\n .from('printed_tickets')\n .update(updateData)\n .eq('id', id);\n\n if (error) {\n return new Response(JSON.stringify({ \n success: false, \n error: 'Failed to update printed ticket' \n }), { status: 500 });\n }\n\n return new Response(JSON.stringify({ \n success: true, \n message: 'Printed ticket updated successfully'\n }), { status: 200 });\n\n } catch (error) {\n\n return new Response(JSON.stringify({ \n success: false, \n error: 'Internal server error' \n }), { status: 500 });\n }\n};","usedDeprecatedRules":[]},{"filePath":"/home/tyler/apps/bct-whitelabel/src/pages/api/printed-tickets/[eventId].ts","messages":[{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":49,"column":12,"nodeType":null,"messageId":"unusedVar","endLine":49,"endColumn":17}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":1,"fixableErrorCount":0,"fixableWarningCount":0,"source":"export const prerender = false;\n\nimport type { APIRoute } from 'astro';\nimport { supabase } from '../../../lib/supabase';\n\nexport const GET: APIRoute = async ({ params }) => {\n try {\n const eventId = params.eventId;\n\n if (!eventId) {\n return new Response(JSON.stringify({ \n success: false, \n error: 'Event ID is required',\n debug: {\n params: params,\n eventId: eventId\n }\n }), { status: 400 });\n }\n\n const { data: tickets, error } = await supabase\n .from('printed_tickets')\n .select(`\n *,\n ticket_types (\n name,\n price\n ),\n events (\n title\n )\n `)\n .eq('event_id', eventId)\n .order('created_at', { ascending: false });\n\n if (error) {\n\n return new Response(JSON.stringify({ \n success: false, \n error: 'Failed to fetch printed tickets' \n }), { status: 500 });\n }\n\n return new Response(JSON.stringify({ \n success: true, \n tickets: tickets || []\n }), { status: 200 });\n\n } catch (error) {\n\n return new Response(JSON.stringify({ \n success: false, \n error: 'Internal server error' \n }), { status: 500 });\n }\n};","usedDeprecatedRules":[]},{"filePath":"/home/tyler/apps/bct-whitelabel/src/pages/api/public/events.ts","messages":[{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":11,"column":15,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":11,"endColumn":18,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[563,566],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[563,566],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":16,"column":10,"nodeType":null,"messageId":"unusedVar","endLine":16,"endColumn":15},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":205,"column":12,"nodeType":null,"messageId":"unusedVar","endLine":205,"endColumn":17}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":3,"fixableErrorCount":0,"fixableWarningCount":0,"source":"import type { APIRoute } from 'astro';\nimport { createClient } from '@supabase/supabase-js';\nimport { logAPIRequest, logSecurityEvent } from '../../../lib/logger';\nimport { checkRateLimit } from '../../../lib/auth';\n\n// Handle missing environment variables gracefully\nconst supabaseUrl = process.env.SUPABASE_URL || import.meta.env.SUPABASE_URL || 'https://zctjaivtfyfxokfaemek.supabase.co';\nconst supabaseServiceKey = process.env.SUPABASE_SERVICE_KEY || import.meta.env.SUPABASE_SERVICE_KEY || '';\n\n// Create supabase client with fallback handling\nlet supabase: any = null;\ntry {\n if (supabaseUrl && supabaseServiceKey) {\n supabase = createClient(supabaseUrl, supabaseServiceKey);\n }\n} catch (error) {\n // Silently handle Supabase initialization errors\n}\n\ninterface PublicEvent {\n id: string;\n title: string;\n description: string;\n venue: string;\n start_time: string;\n end_time: string;\n image_url?: string;\n slug: string;\n ticket_url: string;\n organizer_name: string;\n category?: string;\n price_range?: string;\n is_featured: boolean;\n}\n\nexport const GET: APIRoute = async ({ request, url }) => {\n const startTime = Date.now();\n const clientIP = request.headers.get('x-forwarded-for') || request.headers.get('x-real-ip') || 'unknown';\n const userAgent = request.headers.get('user-agent') || 'unknown';\n \n try {\n // Check if Supabase is available\n if (!supabase) {\n return new Response(JSON.stringify({\n success: true,\n events: [],\n total: 0,\n hasMore: false,\n message: 'Service temporarily unavailable'\n }), {\n status: 200,\n headers: {\n 'Content-Type': 'application/json',\n 'Cache-Control': 'public, max-age=60',\n 'Access-Control-Allow-Origin': '*'\n }\n });\n }\n // Rate limiting - 100 requests per hour per IP\n if (!checkRateLimit(clientIP, 100, 3600000)) {\n logSecurityEvent({\n type: 'rate_limit',\n ipAddress: clientIP,\n userAgent,\n severity: 'medium',\n details: { endpoint: '/api/public/events', limit: 100 }\n });\n\n return new Response(JSON.stringify({ \n error: 'Rate limit exceeded. Please try again later.' \n }), {\n status: 429,\n headers: { 'Content-Type': 'application/json' }\n });\n }\n\n // Parse query parameters\n const searchParams = url.searchParams;\n const limit = Math.min(parseInt(searchParams.get('limit') || '50'), 100); // Max 100 events\n const offset = parseInt(searchParams.get('offset') || '0');\n const category = searchParams.get('category');\n const search = searchParams.get('search');\n const featured = searchParams.get('featured') === 'true';\n const upcoming = searchParams.get('upcoming') !== 'false'; // Default to upcoming only\n\n // Build query\n let query = supabase\n .from('events')\n .select(`\n id,\n title,\n description,\n venue,\n start_time,\n end_time,\n image_url,\n slug,\n category,\n is_featured,\n organizations!inner(name)\n `)\n .eq('is_published', true)\n .eq('is_public', true) // Only show public events\n .order('start_time', { ascending: true });\n\n // Filter upcoming events\n if (upcoming) {\n query = query.gte('start_time', new Date().toISOString());\n }\n\n // Filter by category\n if (category) {\n query = query.eq('category', category);\n }\n\n // Filter featured events\n if (featured) {\n query = query.eq('is_featured', true);\n }\n\n // Search functionality\n if (search && search.trim()) {\n const searchTerm = search.trim();\n query = query.or(`title.ilike.%${searchTerm}%,description.ilike.%${searchTerm}%,venue.ilike.%${searchTerm}%`);\n }\n\n // Apply pagination\n query = query.range(offset, offset + limit - 1);\n\n const { data: events, error } = await query;\n\n if (error) {\n // Silently handle database errors\n return new Response(JSON.stringify({\n success: true,\n events: [],\n total: 0,\n hasMore: false,\n message: 'Unable to load events at this time'\n }), {\n status: 200,\n headers: { \n 'Content-Type': 'application/json',\n 'Access-Control-Allow-Origin': '*'\n }\n });\n }\n\n // Transform data for public consumption\n const publicEvents: PublicEvent[] = events.map(event => {\n // Calculate price range from tickets (this would need a separate query in production)\n const priceRange = 'Free - $50'; // Placeholder - implement based on ticket prices\n \n return {\n id: event.id,\n title: event.title,\n description: event.description?.substring(0, 200) + (event.description?.length > 200 ? '...' : ''), // Truncate for security\n venue: event.venue,\n start_time: event.start_time,\n end_time: event.end_time,\n image_url: event.image_url,\n slug: event.slug,\n ticket_url: `${process.env.PUBLIC_APP_URL || import.meta.env.PUBLIC_APP_URL || 'http://localhost:4321'}/e/${event.slug}`,\n organizer_name: event.organizations?.name || 'Event Organizer',\n category: event.category,\n price_range: priceRange,\n is_featured: event.is_featured || false\n };\n });\n\n const responseTime = Date.now() - startTime;\n\n // Log API request\n logAPIRequest({\n method: 'GET',\n url: url.pathname + url.search,\n statusCode: 200,\n responseTime,\n ipAddress: clientIP,\n userAgent\n });\n\n return new Response(JSON.stringify({\n success: true,\n events: publicEvents,\n total: publicEvents.length,\n hasMore: publicEvents.length === limit,\n filters: {\n category,\n search,\n featured,\n upcoming\n }\n }), {\n status: 200,\n headers: {\n 'Content-Type': 'application/json',\n 'Cache-Control': 'public, max-age=300', // Cache for 5 minutes\n 'Access-Control-Allow-Origin': '*',\n 'Access-Control-Allow-Methods': 'GET',\n 'Access-Control-Allow-Headers': 'Content-Type'\n }\n });\n\n } catch (error) {\n // Silently handle API errors\n const responseTime = Date.now() - startTime;\n \n logAPIRequest({\n method: 'GET',\n url: url.pathname + url.search,\n statusCode: 200,\n responseTime,\n ipAddress: clientIP,\n userAgent\n });\n\n return new Response(JSON.stringify({\n success: true,\n events: [],\n total: 0,\n hasMore: false,\n message: 'Service temporarily unavailable'\n }), {\n status: 200,\n headers: { \n 'Content-Type': 'application/json',\n 'Access-Control-Allow-Origin': '*'\n }\n });\n }\n};\n\n// OPTIONS handler for CORS\nexport const OPTIONS: APIRoute = async () => {\n return new Response(null, {\n status: 200,\n headers: {\n 'Access-Control-Allow-Origin': '*',\n 'Access-Control-Allow-Methods': 'GET, OPTIONS',\n 'Access-Control-Allow-Headers': 'Content-Type',\n 'Access-Control-Max-Age': '86400'\n }\n });\n};","usedDeprecatedRules":[]},{"filePath":"/home/tyler/apps/bct-whitelabel/src/pages/api/refunds/process.ts","messages":[{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'stripeError' is defined but never used.","line":183,"column":16,"nodeType":null,"messageId":"unusedVar","endLine":183,"endColumn":27},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":213,"column":12,"nodeType":null,"messageId":"unusedVar","endLine":213,"endColumn":17}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":2,"fixableErrorCount":0,"fixableWarningCount":0,"source":"export const prerender = false;\n\nimport type { APIRoute } from 'astro';\nimport { supabase } from '../../../lib/supabase';\nimport { requireAuth, getClientIP, checkRateLimit, createAuthResponse } from '../../../lib/auth';\nimport { validateRequest } from '../../../lib/validation';\nimport { stripe } from '../../../lib/stripe';\nimport { z } from 'zod';\n\n// Validation schema for refund requests\nconst refundSchema = z.object({\n ticket_id: z.string().uuid(),\n refund_amount: z.number().positive().max(10000), // Max $100 refund\n reason: z.string().min(5).max(500) // Reasonable reason length\n});\n\nexport const POST: APIRoute = async ({ request }) => {\n try {\n // Rate limiting for refund requests\n const clientIP = getClientIP(request);\n if (!checkRateLimit(`refund:${clientIP}`, 3, 300000)) { // 3 requests per 5 minutes\n return createAuthResponse({ error: 'Rate limit exceeded for refund requests' }, 429);\n }\n\n // Require authentication\n const auth = await requireAuth(request);\n \n const body = await request.json();\n \n // Validate input\n const validation = validateRequest(refundSchema, body);\n if (!validation.success) {\n return createAuthResponse({ \n error: 'Invalid refund request',\n details: validation.error\n }, 400);\n }\n\n const { ticket_id, refund_amount, reason } = validation.data;\n\n // Get ticket with purchase attempt info\n const { data: ticket, error: ticketError } = await supabase\n .from('tickets')\n .select(`\n *,\n purchase_attempts (\n id,\n stripe_payment_intent_id,\n total_amount,\n purchaser_email,\n purchaser_name\n )\n `)\n .eq('id', ticket_id)\n .single();\n\n if (ticketError || !ticket) {\n return createAuthResponse({ error: 'Ticket not found' }, 404);\n }\n\n // Check if ticket is already refunded\n if (ticket.refund_status !== 'none') {\n return createAuthResponse({ \n error: 'Ticket already has a refund request' \n }, 400);\n }\n\n // Validate refund amount\n const ticketPrice = parseFloat(ticket.price);\n if (refund_amount > ticketPrice) {\n return createAuthResponse({ \n error: 'Refund amount cannot exceed ticket price' \n }, 400);\n }\n\n // Create refund record\n const { data: refundRecord, error: refundError } = await supabase\n .from('refunds')\n .insert({\n purchase_attempt_id: ticket.purchase_attempt_id,\n ticket_id: ticket_id,\n amount: refund_amount,\n reason: reason,\n status: 'pending',\n processed_by: auth.user.id\n })\n .select()\n .single();\n\n if (refundError) {\n throw refundError;\n }\n\n // Update ticket status\n const { error: ticketUpdateError } = await supabase\n .from('tickets')\n .update({\n refund_status: 'requested',\n refund_amount: refund_amount,\n refund_requested_at: new Date().toISOString(),\n refund_reason: reason,\n refunded_by: auth.user.id\n })\n .eq('id', ticket_id);\n\n if (ticketUpdateError) {\n throw ticketUpdateError;\n }\n\n // Process Stripe refund if payment intent exists\n let stripeRefund = null;\n if (ticket.purchase_attempts?.stripe_payment_intent_id) {\n try {\n // Update refund status to processing\n await supabase\n .from('refunds')\n .update({ status: 'processing' })\n .eq('id', refundRecord.id);\n\n await supabase\n .from('tickets')\n .update({ refund_status: 'processing' })\n .eq('id', ticket_id);\n\n // Create Stripe refund\n stripeRefund = await stripe!.refunds.create({\n payment_intent: ticket.purchase_attempts.stripe_payment_intent_id,\n amount: Math.round(refund_amount * 100), // Convert to cents\n reason: 'requested_by_customer',\n metadata: {\n ticket_id: ticket_id,\n refund_record_id: refundRecord.id,\n reason: reason\n }\n });\n\n // Update refund with Stripe ID\n await supabase\n .from('refunds')\n .update({ \n stripe_refund_id: stripeRefund.id,\n status: 'completed',\n processed_at: new Date().toISOString()\n })\n .eq('id', refundRecord.id);\n\n // Update ticket status to completed\n await supabase\n .from('tickets')\n .update({\n refund_status: 'completed',\n refund_completed_at: new Date().toISOString(),\n stripe_refund_id: stripeRefund.id\n })\n .eq('id', ticket_id);\n\n // Check if all tickets for this purchase are refunded\n const { data: allTickets } = await supabase\n .from('tickets')\n .select('refund_status')\n .eq('purchase_attempt_id', ticket.purchase_attempt_id);\n\n if (allTickets && allTickets.every(t => t.refund_status === 'completed')) {\n // Mark entire purchase as fully refunded\n await supabase\n .from('purchase_attempts')\n .update({ \n refund_status: 'full',\n refund_completed_at: new Date().toISOString()\n })\n .eq('id', ticket.purchase_attempt_id);\n } else if (allTickets && allTickets.some(t => t.refund_status === 'completed')) {\n // Mark purchase as partially refunded\n await supabase\n .from('purchase_attempts')\n .update({ \n refund_status: 'partial',\n refund_requested_at: new Date().toISOString()\n })\n .eq('id', ticket.purchase_attempt_id);\n }\n\n } catch (stripeError) {\n\n // Update refund status to failed\n await supabase\n .from('refunds')\n .update({ status: 'failed' })\n .eq('id', refundRecord.id);\n\n await supabase\n .from('tickets')\n .update({ refund_status: 'failed' })\n .eq('id', ticket_id);\n\n return createAuthResponse({ \n error: 'Failed to process refund with Stripe'\n // Don't expose internal error details\n }, 500);\n }\n }\n\n return createAuthResponse({\n success: true,\n refund: {\n id: refundRecord.id,\n amount: refund_amount,\n status: stripeRefund ? 'completed' : 'pending',\n stripe_refund_id: stripeRefund?.id\n }\n });\n\n } catch (error) {\n\n return createAuthResponse({ \n error: 'Failed to process refund'\n // Don't expose internal error details in production\n }, 500);\n }\n};","usedDeprecatedRules":[]},{"filePath":"/home/tyler/apps/bct-whitelabel/src/pages/api/scanner-lock/disable.ts","messages":[{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":84,"column":12,"nodeType":null,"messageId":"unusedVar","endLine":84,"endColumn":17}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":1,"fixableErrorCount":0,"fixableWarningCount":0,"source":"import type { APIRoute } from 'astro';\nimport { supabase } from '../../../lib/supabase';\n\nexport const POST: APIRoute = async ({ request }) => {\n try {\n const { eventId } = await request.json();\n \n // Verify user authentication\n const authHeader = request.headers.get('Authorization');\n if (!authHeader) {\n return new Response(JSON.stringify({ error: 'Unauthorized' }), {\n status: 401,\n headers: { 'Content-Type': 'application/json' }\n });\n }\n \n const { data: { user }, error: authError } = await supabase.auth.getUser(authHeader.replace('Bearer ', ''));\n if (authError || !user) {\n return new Response(JSON.stringify({ error: 'Unauthorized' }), {\n status: 401,\n headers: { 'Content-Type': 'application/json' }\n });\n }\n \n // Get user's organization\n const { data: userData, error: userError } = await supabase\n .from('users')\n .select('organization_id')\n .eq('id', user.id)\n .single();\n \n if (userError || !userData?.organization_id) {\n return new Response(JSON.stringify({ error: 'User not found or not in organization' }), {\n status: 403,\n headers: { 'Content-Type': 'application/json' }\n });\n }\n \n // Verify event belongs to user's organization\n const { data: event, error: eventError } = await supabase\n .from('events')\n .select('id, organization_id, scanner_lock_enabled')\n .eq('id', eventId)\n .eq('organization_id', userData.organization_id)\n .single();\n \n if (eventError || !event) {\n return new Response(JSON.stringify({ error: 'Event not found' }), {\n status: 404,\n headers: { 'Content-Type': 'application/json' }\n });\n }\n \n // Check if scanner lock is enabled\n if (!event.scanner_lock_enabled) {\n return new Response(JSON.stringify({ error: 'Scanner lock is not enabled for this event' }), {\n status: 400,\n headers: { 'Content-Type': 'application/json' }\n });\n }\n \n // Disable scanner lock using database function\n const { data: disableResult, error: disableError } = await supabase\n .rpc('disable_scanner_lock', {\n p_event_id: eventId\n });\n \n if (disableError || !disableResult) {\n\n return new Response(JSON.stringify({ error: 'Failed to disable scanner lock' }), {\n status: 500,\n headers: { 'Content-Type': 'application/json' }\n });\n }\n \n return new Response(JSON.stringify({\n success: true,\n message: 'Scanner lock disabled successfully'\n }), {\n status: 200,\n headers: { 'Content-Type': 'application/json' }\n });\n \n } catch (error) {\n\n return new Response(JSON.stringify({ error: 'Internal server error' }), {\n status: 500,\n headers: { 'Content-Type': 'application/json' }\n });\n }\n};","usedDeprecatedRules":[]},{"filePath":"/home/tyler/apps/bct-whitelabel/src/pages/api/scanner-lock/setup.ts","messages":[{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'generateRandomPin' is defined but never used. Allowed unused vars must match /^_/u.","line":3,"column":19,"nodeType":null,"messageId":"unusedVar","endLine":3,"endColumn":36},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'ScannerLockData' is defined but never used. Allowed unused vars must match /^_/u.","line":3,"column":56,"nodeType":null,"messageId":"unusedVar","endLine":3,"endColumn":71},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'organizerEmail' is assigned a value but never used. Allowed unused vars must match /^_/u.","line":7,"column":27,"nodeType":null,"messageId":"unusedVar","endLine":7,"endColumn":41},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":105,"column":12,"nodeType":null,"messageId":"unusedVar","endLine":105,"endColumn":17}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":4,"fixableErrorCount":0,"fixableWarningCount":0,"source":"import type { APIRoute } from 'astro';\nimport { supabase } from '../../../lib/supabase';\nimport { hashPin, generateRandomPin, validatePin, type ScannerLockData } from '../../../lib/scanner-lock';\n\nexport const POST: APIRoute = async ({ request }) => {\n try {\n const { eventId, pin, organizerEmail } = await request.json();\n \n // Validate PIN format\n if (!pin || !validatePin(pin)) {\n return new Response(JSON.stringify({ \n error: 'PIN must be exactly 4 digits' \n }), {\n status: 400,\n headers: { 'Content-Type': 'application/json' }\n });\n }\n \n // Verify user authentication\n const authHeader = request.headers.get('Authorization');\n if (!authHeader) {\n return new Response(JSON.stringify({ error: 'Unauthorized' }), {\n status: 401,\n headers: { 'Content-Type': 'application/json' }\n });\n }\n \n const { data: { user }, error: authError } = await supabase.auth.getUser(authHeader.replace('Bearer ', ''));\n if (authError || !user) {\n return new Response(JSON.stringify({ error: 'Unauthorized' }), {\n status: 401,\n headers: { 'Content-Type': 'application/json' }\n });\n }\n \n // Get user's organization\n const { data: userData, error: userError } = await supabase\n .from('users')\n .select('organization_id')\n .eq('id', user.id)\n .single();\n \n if (userError || !userData?.organization_id) {\n return new Response(JSON.stringify({ error: 'User not found or not in organization' }), {\n status: 403,\n headers: { 'Content-Type': 'application/json' }\n });\n }\n \n // Verify event belongs to user's organization\n const { data: event, error: eventError } = await supabase\n .from('events')\n .select('id, title, start_time, organization_id, scanner_lock_enabled')\n .eq('id', eventId)\n .eq('organization_id', userData.organization_id)\n .single();\n \n if (eventError || !event) {\n return new Response(JSON.stringify({ error: 'Event not found' }), {\n status: 404,\n headers: { 'Content-Type': 'application/json' }\n });\n }\n \n // Check if scanner lock is already enabled\n if (event.scanner_lock_enabled) {\n return new Response(JSON.stringify({ error: 'Scanner lock is already enabled for this event' }), {\n status: 400,\n headers: { 'Content-Type': 'application/json' }\n });\n }\n \n // Hash the PIN\n const pinHash = await hashPin(pin);\n \n // Setup scanner lock using database function\n const { data: setupResult, error: setupError } = await supabase\n .rpc('setup_scanner_lock', {\n p_event_id: eventId,\n p_pin_hash: pinHash\n });\n \n if (setupError || !setupResult) {\n\n return new Response(JSON.stringify({ error: 'Failed to setup scanner lock' }), {\n status: 500,\n headers: { 'Content-Type': 'application/json' }\n });\n }\n \n // Return success response with event details for email\n return new Response(JSON.stringify({\n success: true,\n event: {\n id: event.id,\n title: event.title,\n start_time: event.start_time\n },\n pin // Return the PIN for email purposes - this will be sent securely\n }), {\n status: 200,\n headers: { 'Content-Type': 'application/json' }\n });\n \n } catch (error) {\n\n return new Response(JSON.stringify({ error: 'Internal server error' }), {\n status: 500,\n headers: { 'Content-Type': 'application/json' }\n });\n }\n};","usedDeprecatedRules":[]},{"filePath":"/home/tyler/apps/bct-whitelabel/src/pages/api/scanner-lock/verify.ts","messages":[{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'UnlockAttemptData' is defined but never used. Allowed unused vars must match /^_/u.","line":3,"column":41,"nodeType":null,"messageId":"unusedVar","endLine":3,"endColumn":58},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":105,"column":12,"nodeType":null,"messageId":"unusedVar","endLine":105,"endColumn":17}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":2,"fixableErrorCount":0,"fixableWarningCount":0,"source":"import type { APIRoute } from 'astro';\nimport { supabase } from '../../../lib/supabase';\nimport { verifyPin, getDeviceInfo, type UnlockAttemptData } from '../../../lib/scanner-lock';\n\nexport const POST: APIRoute = async ({ request }) => {\n try {\n const { eventId, pin } = await request.json();\n \n // Get IP address and user agent for logging\n const ipAddress = request.headers.get('x-forwarded-for') || \n request.headers.get('cf-connecting-ip') || \n 'unknown';\n const userAgent = request.headers.get('user-agent') || 'unknown';\n const deviceInfo = getDeviceInfo(userAgent);\n \n // Verify user authentication\n const authHeader = request.headers.get('Authorization');\n if (!authHeader) {\n return new Response(JSON.stringify({ error: 'Unauthorized' }), {\n status: 401,\n headers: { 'Content-Type': 'application/json' }\n });\n }\n \n const { data: { user }, error: authError } = await supabase.auth.getUser(authHeader.replace('Bearer ', ''));\n if (authError || !user) {\n return new Response(JSON.stringify({ error: 'Unauthorized' }), {\n status: 401,\n headers: { 'Content-Type': 'application/json' }\n });\n }\n \n // Get user's organization\n const { data: userData, error: userError } = await supabase\n .from('users')\n .select('organization_id')\n .eq('id', user.id)\n .single();\n \n if (userError || !userData?.organization_id) {\n return new Response(JSON.stringify({ error: 'User not found or not in organization' }), {\n status: 403,\n headers: { 'Content-Type': 'application/json' }\n });\n }\n \n // Get event with scanner lock info\n const { data: event, error: eventError } = await supabase\n .from('events')\n .select('id, title, organization_id, scanner_lock_enabled, scanner_pin_hash')\n .eq('id', eventId)\n .eq('organization_id', userData.organization_id)\n .single();\n \n if (eventError || !event) {\n return new Response(JSON.stringify({ error: 'Event not found' }), {\n status: 404,\n headers: { 'Content-Type': 'application/json' }\n });\n }\n \n // Check if scanner lock is enabled\n if (!event.scanner_lock_enabled || !event.scanner_pin_hash) {\n return new Response(JSON.stringify({ error: 'Scanner lock is not enabled for this event' }), {\n status: 400,\n headers: { 'Content-Type': 'application/json' }\n });\n }\n \n // Verify PIN\n const isValidPin = await verifyPin(pin, event.scanner_pin_hash);\n \n // Log the unlock attempt\n const attemptResult = isValidPin ? 'SUCCESS' : 'INVALID_PIN';\n \n await supabase\n .from('scanner_unlock_attempts')\n .insert({\n event_id: eventId,\n attempted_by: user.id,\n attempt_result: attemptResult,\n ip_address: ipAddress,\n user_agent: userAgent,\n device_info: deviceInfo\n });\n \n if (isValidPin) {\n return new Response(JSON.stringify({\n success: true,\n message: 'PIN verified successfully'\n }), {\n status: 200,\n headers: { 'Content-Type': 'application/json' }\n });\n } else {\n return new Response(JSON.stringify({\n success: false,\n error: 'Invalid PIN'\n }), {\n status: 400,\n headers: { 'Content-Type': 'application/json' }\n });\n }\n \n } catch (error) {\n\n return new Response(JSON.stringify({ error: 'Internal server error' }), {\n status: 500,\n headers: { 'Content-Type': 'application/json' }\n });\n }\n};","usedDeprecatedRules":[]},{"filePath":"/home/tyler/apps/bct-whitelabel/src/pages/api/send-pin-email.ts","messages":[{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":169,"column":12,"nodeType":null,"messageId":"unusedVar","endLine":169,"endColumn":17}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":1,"fixableErrorCount":0,"fixableWarningCount":0,"source":"import type { APIRoute } from 'astro';\nimport { Resend } from 'resend';\n\nconst resend = new Resend(process.env.RESEND_API_KEY);\n\nexport const POST: APIRoute = async ({ request }) => {\n try {\n const { event, pin, email, type = 'immediate' } = await request.json();\n \n if (!event || !pin || !email) {\n return new Response(JSON.stringify({ error: 'Missing required fields' }), {\n status: 400,\n headers: { 'Content-Type': 'application/json' }\n });\n }\n \n // Validate PIN format\n if (!/^\\d{4}$/.test(pin)) {\n return new Response(JSON.stringify({ error: 'Invalid PIN format' }), {\n status: 400,\n headers: { 'Content-Type': 'application/json' }\n });\n }\n \n const eventDate = new Date(event.start_time).toLocaleDateString('en-US', {\n weekday: 'long',\n year: 'numeric',\n month: 'long',\n day: 'numeric'\n });\n \n const eventTime = new Date(event.start_time).toLocaleTimeString('en-US', {\n hour: 'numeric',\n minute: '2-digit',\n hour12: true\n });\n \n let subject: string;\n let htmlContent: string;\n \n if (type === 'immediate') {\n subject = `Scanner Lock PIN for ${event.title}`;\n htmlContent = `\n <!DOCTYPE html>\n <html>\n <head>\n <meta charset=\"utf-8\">\n <title>Scanner Lock PIN</title>\n </head>\n <body style=\"font-family: Arial, sans-serif; line-height: 1.6; color: #333; max-width: 600px; margin: 0 auto; padding: 20px;\">\n <div style=\"background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; padding: 30px; border-radius: 10px; text-align: center; margin-bottom: 30px;\">\n <h1 style=\"margin: 0; font-size: 28px;\">🔒 Scanner Lock PIN</h1>\n <p style=\"margin: 10px 0 0 0; font-size: 16px; opacity: 0.9;\">Black Canyon Tickets</p>\n </div>\n \n <div style=\"background: #f8f9fa; padding: 25px; border-radius: 10px; margin-bottom: 25px;\">\n <h2 style=\"color: #667eea; margin: 0 0 15px 0;\">Your Scanner Access PIN</h2>\n <p style=\"margin: 0 0 15px 0;\">Your scanner has been locked for the event:</p>\n <p style=\"font-weight: bold; font-size: 18px; margin: 0 0 15px 0; color: #333;\">${event.title}</p>\n <p style=\"margin: 0 0 15px 0;\">Date: ${eventDate} at ${eventTime}</p>\n \n <div style=\"background: white; border: 2px solid #667eea; border-radius: 8px; padding: 20px; text-align: center; margin: 20px 0;\">\n <p style=\"margin: 0 0 10px 0; font-size: 16px;\">Your PIN is:</p>\n <div style=\"font-size: 32px; font-weight: bold; font-family: monospace; color: #667eea; letter-spacing: 8px;\">${pin}</div>\n </div>\n \n <p style=\"margin: 15px 0 0 0; font-size: 14px; color: #666;\">\n Use this PIN to unlock your scanner if you need to exit scan-only mode.\n </p>\n </div>\n \n <div style=\"background: #fff3cd; border: 1px solid #ffeaa7; border-radius: 8px; padding: 20px; margin-bottom: 25px;\">\n <h3 style=\"color: #856404; margin: 0 0 10px 0;\">Important Security Information</h3>\n <ul style=\"margin: 0; padding-left: 20px; color: #856404;\">\n <li>Keep this PIN secure and do not share it with unauthorized personnel</li>\n <li>The scanner is now locked to scan-only mode for security</li>\n <li>You will receive a reminder email when your event starts</li>\n <li>The PIN will be required to unlock and return to normal portal access</li>\n </ul>\n </div>\n \n <div style=\"text-align: center; margin-top: 30px; padding-top: 20px; border-top: 1px solid #eee;\">\n <p style=\"margin: 0; font-size: 14px; color: #666;\">\n Sent by Black Canyon Tickets Scanner Lock System<br>\n <a href=\"https://portal.blackcanyontickets.com\" style=\"color: #667eea;\">portal.blackcanyontickets.com</a>\n </p>\n </div>\n </body>\n </html>\n `;\n } else {\n // Reminder email\n subject = `Reminder: Scanner Lock PIN for ${event.title}`;\n htmlContent = `\n <!DOCTYPE html>\n <html>\n <head>\n <meta charset=\"utf-8\">\n <title>Scanner Lock PIN Reminder</title>\n </head>\n <body style=\"font-family: Arial, sans-serif; line-height: 1.6; color: #333; max-width: 600px; margin: 0 auto; padding: 20px;\">\n <div style=\"background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; padding: 30px; border-radius: 10px; text-align: center; margin-bottom: 30px;\">\n <h1 style=\"margin: 0; font-size: 28px;\">🔔 Scanner PIN Reminder</h1>\n <p style=\"margin: 10px 0 0 0; font-size: 16px; opacity: 0.9;\">Your Event is Starting Soon</p>\n </div>\n \n <div style=\"background: #f8f9fa; padding: 25px; border-radius: 10px; margin-bottom: 25px;\">\n <h2 style=\"color: #667eea; margin: 0 0 15px 0;\">Scanner Lock PIN Reminder</h2>\n <p style=\"margin: 0 0 15px 0;\">Your event is starting! Here's your scanner PIN in case you need to unlock your device:</p>\n <p style=\"font-weight: bold; font-size: 18px; margin: 0 0 15px 0; color: #333;\">${event.title}</p>\n <p style=\"margin: 0 0 15px 0;\">Date: ${eventDate} at ${eventTime}</p>\n \n <div style=\"background: white; border: 2px solid #667eea; border-radius: 8px; padding: 20px; text-align: center; margin: 20px 0;\">\n <p style=\"margin: 0 0 10px 0; font-size: 16px;\">Your PIN is:</p>\n <div style=\"font-size: 32px; font-weight: bold; font-family: monospace; color: #667eea; letter-spacing: 8px;\">${pin}</div>\n </div>\n \n <p style=\"margin: 15px 0 0 0; font-size: 14px; color: #666;\">\n Use this PIN to unlock your scanner if you need to exit scan-only mode during the event.\n </p>\n </div>\n \n <div style=\"background: #e8f5e8; border: 1px solid #c3e6c3; border-radius: 8px; padding: 20px; margin-bottom: 25px;\">\n <h3 style=\"color: #2d5a2d; margin: 0 0 10px 0;\">Event Day Reminders</h3>\n <ul style=\"margin: 0; padding-left: 20px; color: #2d5a2d;\">\n <li>Your scanner is locked and ready for secure ticket scanning</li>\n <li>Staff can only scan tickets - no other portal access</li>\n <li>Use the PIN above to unlock if you need administrative access</li>\n <li>Keep the PIN secure throughout the event</li>\n </ul>\n </div>\n \n <div style=\"text-align: center; margin-top: 30px; padding-top: 20px; border-top: 1px solid #eee;\">\n <p style=\"margin: 0; font-size: 14px; color: #666;\">\n Sent by Black Canyon Tickets Scanner Lock System<br>\n <a href=\"https://portal.blackcanyontickets.com\" style=\"color: #667eea;\">portal.blackcanyontickets.com</a>\n </p>\n </div>\n </body>\n </html>\n `;\n }\n \n // Send email\n const { data, error } = await resend.emails.send({\n from: 'Scanner Lock <scanner@blackcanyontickets.com>',\n to: [email],\n subject,\n html: htmlContent\n });\n \n if (error) {\n\n return new Response(JSON.stringify({ error: 'Failed to send email' }), {\n status: 500,\n headers: { 'Content-Type': 'application/json' }\n });\n }\n \n return new Response(JSON.stringify({ \n success: true, \n emailId: data?.id,\n message: 'Email sent successfully' \n }), {\n status: 200,\n headers: { 'Content-Type': 'application/json' }\n });\n \n } catch (error) {\n\n return new Response(JSON.stringify({ error: 'Internal server error' }), {\n status: 500,\n headers: { 'Content-Type': 'application/json' }\n });\n }\n};","usedDeprecatedRules":[]},{"filePath":"/home/tyler/apps/bct-whitelabel/src/pages/api/send-reminder-emails.ts","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/home/tyler/apps/bct-whitelabel/src/pages/api/stripe/account-status.ts","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/home/tyler/apps/bct-whitelabel/src/pages/api/stripe/create-embedded-account.ts","messages":[{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'account' is assigned a value but never used. Allowed unused vars must match /^_/u.","line":106,"column":15,"nodeType":null,"messageId":"unusedVar","endLine":106,"endColumn":22}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":1,"fixableErrorCount":0,"fixableWarningCount":0,"source":"export const prerender = false;\n\nimport type { APIRoute } from 'astro';\nimport { stripe } from '../../../lib/stripe';\nimport { supabase, supabaseAdmin } from '../../../lib/supabase';\nimport { verifyAuth } from '../../../lib/auth';\nimport { logUserActivity } from '../../../lib/logger';\n\nexport const POST: APIRoute = async ({ request }) => {\n try {\n\n // Verify authentication\n const authContext = await verifyAuth(request);\n if (!authContext) {\n console.error('Stripe account creation failed: Unauthorized request');\n return new Response(JSON.stringify({ error: 'Unauthorized' }), { \n status: 401,\n headers: { 'Content-Type': 'application/json' }\n });\n }\n\n const { user } = authContext;\n\n // Get user's organization ID first (using admin client to bypass RLS)\n const { data: userData, error: userError } = await (supabaseAdmin || supabase)\n .from('users')\n .select('organization_id')\n .eq('id', user.id)\n .single();\n\n if (userError || !userData?.organization_id) {\n console.error('Stripe account creation failed: Organization not found', {\n userId: user.id,\n userEmail: user.email,\n error: userError\n });\n return new Response(JSON.stringify({ error: 'Organization not found' }), { \n status: 404,\n headers: { 'Content-Type': 'application/json' }\n });\n }\n\n // Now get the organization details\n const { data: organization, error: orgError } = await (supabaseAdmin || supabase)\n .from('organizations')\n .select('*')\n .eq('id', userData.organization_id)\n .single();\n\n if (orgError || !organization) {\n console.error('Stripe account creation failed: Organization details not found', {\n userId: user.id,\n organizationId: userData.organization_id,\n error: orgError\n });\n return new Response(JSON.stringify({ error: 'Organization details not found' }), { \n status: 404,\n headers: { 'Content-Type': 'application/json' }\n });\n }\n\n // Check if Stripe is properly initialized\n if (!stripe) {\n console.error('Stripe account creation failed: Stripe not configured', {\n userId: user.id,\n organizationId: organization.id,\n envVars: {\n hasSecretKey: !!process.env.STRIPE_SECRET_KEY,\n hasPublishableKey: !!process.env.PUBLIC_STRIPE_PUBLISHABLE_KEY\n }\n });\n return new Response(JSON.stringify({ error: 'Stripe not configured' }), { \n status: 500,\n headers: { 'Content-Type': 'application/json' }\n });\n }\n\n // Check if organization is approved for Stripe onboarding\n if (organization.account_status !== 'approved') {\n console.warn('Stripe account creation blocked: Organization not approved', {\n userId: user.id,\n organizationId: organization.id,\n organizationName: organization.name,\n currentStatus: organization.account_status\n });\n return new Response(JSON.stringify({ \n error: 'Organization must be approved before starting Stripe onboarding',\n account_status: organization.account_status\n }), { \n status: 403,\n headers: { 'Content-Type': 'application/json' }\n });\n }\n\n // Check if Stripe account already exists\n if (organization.stripe_account_id) {\n console.log('Existing Stripe account found, creating new session', {\n userId: user.id,\n organizationId: organization.id,\n stripeAccountId: organization.stripe_account_id\n });\n\n // Return existing account for re-onboarding\n try {\n console.log('Retrieving existing Stripe account details...');\n const account = await stripe.accounts.retrieve(organization.stripe_account_id);\n \n // Create new account session for existing account\n const accountSession = await stripe.accountSessions.create({\n account: organization.stripe_account_id,\n components: {\n account_onboarding: {\n enabled: true,\n features: {\n external_account_collection: true\n }\n }\n }\n });\n\n // Log the action\n await logUserActivity({\n userId: user.id,\n action: 'stripe_onboarding_resumed',\n resourceType: 'organization',\n resourceId: organization.id,\n details: { stripe_account_id: organization.stripe_account_id }\n });\n\n console.log('Account session created successfully for existing account');\n return new Response(JSON.stringify({\n accountId: organization.stripe_account_id,\n clientSecret: accountSession.client_secret,\n existing: true\n }), {\n status: 200,\n headers: { 'Content-Type': 'application/json' }\n });\n } catch (stripeError) {\n console.error('Failed to retrieve/create session for existing Stripe account', {\n stripeAccountId: organization.stripe_account_id,\n error: stripeError\n });\n // Fall through to create new account\n }\n }\n\n // Create new Stripe Connect account\n console.log('Creating new Stripe Express account', {\n userId: user.id,\n organizationId: organization.id,\n organizationName: organization.name,\n businessType: organization.business_type,\n country: organization.country || 'US'\n });\n\n const account = await stripe.accounts.create({\n type: 'express',\n country: organization.country || 'US',\n email: user.email,\n business_type: organization.business_type === 'company' ? 'company' : 'individual',\n capabilities: {\n card_payments: { requested: true },\n transfers: { requested: true }\n },\n business_profile: {\n name: organization.name,\n url: organization.website_url || undefined,\n support_email: user.email,\n support_phone: organization.phone_number || undefined,\n mcc: '7922' // Event ticket agencies\n },\n ...(organization.business_type === 'company' && {\n company: {\n name: organization.name,\n phone: organization.phone_number || undefined,\n ...(organization.address_line1 && {\n address: {\n line1: organization.address_line1,\n line2: organization.address_line2 || undefined,\n city: organization.city || undefined,\n state: organization.state || undefined,\n postal_code: organization.postal_code || undefined,\n country: organization.country || 'US'\n }\n })\n }\n }),\n ...(organization.business_type === 'individual' && {\n individual: {\n email: user.email,\n phone: organization.phone_number || undefined,\n ...(organization.address_line1 && {\n address: {\n line1: organization.address_line1,\n line2: organization.address_line2 || undefined,\n city: organization.city || undefined,\n state: organization.state || undefined,\n postal_code: organization.postal_code || undefined,\n country: organization.country || 'US'\n }\n })\n }\n }),\n settings: {\n payouts: {\n schedule: {\n interval: 'daily'\n }\n }\n }\n });\n\n // Create account session for embedded onboarding\n const accountSession = await stripe.accountSessions.create({\n account: account.id,\n components: {\n account_onboarding: {\n enabled: true,\n features: {\n external_account_collection: true\n }\n }\n }\n });\n\n // Update organization with Stripe account ID\n const { error: updateError } = await (supabaseAdmin || supabase)\n .from('organizations')\n .update({\n stripe_account_id: account.id,\n stripe_onboarding_status: 'in_progress',\n stripe_details_submitted: false,\n stripe_charges_enabled: false,\n stripe_payouts_enabled: false\n })\n .eq('id', organization.id);\n\n if (updateError) {\n console.error('Failed to update organization with Stripe account ID', {\n organizationId: organization.id,\n stripeAccountId: account.id,\n error: updateError\n });\n // Don't fail the request, just log the error\n }\n\n // Log the successful account creation\n await logUserActivity({\n userId: user.id,\n action: 'stripe_account_created',\n resourceType: 'organization',\n resourceId: organization.id,\n details: { \n stripe_account_id: account.id,\n business_type: organization.business_type,\n country: organization.country || 'US'\n }\n });\n\n console.log('Stripe account creation completed successfully', {\n userId: user.id,\n organizationId: organization.id,\n stripeAccountId: account.id,\n clientSecretGenerated: !!accountSession.client_secret\n });\n\n return new Response(JSON.stringify({\n accountId: account.id,\n clientSecret: accountSession.client_secret,\n existing: false\n }), {\n status: 201,\n headers: { 'Content-Type': 'application/json' }\n });\n\n } catch (error) {\n console.error('Payment account creation error:', error);\n console.error('Error details:', {\n message: error instanceof Error ? error.message : 'Unknown error',\n type: error?.constructor?.name,\n raw: error\n });\n \n // Check if it's a Stripe error with specific details\n if (error && typeof error === 'object' && 'type' in error) {\n console.error('Stripe error type:', error.type);\n console.error('Stripe error code:', error.code);\n console.error('Stripe error param:', error.param);\n }\n \n return new Response(JSON.stringify({ \n error: 'Failed to create payment account',\n details: error instanceof Error ? error.message : 'Unknown error',\n debug: error\n }), { \n status: 500,\n headers: { 'Content-Type': 'application/json' }\n });\n }\n};","usedDeprecatedRules":[]},{"filePath":"/home/tyler/apps/bct-whitelabel/src/pages/api/stripe/onboarding-url.ts","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/home/tyler/apps/bct-whitelabel/src/pages/api/templates/[id].ts","messages":[{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":37,"column":12,"nodeType":null,"messageId":"unusedVar","endLine":37,"endColumn":17},{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":60,"column":23,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":60,"endColumn":26,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[1348,1351],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[1348,1351],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":90,"column":12,"nodeType":null,"messageId":"unusedVar","endLine":90,"endColumn":17},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":128,"column":12,"nodeType":null,"messageId":"unusedVar","endLine":128,"endColumn":17}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":4,"fixableErrorCount":0,"fixableWarningCount":0,"source":"import type { APIRoute } from 'astro';\nimport { supabase } from '../../../lib/supabase';\n\nexport const prerender = false;\n\nexport const GET: APIRoute = async ({ params }) => {\n const { id } = params;\n\n if (!id) {\n return new Response(JSON.stringify({ \n success: false, \n error: 'Template ID is required' \n }), { status: 400 });\n }\n\n try {\n const { data: template, error } = await supabase\n .from('custom_page_templates')\n .select('*')\n .eq('id', id)\n .single();\n\n if (error) {\n\n return new Response(JSON.stringify({ \n success: false, \n error: 'Template not found' \n }), { status: 404 });\n }\n\n return new Response(JSON.stringify({ \n success: true, \n template,\n pageData: template.page_data \n }), { status: 200 });\n\n } catch (error) {\n\n return new Response(JSON.stringify({ \n success: false, \n error: 'Internal server error' \n }), { status: 500 });\n }\n};\n\nexport const PUT: APIRoute = async ({ params, request }) => {\n const { id } = params;\n\n if (!id) {\n return new Response(JSON.stringify({ \n success: false, \n error: 'Template ID is required' \n }), { status: 400 });\n }\n\n try {\n const body = await request.json();\n const { name, description, page_data, custom_css, updated_by } = body;\n\n const updateData: any = { \n updated_at: new Date().toISOString() \n };\n\n if (name) updateData.name = name;\n if (description !== undefined) updateData.description = description;\n if (page_data) updateData.page_data = page_data;\n if (custom_css !== undefined) updateData.custom_css = custom_css;\n if (updated_by) updateData.updated_by = updated_by;\n\n const { data: template, error } = await supabase\n .from('custom_page_templates')\n .update(updateData)\n .eq('id', id)\n .select()\n .single();\n\n if (error) {\n\n return new Response(JSON.stringify({ \n success: false, \n error: 'Failed to update template' \n }), { status: 500 });\n }\n\n return new Response(JSON.stringify({ \n success: true, \n template \n }), { status: 200 });\n\n } catch (error) {\n\n return new Response(JSON.stringify({ \n success: false, \n error: 'Internal server error' \n }), { status: 500 });\n }\n};\n\nexport const DELETE: APIRoute = async ({ params }) => {\n const { id } = params;\n\n if (!id) {\n return new Response(JSON.stringify({ \n success: false, \n error: 'Template ID is required' \n }), { status: 400 });\n }\n\n try {\n // Soft delete by setting is_active to false\n const { error } = await supabase\n .from('custom_page_templates')\n .update({ is_active: false })\n .eq('id', id);\n\n if (error) {\n\n return new Response(JSON.stringify({ \n success: false, \n error: 'Failed to delete template' \n }), { status: 500 });\n }\n\n return new Response(JSON.stringify({ \n success: true \n }), { status: 200 });\n\n } catch (error) {\n\n return new Response(JSON.stringify({ \n success: false, \n error: 'Internal server error' \n }), { status: 500 });\n }\n};","usedDeprecatedRules":[]},{"filePath":"/home/tyler/apps/bct-whitelabel/src/pages/api/templates/index.ts","messages":[{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":57,"column":12,"nodeType":null,"messageId":"unusedVar","endLine":57,"endColumn":17},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":104,"column":12,"nodeType":null,"messageId":"unusedVar","endLine":104,"endColumn":17}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":2,"fixableErrorCount":0,"fixableWarningCount":0,"source":"import type { APIRoute } from 'astro';\nimport { supabase } from '../../../lib/supabase';\n\nexport const prerender = false;\n\nexport const GET: APIRoute = async ({ request, url }) => {\n const organizationId = url.searchParams.get('organization_id');\n \n if (!organizationId) {\n return new Response(JSON.stringify({ \n success: false, \n error: 'Organization ID is required' \n }), { status: 400 });\n }\n\n // Authentication\n const authHeader = request.headers.get('authorization');\n if (!authHeader) {\n return new Response(JSON.stringify({ \n success: false, \n error: 'Authorization header required' \n }), { status: 401 });\n }\n\n const { data: { user }, error: authError } = await supabase.auth.getUser(\n authHeader.replace('Bearer ', '')\n );\n\n if (authError || !user) {\n return new Response(JSON.stringify({ \n success: false, \n error: 'Invalid authentication token' \n }), { status: 401 });\n }\n\n try {\n const { data: templates, error } = await supabase\n .from('custom_page_templates')\n .select('*')\n .eq('organization_id', organizationId)\n .eq('is_active', true)\n .order('updated_at', { ascending: false });\n\n if (error) {\n\n return new Response(JSON.stringify({ \n success: false, \n error: 'Failed to fetch templates' \n }), { status: 500 });\n }\n\n return new Response(JSON.stringify({ \n success: true, \n templates: templates || [] \n }), { status: 200 });\n\n } catch (error) {\n\n return new Response(JSON.stringify({ \n success: false, \n error: 'Internal server error' \n }), { status: 500 });\n }\n};\n\nexport const POST: APIRoute = async ({ request }) => {\n try {\n const body = await request.json();\n const { name, description, organization_id, created_by } = body;\n\n if (!name || !organization_id) {\n return new Response(JSON.stringify({ \n success: false, \n error: 'Name and organization ID are required' \n }), { status: 400 });\n }\n\n const { data: template, error } = await supabase\n .from('custom_page_templates')\n .insert({\n name,\n description,\n organization_id,\n created_by,\n page_data: {},\n is_active: true\n })\n .select()\n .single();\n\n if (error) {\n\n return new Response(JSON.stringify({ \n success: false, \n error: 'Failed to create template' \n }), { status: 500 });\n }\n\n return new Response(JSON.stringify({ \n success: true, \n template \n }), { status: 201 });\n\n } catch (error) {\n\n return new Response(JSON.stringify({ \n success: false, \n error: 'Internal server error' \n }), { status: 500 });\n }\n};","usedDeprecatedRules":[]},{"filePath":"/home/tyler/apps/bct-whitelabel/src/pages/api/territory-manager/apply.ts","messages":[{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":124,"column":12,"nodeType":null,"messageId":"unusedVar","endLine":124,"endColumn":17},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'emailContent' is assigned a value but never used. Allowed unused vars must match /^_/u.","line":138,"column":9,"nodeType":null,"messageId":"unusedVar","endLine":138,"endColumn":21}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":2,"fixableErrorCount":0,"fixableWarningCount":0,"source":"import type { APIRoute } from 'astro';\nimport { territoryManagerAPI } from '../../../lib/territory-manager-api';\nimport type { ApplicationFormData } from '../../../lib/territory-manager-types';\n\nexport const POST: APIRoute = async ({ request }) => {\n try {\n const formData = await request.formData();\n \n // Extract form data\n const applicationData: ApplicationFormData = {\n personalInfo: {\n fullName: formData.get('fullName') as string,\n email: formData.get('email') as string,\n phone: formData.get('phone') as string,\n address: {\n street: formData.get('street') as string,\n city: formData.get('city') as string,\n state: formData.get('state') as string,\n zip_code: formData.get('zip') as string,\n country: 'US'\n }\n },\n preferences: {\n desiredTerritory: formData.get('desiredTerritory') as string,\n hasTransportation: formData.get('hasTransportation') === 'yes',\n hasEventExperience: formData.get('hasExperience') === 'yes',\n motivation: formData.get('motivation') as string,\n availability: {\n days_of_week: JSON.parse(formData.get('daysAvailable') as string || '[]'),\n time_ranges: [], // Default empty, can be expanded later\n travel_radius: 50 // Default 50 miles\n }\n },\n documents: {\n // Files would be uploaded to storage separately\n // For now, we'll just store the file names\n },\n consent: {\n background_check: formData.get('consentBackground') === 'true',\n data_processing: formData.get('consentData') === 'true',\n terms_of_service: formData.get('consentTerms') === 'true',\n privacy_policy: formData.get('consentData') === 'true'\n }\n };\n\n // Validate required fields\n if (!applicationData.personalInfo.fullName || \n !applicationData.personalInfo.email || \n !applicationData.personalInfo.phone ||\n !applicationData.preferences.desiredTerritory ||\n !applicationData.preferences.motivation) {\n return new Response(JSON.stringify({ error: 'Missing required fields' }), {\n status: 400,\n headers: { 'Content-Type': 'application/json' }\n });\n }\n\n // Validate consent checkboxes\n if (!applicationData.consent.background_check || \n !applicationData.consent.data_processing || \n !applicationData.consent.terms_of_service) {\n return new Response(JSON.stringify({ error: 'Required consent not provided' }), {\n status: 400,\n headers: { 'Content-Type': 'application/json' }\n });\n }\n\n // Handle file uploads\n const idUpload = formData.get('idUpload') as File;\n const resumeUpload = formData.get('resumeUpload') as File;\n\n if (idUpload) {\n // TODO: Upload to secure storage (AWS S3, Supabase Storage, etc.)\n // For now, we'll just validate file type and size\n const allowedTypes = ['image/jpeg', 'image/png', 'application/pdf'];\n if (!allowedTypes.includes(idUpload.type)) {\n return new Response(JSON.stringify({ error: 'Invalid ID file type' }), {\n status: 400,\n headers: { 'Content-Type': 'application/json' }\n });\n }\n \n if (idUpload.size > 10 * 1024 * 1024) { // 10MB limit\n return new Response(JSON.stringify({ error: 'ID file too large' }), {\n status: 400,\n headers: { 'Content-Type': 'application/json' }\n });\n }\n }\n\n if (resumeUpload) {\n const allowedTypes = ['application/pdf', 'application/msword', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'];\n if (!allowedTypes.includes(resumeUpload.type)) {\n return new Response(JSON.stringify({ error: 'Invalid resume file type' }), {\n status: 400,\n headers: { 'Content-Type': 'application/json' }\n });\n }\n \n if (resumeUpload.size > 10 * 1024 * 1024) { // 10MB limit\n return new Response(JSON.stringify({ error: 'Resume file too large' }), {\n status: 400,\n headers: { 'Content-Type': 'application/json' }\n });\n }\n }\n\n // Submit application\n const application = await territoryManagerAPI.submitApplication(applicationData);\n\n // Send confirmation email (would integrate with email service)\n await sendConfirmationEmail(applicationData.personalInfo.email, applicationData.personalInfo.fullName);\n\n // Log application submission for admin review\n\n return new Response(JSON.stringify({ \n success: true, \n applicationId: application.id \n }), {\n status: 200,\n headers: { 'Content-Type': 'application/json' }\n });\n\n } catch (error) {\n\n return new Response(JSON.stringify({ error: 'Internal server error' }), {\n status: 500,\n headers: { 'Content-Type': 'application/json' }\n });\n }\n};\n\nasync function sendConfirmationEmail(email: string, name: string) {\n // TODO: Integrate with email service (Resend, SendGrid, etc.)\n // For now, just log the action\n\n // Example email content:\n const emailContent = {\n to: email,\n subject: 'Territory Manager Application Received - Black Canyon Tickets',\n html: `\n <h2>Thank you for your interest in becoming a Territory Manager!</h2>\n <p>Hi ${name},</p>\n <p>We've received your Territory Manager application and will review it within 2-3 business days.</p>\n <p>If your application is approved, you'll receive an email with next steps including:</p>\n <ul>\n <li>Background check process</li>\n <li>Access to your Territory Manager portal</li>\n <li>Training materials and resources</li>\n <li>Your exclusive territory assignment</li>\n </ul>\n <p>We're excited about the possibility of having you join our team!</p>\n <p>Best regards,<br>The Black Canyon Tickets Team</p>\n `\n };\n \n // TODO: Actually send the email\n return Promise.resolve();\n}","usedDeprecatedRules":[]},{"filePath":"/home/tyler/apps/bct-whitelabel/src/pages/api/territory-manager/dashboard.ts","messages":[{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'request' is defined but never used. Allowed unused args must match /^_/u.","line":5,"column":39,"nodeType":null,"messageId":"unusedVar","endLine":5,"endColumn":46},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":73,"column":12,"nodeType":null,"messageId":"unusedVar","endLine":73,"endColumn":17},{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":82,"column":47,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":82,"endColumn":50,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[2682,2685],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[2682,2685],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":82,"column":55,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":82,"endColumn":58,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[2690,2693],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[2690,2693],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":4,"fixableErrorCount":0,"fixableWarningCount":0,"source":"import type { APIRoute } from 'astro';\nimport { TerritoryManagerAuth } from '../../../lib/territory-manager-auth';\nimport { territoryManagerAPI } from '../../../lib/territory-manager-api';\n\nexport const GET: APIRoute = async ({ request }) => {\n try {\n // Check if user is authenticated as territory manager\n const territoryManager = await TerritoryManagerAuth.getCurrentTerritoryManager();\n if (!territoryManager) {\n return new Response(JSON.stringify({ error: 'Not authenticated as territory manager' }), {\n status: 401,\n headers: { 'Content-Type': 'application/json' }\n });\n }\n\n // Load dashboard data\n const [\n stats,\n leads,\n commissions,\n achievements,\n notifications,\n territories\n ] = await Promise.all([\n territoryManagerAPI.getTerritoryManagerStats(territoryManager.id),\n territoryManagerAPI.getLeads(territoryManager.id),\n territoryManagerAPI.getCommissions(territoryManager.id),\n territoryManagerAPI.getAchievements(),\n territoryManagerAPI.getNotifications(territoryManager.id),\n territoryManagerAPI.getTerritories()\n ]);\n\n // Get territory info\n const territory = territories.find(t => t.id === territoryManager.territory_id);\n\n // Generate recent activity\n const recentActivity = [\n ...commissions.slice(0, 3).map(c => ({\n type: 'commission',\n message: `Earned $${c.total_commission} commission`,\n created_at: c.created_at\n })),\n ...leads.slice(0, 2).map(l => ({\n type: 'lead',\n message: `${l.status === 'converted' ? 'Converted' : 'Added'} lead: ${l.event_name}`,\n created_at: l.created_at\n }))\n ].sort((a, b) => new Date(b.created_at).getTime() - new Date(a.created_at).getTime());\n\n // Generate earnings history from commissions\n const earningsHistory = generateEarningsHistory(commissions);\n\n // Filter for active leads\n const activeLeads = leads.filter(l => l.status !== 'converted' && l.status !== 'dead');\n\n const dashboardData = {\n territoryManager,\n territory,\n profile: territoryManager.profile,\n stats,\n recent_activity: recentActivity,\n active_leads: activeLeads,\n achievements,\n notifications,\n earnings_history: earningsHistory\n };\n\n return new Response(JSON.stringify(dashboardData), {\n status: 200,\n headers: { 'Content-Type': 'application/json' }\n });\n\n } catch (error) {\n\n return new Response(JSON.stringify({ error: 'Internal server error' }), {\n status: 500,\n headers: { 'Content-Type': 'application/json' }\n });\n }\n};\n\nfunction generateEarningsHistory(commissions: any[]): any[] {\n const monthlyEarnings = new Map<string, number>();\n \n commissions.forEach(commission => {\n const month = commission.created_at.substring(0, 7); // YYYY-MM\n const current = monthlyEarnings.get(month) || 0;\n monthlyEarnings.set(month, current + commission.total_commission);\n });\n\n return Array.from(monthlyEarnings.entries())\n .map(([month, amount]) => ({\n date: month + '-01',\n amount\n }))\n .sort((a, b) => a.date.localeCompare(b.date));\n}","usedDeprecatedRules":[]},{"filePath":"/home/tyler/apps/bct-whitelabel/src/pages/api/territory-manager/notifications/mark-all-read.ts","messages":[{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'request' is defined but never used. Allowed unused args must match /^_/u.","line":5,"column":40,"nodeType":null,"messageId":"unusedVar","endLine":5,"endColumn":47},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":33,"column":12,"nodeType":null,"messageId":"unusedVar","endLine":33,"endColumn":17}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":2,"fixableErrorCount":0,"fixableWarningCount":0,"source":"import type { APIRoute } from 'astro';\nimport { TerritoryManagerAuth } from '../../../../lib/territory-manager-auth';\nimport { territoryManagerAPI } from '../../../../lib/territory-manager-api';\n\nexport const POST: APIRoute = async ({ request }) => {\n try {\n // Check if user is authenticated as territory manager\n const territoryManager = await TerritoryManagerAuth.getCurrentTerritoryManager();\n if (!territoryManager) {\n return new Response(JSON.stringify({ error: 'Not authenticated as territory manager' }), {\n status: 401,\n headers: { 'Content-Type': 'application/json' }\n });\n }\n\n // Get all unread notifications\n const notifications = await territoryManagerAPI.getNotifications(territoryManager.id);\n const unreadNotifications = notifications.filter(n => !n.read);\n\n // Mark all as read\n await Promise.all(\n unreadNotifications.map(n => territoryManagerAPI.markNotificationAsRead(n.id))\n );\n\n return new Response(JSON.stringify({ \n success: true, \n markedCount: unreadNotifications.length \n }), {\n status: 200,\n headers: { 'Content-Type': 'application/json' }\n });\n\n } catch (error) {\n\n return new Response(JSON.stringify({ error: 'Internal server error' }), {\n status: 500,\n headers: { 'Content-Type': 'application/json' }\n });\n }\n};","usedDeprecatedRules":[]},{"filePath":"/home/tyler/apps/bct-whitelabel/src/pages/api/territory-manager/referral-link.ts","messages":[{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":28,"column":12,"nodeType":null,"messageId":"unusedVar","endLine":28,"endColumn":17}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":1,"fixableErrorCount":0,"fixableWarningCount":0,"source":"import type { APIRoute } from 'astro';\nimport { TerritoryManagerAuth } from '../../../lib/territory-manager-auth';\nimport { territoryManagerAPI } from '../../../lib/territory-manager-api';\n\nexport const POST: APIRoute = async ({ request }) => {\n try {\n // Check if user is authenticated as territory manager\n const territoryManager = await TerritoryManagerAuth.getCurrentTerritoryManager();\n if (!territoryManager) {\n return new Response(JSON.stringify({ error: 'Not authenticated as territory manager' }), {\n status: 401,\n headers: { 'Content-Type': 'application/json' }\n });\n }\n\n // Get optional event type from request body\n const body = await request.json().catch(() => ({}));\n const { eventType } = body;\n\n // Generate referral link\n const referralLink = await territoryManagerAPI.generateReferralLink(territoryManager.id, eventType);\n\n return new Response(JSON.stringify(referralLink), {\n status: 200,\n headers: { 'Content-Type': 'application/json' }\n });\n\n } catch (error) {\n\n return new Response(JSON.stringify({ error: 'Internal server error' }), {\n status: 500,\n headers: { 'Content-Type': 'application/json' }\n });\n }\n};","usedDeprecatedRules":[]},{"filePath":"/home/tyler/apps/bct-whitelabel/src/pages/api/tickets/preview.ts","messages":[{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":99,"column":12,"nodeType":null,"messageId":"unusedVar","endLine":99,"endColumn":17}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":1,"fixableErrorCount":0,"fixableWarningCount":0,"source":"import type { APIRoute } from 'astro';\nimport { supabase } from '../../../lib/supabase';\n\nexport const GET: APIRoute = async ({ url, cookies }) => {\n try {\n // Get query parameters\n const eventId = url.searchParams.get('event_id');\n const ticketTypeId = url.searchParams.get('ticket_type_id');\n \n if (!eventId || !ticketTypeId) {\n return new Response(JSON.stringify({ \n success: false, \n error: 'Event ID and ticket type ID are required' \n }), { status: 400 });\n }\n\n // Authenticate user (basic auth check)\n const token = cookies.get('sb-access-token')?.value;\n if (!token) {\n return new Response(JSON.stringify({ \n success: false, \n error: 'Authentication required' \n }), { status: 401 });\n }\n\n // Fetch event and ticket type data\n const { data: eventData, error: eventError } = await supabase\n .from('events')\n .select(`\n id,\n title,\n description,\n start_time,\n end_time,\n venue,\n address,\n image_url,\n organizations (\n name\n )\n `)\n .eq('id', eventId)\n .single();\n\n if (eventError || !eventData) {\n return new Response(JSON.stringify({ \n success: false, \n error: 'Event not found' \n }), { status: 404 });\n }\n\n const { data: ticketTypeData, error: ticketTypeError } = await supabase\n .from('ticket_types')\n .select('id, name, price, description')\n .eq('id', ticketTypeId)\n .eq('event_id', eventId)\n .single();\n\n if (ticketTypeError || !ticketTypeData) {\n return new Response(JSON.stringify({ \n success: false, \n error: 'Ticket type not found' \n }), { status: 404 });\n }\n\n // Format dates\n const startTime = new Date(eventData.start_time);\n const eventDate = startTime.toLocaleDateString('en-US', {\n weekday: 'long',\n year: 'numeric',\n month: 'long',\n day: 'numeric'\n });\n \n const eventTime = startTime.toLocaleTimeString('en-US', {\n hour: 'numeric',\n minute: '2-digit',\n hour12: true\n });\n\n // Prepare preview data\n const previewData = {\n eventTitle: eventData.title,\n eventDate: eventDate,\n eventTime: eventTime,\n venue: eventData.venue,\n address: eventData.address,\n ticketTypeName: ticketTypeData.name,\n ticketTypePrice: ticketTypeData.price,\n organizationName: eventData.organizations?.name || 'Event Organizer',\n imageUrl: eventData.image_url\n };\n\n return new Response(JSON.stringify({ \n success: true, \n preview: previewData\n }), { status: 200 });\n\n } catch (error) {\n\n return new Response(JSON.stringify({ \n success: false, \n error: 'Internal server error' \n }), { status: 500 });\n }\n};","usedDeprecatedRules":[]},{"filePath":"/home/tyler/apps/bct-whitelabel/src/pages/api/upload-event-image.ts","messages":[{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'uploadData' is assigned a value but never used. Allowed unused vars must match /^_/u.","line":69,"column":19,"nodeType":null,"messageId":"unusedVar","endLine":69,"endColumn":29},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":96,"column":12,"nodeType":null,"messageId":"unusedVar","endLine":96,"endColumn":17}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":2,"fixableErrorCount":0,"fixableWarningCount":0,"source":"import type { APIRoute } from 'astro';\nimport { supabase } from '../../lib/supabase';\nimport { v4 as uuidv4 } from 'uuid';\n\nconst MAX_FILE_SIZE = 2 * 1024 * 1024; // 2MB\nconst ALLOWED_TYPES = ['image/jpeg', 'image/png', 'image/webp'];\n\nexport const POST: APIRoute = async ({ request }) => {\n try {\n \n // Get the authorization header\n const authHeader = request.headers.get('authorization');\n if (!authHeader) {\n return new Response(JSON.stringify({ error: 'Authorization required' }), {\n status: 401,\n headers: { 'Content-Type': 'application/json' }\n });\n }\n\n // Verify the user is authenticated\n const { data: { user }, error: authError } = await supabase.auth.getUser(\n authHeader.replace('Bearer ', '')\n );\n\n if (authError || !user) {\n return new Response(JSON.stringify({ error: 'Invalid authentication' }), {\n status: 401,\n headers: { 'Content-Type': 'application/json' }\n });\n }\n\n // Parse the form data\n const formData = await request.formData();\n const file = formData.get('file') as File;\n\n if (!file) {\n return new Response(JSON.stringify({ error: 'No file provided' }), {\n status: 400,\n headers: { 'Content-Type': 'application/json' }\n });\n }\n\n // Validate file type\n if (!ALLOWED_TYPES.includes(file.type)) {\n return new Response(JSON.stringify({ error: 'Invalid file type. Only JPG, PNG, and WebP are allowed.' }), {\n status: 400,\n headers: { 'Content-Type': 'application/json' }\n });\n }\n\n // Validate file size\n if (file.size > MAX_FILE_SIZE) {\n return new Response(JSON.stringify({ error: 'File too large. Maximum size is 2MB.' }), {\n status: 400,\n headers: { 'Content-Type': 'application/json' }\n });\n }\n\n // Convert file to buffer\n const arrayBuffer = await file.arrayBuffer();\n const buffer = Buffer.from(arrayBuffer);\n\n // Generate unique filename\n const fileExtension = file.type.split('/')[1];\n const fileName = `${uuidv4()}.${fileExtension}`;\n const filePath = `events/${fileName}`;\n\n // Upload to Supabase Storage\n const { data: uploadData, error: uploadError } = await supabase.storage\n .from('event-images')\n .upload(filePath, buffer, {\n contentType: file.type,\n upsert: false\n });\n\n if (uploadError) {\n return new Response(JSON.stringify({ \n error: 'Upload failed', \n details: uploadError.message \n }), {\n status: 500,\n headers: { 'Content-Type': 'application/json' }\n });\n }\n\n // Get the public URL\n const { data: { publicUrl } } = supabase.storage\n .from('event-images')\n .getPublicUrl(filePath);\n\n return new Response(JSON.stringify({ imageUrl: publicUrl }), {\n status: 200,\n headers: { 'Content-Type': 'application/json' }\n });\n\n } catch (error) {\n return new Response(JSON.stringify({ error: 'Internal server error' }), {\n status: 500,\n headers: { 'Content-Type': 'application/json' }\n });\n }\n};","usedDeprecatedRules":[]},{"filePath":"/home/tyler/apps/bct-whitelabel/src/pages/api/webhooks/stripe.ts","messages":[{"ruleId":"no-empty","severity":1,"message":"Empty block statement.","line":266,"column":23,"nodeType":"BlockStatement","messageId":"unexpected","endLine":268,"endColumn":6,"suggestions":[{"messageId":"suggestComment","data":{"type":"block"},"fix":{"range":[9081,9087],"text":" /* empty */ "},"desc":"Add comment inside empty block statement."}]}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":1,"fixableErrorCount":0,"fixableWarningCount":0,"source":"export const prerender = false;\n\nimport type { APIRoute } from 'astro';\nimport Stripe from 'stripe';\nimport { supabase } from '../../../lib/supabase';\nimport { sendTicketConfirmationEmail, sendOrderConfirmationEmail, sendOrganizerNotificationEmail } from '../../../lib/email';\nimport { logPaymentEvent } from '../../../lib/logger';\n\n// Initialize Stripe with the secret key\nconst stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, {\n apiVersion: '2024-06-20'\n});\n\nconst endpointSecret = process.env.STRIPE_WEBHOOK_SECRET!;\n\nif (!endpointSecret) {\n throw new Error('Missing STRIPE_WEBHOOK_SECRET environment variable');\n}\n\nexport const POST: APIRoute = async ({ request }) => {\n try {\n const body = await request.text();\n const signature = request.headers.get('stripe-signature');\n\n if (!signature) {\n console.error('Stripe webhook: Missing signature');\n return new Response('Missing signature', { status: 400 });\n }\n\n let event: Stripe.Event;\n\n try {\n // Verify the webhook signature\n event = stripe.webhooks.constructEvent(body, signature, endpointSecret);\n } catch (err) {\n console.error('Stripe webhook verification failed:', err);\n return new Response(`Webhook Error: ${(err as Error).message}`, { status: 400 });\n }\n\n // Handle the event\n switch (event.type) {\n case 'payment_intent.succeeded':\n await handlePaymentSucceeded(event.data.object as Stripe.PaymentIntent);\n break;\n \n case 'payment_intent.payment_failed':\n await handlePaymentFailed(event.data.object as Stripe.PaymentIntent);\n break;\n \n case 'charge.dispute.created':\n await handleChargeDispute(event.data.object as Stripe.Dispute);\n break;\n \n case 'account.updated':\n await handleAccountUpdated(event.data.object as Stripe.Account);\n break;\n \n default:\n console.log(`Unhandled Stripe event type: ${event.type}`);\n }\n\n return new Response('OK', { status: 200 });\n } catch (_error) {\n console.error('Stripe webhook error:', _error);\n return new Response('Internal Server Error', { status: 500 });\n }\n};\n\nasync function handlePaymentSucceeded(paymentIntent: Stripe.PaymentIntent) {\n console.log(`Processing payment success for ${paymentIntent.id}`);\n try {\n // Log payment event\n logPaymentEvent({\n type: 'payment_completed',\n amount: paymentIntent.amount,\n currency: paymentIntent.currency,\n paymentIntentId: paymentIntent.id\n });\n\n // Find the purchase attempt by payment intent ID\n const { data: purchaseAttempt, error: findError } = await supabase\n .from('purchase_attempts')\n .select(`\n *,\n events (\n title,\n venue,\n start_time,\n description,\n created_by,\n users (name, email)\n )\n `)\n .eq('stripe_payment_intent_id', paymentIntent.id)\n .single();\n\n if (findError || !purchaseAttempt) {\n console.error('Purchase attempt not found for payment intent:', paymentIntent.id, findError);\n return;\n }\n\n // Update purchase attempt status\n const { error: updateError } = await supabase\n .from('purchase_attempts')\n .update({ \n status: 'completed',\n completed_at: new Date().toISOString()\n })\n .eq('id', purchaseAttempt.id);\n\n if (updateError) {\n console.error('Failed to update purchase attempt:', updateError);\n return;\n }\n\n // Create tickets for each item in the purchase\n const { data: purchaseItems, error: itemsError } = await supabase\n .from('purchase_attempt_items')\n .select(`\n *,\n ticket_types (name, description),\n seats (row, number)\n `)\n .eq('purchase_attempt_id', purchaseAttempt.id);\n\n if (itemsError || !purchaseItems) {\n console.error('Failed to fetch purchase items:', itemsError);\n return;\n }\n\n const tickets = [];\n const orderTickets = [];\n\n for (const item of purchaseItems) {\n for (let i = 0; i < item.quantity; i++) {\n const { data: ticket, error: ticketError } = await supabase\n .from('tickets')\n .insert({\n event_id: purchaseAttempt.event_id,\n ticket_type_id: item.ticket_type_id,\n seat_id: item.seat_id,\n price: item.unit_price,\n purchaser_email: purchaseAttempt.purchaser_email,\n purchaser_name: purchaseAttempt.purchaser_name,\n purchase_attempt_id: purchaseAttempt.id,\n stripe_payment_intent_id: paymentIntent.id,\n status: 'valid'\n })\n .select()\n .single();\n\n if (ticketError) {\n console.error('Failed to create ticket:', ticketError);\n continue;\n }\n\n tickets.push(ticket);\n\n // Send individual ticket confirmation email\n try {\n await sendTicketConfirmationEmail({\n ticketId: ticket.id,\n ticketUuid: ticket.uuid,\n eventTitle: purchaseAttempt.events.title,\n eventVenue: purchaseAttempt.events.venue,\n eventDate: new Date(purchaseAttempt.events.start_time).toLocaleDateString(),\n eventTime: new Date(purchaseAttempt.events.start_time).toLocaleTimeString(),\n ticketType: item.ticket_types.name,\n seatInfo: item.seats ? `Row ${item.seats.row}, Seat ${item.seats.number}` : undefined,\n price: item.unit_price,\n purchaserName: purchaseAttempt.purchaser_name,\n purchaserEmail: purchaseAttempt.purchaser_email,\n organizerName: purchaseAttempt.events.users.name,\n organizerEmail: purchaseAttempt.events.users.email,\n qrCodeUrl: '', // Will be generated in email function\n orderNumber: purchaseAttempt.id,\n totalAmount: purchaseAttempt.total_amount,\n platformFee: purchaseAttempt.platform_fee,\n eventDescription: purchaseAttempt.events.description,\n additionalInfo: 'Please arrive 15 minutes early for entry.'\n });\n } catch (_emailError) {\n console.error('Failed to send ticket confirmation email:', _emailError);\n }\n }\n\n // Add to order summary\n orderTickets.push({\n type: item.ticket_types.name,\n quantity: item.quantity,\n price: item.unit_price,\n seatInfo: item.seats ? `Row ${item.seats.row}, Seat ${item.seats.number}` : undefined\n });\n }\n\n // Send order confirmation email\n try {\n await sendOrderConfirmationEmail({\n orderNumber: purchaseAttempt.id,\n purchaserName: purchaseAttempt.purchaser_name,\n purchaserEmail: purchaseAttempt.purchaser_email,\n eventTitle: purchaseAttempt.events.title,\n eventVenue: purchaseAttempt.events.venue,\n eventDate: new Date(purchaseAttempt.events.start_time).toLocaleDateString(),\n totalAmount: purchaseAttempt.total_amount,\n platformFee: purchaseAttempt.platform_fee,\n tickets: orderTickets,\n organizerName: purchaseAttempt.events.users.name,\n refundPolicy: 'Refunds available up to 24 hours before the event.'\n });\n } catch (_emailError) {\n console.error('Failed to send order confirmation email:', _emailError);\n }\n\n // Send organizer notification\n try {\n await sendOrganizerNotificationEmail({\n organizerEmail: purchaseAttempt.events.users.email,\n organizerName: purchaseAttempt.events.users.name,\n eventTitle: purchaseAttempt.events.title,\n purchaserName: purchaseAttempt.purchaser_name,\n purchaserEmail: purchaseAttempt.purchaser_email,\n ticketType: orderTickets.map(t => `${t.quantity}x ${t.type}`).join(', '),\n amount: purchaseAttempt.total_amount - purchaseAttempt.platform_fee,\n orderNumber: purchaseAttempt.id\n });\n } catch (_emailError) {\n console.error('Failed to send order confirmation email:', _emailError);\n }\n\n } catch (_error) {\n console.error('Payment processing error:', _error);\n // Log payment error\n logPaymentEvent({\n type: 'payment_failed',\n amount: paymentIntent.amount,\n currency: paymentIntent.currency,\n paymentIntentId: paymentIntent.id,\n error: (_error as Error).message\n });\n }\n}\n\nasync function handlePaymentFailed(paymentIntent: Stripe.PaymentIntent) {\n console.log(`Processing payment failure for ${paymentIntent.id}`);\n try {\n // Update purchase attempt status\n const { error } = await supabase\n .from('purchase_attempts')\n .update({ \n status: 'failed',\n failure_reason: 'Payment failed'\n })\n .eq('stripe_payment_intent_id', paymentIntent.id);\n\n if (error) {\n console.error('Failed to update purchase attempt for failed payment:', error);\n }\n\n // Release any reserved tickets\n const { error: releaseError } = await supabase\n .rpc('release_reservations_by_payment_intent', {\n p_payment_intent_id: paymentIntent.id\n });\n\n if (releaseError) {\n\n }\n \n } catch (_error) {\n console.error('Error handling payment failure:', _error);\n }\n}\n\nasync function handleChargeDispute(dispute: Stripe.Dispute) {\n console.log(`Processing charge dispute ${dispute.id}`);\n try {\n // Log the dispute for manual review\n await supabase\n .from('audit_logs')\n .insert({\n action: 'dispute_created',\n resource_type: 'charge',\n resource_id: dispute.charge as string,\n old_values: null,\n new_values: {\n dispute_id: dispute.id,\n amount: dispute.amount,\n reason: dispute.reason,\n status: dispute.status\n },\n ip_address: null,\n user_agent: 'stripe-webhook'\n });\n \n // TODO: Send alert to admin team\n \n } catch (_error) {\n console.error('Error handling payment failure:', _error);\n }\n}\n\nasync function handleAccountUpdated(account: Stripe.Account) {\n console.log('Processing account.updated webhook for Stripe account:', account.id);\n \n try {\n // Determine overall onboarding status\n let onboarding_status = 'in_progress';\n if (account.details_submitted && account.charges_enabled) {\n onboarding_status = 'completed';\n } else if (account.details_submitted) {\n onboarding_status = 'pending_review';\n }\n\n console.log('Account status determined:', {\n stripe_account_id: account.id,\n details_submitted: account.details_submitted,\n charges_enabled: account.charges_enabled,\n payouts_enabled: account.payouts_enabled,\n onboarding_status\n });\n\n // Update organization with comprehensive Stripe status\n const { data: updatedOrg, error } = await supabase\n .from('organizations')\n .update({\n stripe_onboarding_status: onboarding_status,\n stripe_details_submitted: account.details_submitted,\n stripe_charges_enabled: account.charges_enabled,\n stripe_payouts_enabled: account.payouts_enabled,\n stripe_account_status: account.charges_enabled ? 'active' : 'pending',\n ...(onboarding_status === 'completed' && {\n onboarding_completed_at: new Date().toISOString(),\n account_status: 'active'\n })\n })\n .eq('stripe_account_id', account.id)\n .select('id, name, stripe_onboarding_status')\n .single();\n\n if (error) {\n console.error('Failed to update organization from webhook:', {\n stripe_account_id: account.id,\n error\n });\n } else {\n console.log('Organization updated successfully from webhook:', {\n organization_id: updatedOrg?.id,\n organization_name: updatedOrg?.name,\n new_onboarding_status: onboarding_status,\n stripe_account_id: account.id\n });\n\n // Log successful onboarding completion\n if (onboarding_status === 'completed') {\n console.log('🎉 Stripe onboarding completed via webhook for organization:', updatedOrg?.name);\n \n // Log the completion activity\n logPaymentEvent({\n type: 'onboarding_completed',\n amount: 0,\n currency: account.default_currency || 'usd',\n paymentIntentId: null,\n stripeAccountId: account.id,\n organizationId: updatedOrg?.id || null\n });\n }\n }\n \n } catch (error) {\n console.error('Error processing account.updated webhook:', {\n stripe_account_id: account.id,\n error: error instanceof Error ? error.message : error,\n stack: error instanceof Error ? error.stack : undefined\n });\n }\n}","usedDeprecatedRules":[]},{"filePath":"/home/tyler/apps/bct-whitelabel/styleGuide/visualAudit.ts","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]}]