feat: add advanced analytics and territory management system
- Add comprehensive analytics components with export functionality - Implement territory management with manager performance tracking - Add seatmap components for venue layout management - Create customer management features with modal interface - Add advanced hooks for dashboard flags and territory data - Implement seat selection and venue management utilities - Add type definitions for ticketing and seatmap systems 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
74
reactrebuild0825/scripts/check-hardcoded-colors.js
Normal file
74
reactrebuild0825/scripts/check-hardcoded-colors.js
Normal file
@@ -0,0 +1,74 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
/**
|
||||
* Check for hardcoded colors in the codebase
|
||||
* This script scans for hex colors, rgb(), rgba(), and other hardcoded color patterns
|
||||
*/
|
||||
|
||||
import { exec } from 'child_process';
|
||||
import { promisify } from 'util';
|
||||
|
||||
const execAsync = promisify(exec);
|
||||
|
||||
const colorPatterns = [
|
||||
// Hex colors
|
||||
'#[0-9a-fA-F]{3,8}',
|
||||
// RGB/RGBA functions
|
||||
'rgb\\s*\\(',
|
||||
'rgba\\s*\\(',
|
||||
// HSL/HSLA functions
|
||||
'hsl\\s*\\(',
|
||||
'hsla\\s*\\(',
|
||||
// Hardcoded Tailwind classes
|
||||
'bg-white',
|
||||
'bg-black',
|
||||
'text-white',
|
||||
'text-black'
|
||||
];
|
||||
|
||||
const excludePaths = [
|
||||
'node_modules',
|
||||
'dist',
|
||||
'build',
|
||||
'.git',
|
||||
'scripts',
|
||||
'tailwind.config.js',
|
||||
'tokens.ts',
|
||||
'tokens.css'
|
||||
];
|
||||
|
||||
async function checkHardcodedColors() {
|
||||
console.log('🎨 Checking for hardcoded colors in codebase...\n');
|
||||
|
||||
let hasViolations = false;
|
||||
|
||||
for (const pattern of colorPatterns) {
|
||||
const excludeArgs = excludePaths.map(path => `--exclude-dir=${path}`).join(' ');
|
||||
const command = `grep -r -n --color=never ${excludeArgs} "${pattern}" src/ || true`;
|
||||
|
||||
try {
|
||||
const { stdout } = await execAsync(command);
|
||||
|
||||
if (stdout.trim()) {
|
||||
console.log(`❌ Found hardcoded color pattern: ${pattern}`);
|
||||
console.log(stdout);
|
||||
hasViolations = true;
|
||||
}
|
||||
} catch (error) {
|
||||
// grep returns non-zero exit code when no matches found, which is expected
|
||||
if (error.code !== 1) {
|
||||
console.error(`Error checking pattern ${pattern}:`, error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (hasViolations) {
|
||||
console.log('\n❌ Hardcoded colors found! Please use design tokens instead.');
|
||||
console.log('📖 See src/theme/tokens.ts for available tokens.');
|
||||
process.exit(1);
|
||||
} else {
|
||||
console.log('✅ No hardcoded colors found! All colors are using design tokens.');
|
||||
}
|
||||
}
|
||||
|
||||
checkHardcodedColors().catch(console.error);
|
||||
100
reactrebuild0825/scripts/deploy-functions.sh
Executable file
100
reactrebuild0825/scripts/deploy-functions.sh
Executable file
@@ -0,0 +1,100 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Stripe Connect Functions Deployment Script
|
||||
# This script helps deploy Firebase Functions with proper environment setup
|
||||
|
||||
set -e
|
||||
|
||||
# Colors for output
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
echo -e "${BLUE}🚀 Stripe Connect Functions Deployment${NC}"
|
||||
echo "=========================================="
|
||||
|
||||
# Check if Firebase CLI is installed
|
||||
if ! command -v firebase &> /dev/null; then
|
||||
echo -e "${RED}❌ Firebase CLI not found. Install with: npm install -g firebase-tools${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check if logged in to Firebase
|
||||
if ! firebase projects:list &> /dev/null; then
|
||||
echo -e "${YELLOW}⚠️ Not logged in to Firebase. Logging in...${NC}"
|
||||
firebase login
|
||||
fi
|
||||
|
||||
# Get current project
|
||||
PROJECT=$(firebase use 2>/dev/null | grep "active project" | awk '{print $4}' | tr -d '()')
|
||||
|
||||
if [ -z "$PROJECT" ]; then
|
||||
echo -e "${RED}❌ No Firebase project selected. Run 'firebase use <project-id>' first.${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo -e "${GREEN}📁 Project: ${PROJECT}${NC}"
|
||||
|
||||
# Check Functions directory
|
||||
if [ ! -d "functions" ]; then
|
||||
echo -e "${RED}❌ Functions directory not found. Make sure you're in the project root.${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Install Functions dependencies
|
||||
echo -e "${BLUE}📦 Installing Functions dependencies...${NC}"
|
||||
cd functions
|
||||
npm install
|
||||
|
||||
# Check if environment config exists
|
||||
echo -e "${BLUE}🔧 Checking environment configuration...${NC}"
|
||||
|
||||
CONFIG=$(firebase functions:config:get 2>/dev/null || echo "{}")
|
||||
|
||||
if [ "$CONFIG" = "{}" ]; then
|
||||
echo -e "${YELLOW}⚠️ No environment config found. You'll need to set:${NC}"
|
||||
echo " firebase functions:config:set stripe.secret_key=\"sk_...\""
|
||||
echo " firebase functions:config:set stripe.webhook_secret=\"whsec_...\""
|
||||
echo " firebase functions:config:set app.url=\"https://your-domain.com\""
|
||||
echo ""
|
||||
read -p "Continue anyway? (y/n): " -n 1 -r
|
||||
echo
|
||||
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
echo -e "${GREEN}✅ Environment config found${NC}"
|
||||
fi
|
||||
|
||||
# Build Functions
|
||||
echo -e "${BLUE}🔨 Building Functions...${NC}"
|
||||
npm run build
|
||||
|
||||
# Deploy Functions
|
||||
echo -e "${BLUE}🚀 Deploying Functions...${NC}"
|
||||
cd ..
|
||||
|
||||
firebase deploy --only functions
|
||||
|
||||
if [ $? -eq 0 ]; then
|
||||
echo ""
|
||||
echo -e "${GREEN}✅ Functions deployed successfully!${NC}"
|
||||
echo ""
|
||||
echo "📋 Next steps:"
|
||||
echo "1. Configure Stripe webhook endpoint:"
|
||||
echo " URL: https://us-central1-${PROJECT}.cloudfunctions.net/stripeWebhook"
|
||||
echo " Events: account.updated"
|
||||
echo ""
|
||||
echo "2. Test the integration:"
|
||||
echo " - Visit your app and try connecting a Stripe account"
|
||||
echo " - Check function logs: firebase functions:log"
|
||||
echo ""
|
||||
echo "3. Update frontend API URL if needed:"
|
||||
echo " - In useStripeConnect.ts, update getApiUrl() function"
|
||||
echo ""
|
||||
else
|
||||
echo -e "${RED}❌ Deployment failed. Check the logs above.${NC}"
|
||||
exit 1
|
||||
fi
|
||||
312
reactrebuild0825/scripts/qr-system-demo.js
Normal file
312
reactrebuild0825/scripts/qr-system-demo.js
Normal file
@@ -0,0 +1,312 @@
|
||||
/**
|
||||
* QR System Demonstration Script
|
||||
* Shows QR validation, backup code generation, and manual entry flow
|
||||
*/
|
||||
|
||||
// Import the QR validator and generator (simplified for demo)
|
||||
function createMockQRValidator() {
|
||||
return {
|
||||
validateQR: (qrString) => {
|
||||
if (!qrString || typeof qrString !== 'string') {
|
||||
return { valid: false, format: 'unknown', errorReason: 'invalid_format' };
|
||||
}
|
||||
|
||||
const trimmed = qrString.trim();
|
||||
|
||||
if (trimmed.startsWith('BCT.v')) {
|
||||
const parts = trimmed.split('.');
|
||||
if (parts.length === 4) {
|
||||
return {
|
||||
valid: true,
|
||||
format: 'signed',
|
||||
ticketId: '550e8400-e29b-41d4-a716-446655440000',
|
||||
eventId: 'evt_789012345',
|
||||
metadata: {
|
||||
version: 2,
|
||||
issuedAt: Math.floor(Date.now() / 1000),
|
||||
expiresAt: Math.floor(Date.now() / 1000) + 30 * 24 * 60 * 60,
|
||||
zone: 'GA',
|
||||
seat: 'A12'
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if (trimmed.startsWith('TICKET_')) {
|
||||
const ticketId = trimmed.substring(7);
|
||||
if (/^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(ticketId)) {
|
||||
return { valid: true, format: 'simple', ticketId };
|
||||
}
|
||||
}
|
||||
|
||||
if (/^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(trimmed)) {
|
||||
return { valid: true, format: 'simple', ticketId: trimmed };
|
||||
}
|
||||
|
||||
return { valid: false, format: 'unknown', errorReason: 'invalid_format' };
|
||||
},
|
||||
|
||||
validateBackupCode: (code) => {
|
||||
if (!code || typeof code !== 'string') {
|
||||
return { valid: false };
|
||||
}
|
||||
|
||||
const normalized = code.replace(/[^a-zA-Z0-9]/g, '').toUpperCase();
|
||||
|
||||
if (normalized.length !== 8) {
|
||||
return { valid: false };
|
||||
}
|
||||
|
||||
if (!/^[0-9A-F]{8}$/.test(normalized)) {
|
||||
return { valid: false };
|
||||
}
|
||||
|
||||
return { valid: true, normalizedCode: normalized };
|
||||
},
|
||||
|
||||
extractTicketId: (qrString) => {
|
||||
const result = this.validateQR(qrString);
|
||||
return result.valid ? result.ticketId : null;
|
||||
},
|
||||
|
||||
generateBackupCode: (ticketId) => {
|
||||
if (!/^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(ticketId)) {
|
||||
throw new Error('Invalid ticket ID format');
|
||||
}
|
||||
const cleanId = ticketId.replace(/-/g, '');
|
||||
return cleanId.slice(-8).toUpperCase();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function getQRErrorMessage(result) {
|
||||
if (result.valid) {
|
||||
return 'Valid ticket';
|
||||
}
|
||||
|
||||
switch (result.errorReason) {
|
||||
case 'invalid_format': return 'Invalid QR code format';
|
||||
case 'expired': return 'Ticket has expired';
|
||||
case 'signature_invalid': return 'Invalid or tampered ticket';
|
||||
case 'malformed': return 'Corrupted QR code data';
|
||||
case 'missing_data': return 'Incomplete ticket information';
|
||||
default: return 'Unknown error occurred';
|
||||
}
|
||||
}
|
||||
|
||||
function formatBackupCode(code) {
|
||||
if (code.length !== 8) {
|
||||
return code;
|
||||
}
|
||||
return `${code.substring(0, 4)}-${code.substring(4)}`;
|
||||
}
|
||||
|
||||
console.log('🎫 Black Canyon Tickets - QR System Demonstration\\n');
|
||||
|
||||
// Sample ticket data
|
||||
const sampleTicketData = {
|
||||
ticketId: '550e8400-e29b-41d4-a716-446655440000',
|
||||
eventId: 'evt_789012345',
|
||||
zone: 'GA',
|
||||
seat: 'A12',
|
||||
expiresInDays: 30
|
||||
};
|
||||
|
||||
console.log('1. QR Code Validation');
|
||||
console.log('====================');
|
||||
const validator = createMockQRValidator();
|
||||
|
||||
// Test different QR formats
|
||||
const testQRs = [
|
||||
// Simple format
|
||||
`TICKET_${sampleTicketData.ticketId}`,
|
||||
|
||||
// Signed token format (simulated)
|
||||
'BCT.v2.eyJraWQiOiIxMjMiLCJlaWQiOiI0NTYifQ.abc123signature',
|
||||
|
||||
// Legacy UUID only
|
||||
sampleTicketData.ticketId,
|
||||
|
||||
// Invalid formats
|
||||
'INVALID_QR_CODE',
|
||||
'BCT.v2.invalid.signature',
|
||||
'TICKET_not-a-uuid'
|
||||
];
|
||||
|
||||
testQRs.forEach((qr, index) => {
|
||||
console.log(`Test ${index + 1}: ${qr.substring(0, 40)}${qr.length > 40 ? '...' : ''}`);
|
||||
const result = validator.validateQR(qr);
|
||||
console.log(` ✅ Valid: ${result.valid}`);
|
||||
console.log(` 📱 Format: ${result.format}`);
|
||||
if (result.ticketId) {
|
||||
console.log(` 🎫 Ticket ID: ${result.ticketId}`);
|
||||
}
|
||||
if (result.eventId) {
|
||||
console.log(` 🎪 Event ID: ${result.eventId}`);
|
||||
}
|
||||
if (!result.valid) {
|
||||
console.log(` ❌ Error: ${getQRErrorMessage(result)}`);
|
||||
}
|
||||
if (result.metadata) {
|
||||
if (result.metadata.expiresAt) {
|
||||
const expires = new Date(result.metadata.expiresAt * 1000);
|
||||
console.log(` ⏰ Expires: ${expires.toLocaleString()}`);
|
||||
}
|
||||
if (result.metadata.zone) {
|
||||
console.log(` 🏟️ Zone: ${result.metadata.zone}`);
|
||||
}
|
||||
if (result.metadata.seat) {
|
||||
console.log(` 💺 Seat: ${result.metadata.seat}`);
|
||||
}
|
||||
}
|
||||
console.log();
|
||||
});
|
||||
|
||||
console.log('2. Backup Code Validation');
|
||||
console.log('=========================');
|
||||
const testBackupCodes = [
|
||||
'55440000', // Valid (last 8 chars of ticket ID)
|
||||
'1234ABCD', // Valid hex format
|
||||
'12345', // Too short
|
||||
'123456789', // Too long
|
||||
'1234GHIJ', // Invalid characters
|
||||
'' // Empty
|
||||
];
|
||||
|
||||
testBackupCodes.forEach((code, index) => {
|
||||
console.log(`Test ${index + 1}: "${code}"`);
|
||||
const result = validator.validateBackupCode(code);
|
||||
console.log(` ✅ Valid: ${result.valid}`);
|
||||
if (result.valid && result.normalizedCode) {
|
||||
console.log(` 🔢 Normalized: ${formatBackupCode(result.normalizedCode)}`);
|
||||
}
|
||||
console.log();
|
||||
});
|
||||
|
||||
console.log('3. Backup Code Generation');
|
||||
console.log('========================');
|
||||
const testTicketIds = [
|
||||
sampleTicketData.ticketId,
|
||||
'123e4567-e89b-12d3-a456-426614174000',
|
||||
'invalid-uuid'
|
||||
];
|
||||
|
||||
testTicketIds.forEach((ticketId, index) => {
|
||||
console.log(`Test ${index + 1}: ${ticketId}`);
|
||||
try {
|
||||
const backupCode = validator.generateBackupCode(ticketId);
|
||||
console.log(` 🔢 Backup Code: ${formatBackupCode(backupCode)}`);
|
||||
} catch (error) {
|
||||
console.log(` ❌ Error: ${error.message}`);
|
||||
}
|
||||
console.log();
|
||||
});
|
||||
|
||||
console.log('4. QR Format Detection');
|
||||
console.log('======================');
|
||||
const mixedQRs = [
|
||||
'TICKET_550e8400-e29b-41d4-a716-446655440000',
|
||||
'BCT.v2.eyJ0aWQiOiIxMjMiLCJlaWQiOiI0NTYifQ.signature',
|
||||
'550e8400-e29b-41d4-a716-446655440000', // Legacy UUID only
|
||||
'MANUAL_55440000'
|
||||
];
|
||||
|
||||
mixedQRs.forEach((qr, index) => {
|
||||
console.log(`QR ${index + 1}: ${qr}`);
|
||||
const ticketId = validator.extractTicketId(qr);
|
||||
if (ticketId) {
|
||||
console.log(` 🎫 Extracted Ticket ID: ${ticketId}`);
|
||||
try {
|
||||
const backupCode = validator.generateBackupCode(ticketId);
|
||||
console.log(` 🔢 Backup Code: ${formatBackupCode(backupCode)}`);
|
||||
} catch (error) {
|
||||
console.log(` ❌ Backup Code Error: ${error.message}`);
|
||||
}
|
||||
} else {
|
||||
console.log(` ❌ Could not extract ticket ID`);
|
||||
}
|
||||
console.log();
|
||||
});
|
||||
|
||||
console.log('5. Manual Entry Flow Simulation');
|
||||
console.log('===============================');
|
||||
console.log('Simulating gate staff manual entry scenarios:');
|
||||
console.log();
|
||||
|
||||
// Scenario 1: Perfect entry
|
||||
console.log('Scenario 1: Gate staff enters backup code correctly');
|
||||
const perfectCode = '55440000';
|
||||
console.log(`Staff enters: "${perfectCode}"`);
|
||||
const result1 = validator.validateBackupCode(perfectCode);
|
||||
if (result1.valid) {
|
||||
console.log(`✅ Code accepted: ${formatBackupCode(result1.normalizedCode)}`);
|
||||
console.log(`🚪 Entry granted`);
|
||||
} else {
|
||||
console.log(`❌ Code rejected`);
|
||||
}
|
||||
console.log();
|
||||
|
||||
// Scenario 2: Entry with hyphens
|
||||
console.log('Scenario 2: Gate staff enters code with formatting');
|
||||
const formattedCode = '5544-0000';
|
||||
console.log(`Staff enters: "${formattedCode}"`);
|
||||
const result2 = validator.validateBackupCode(formattedCode);
|
||||
if (result2.valid) {
|
||||
console.log(`✅ Code accepted: ${formatBackupCode(result2.normalizedCode)}`);
|
||||
console.log(`🚪 Entry granted`);
|
||||
} else {
|
||||
console.log(`❌ Code rejected`);
|
||||
}
|
||||
console.log();
|
||||
|
||||
// Scenario 3: Invalid entry
|
||||
console.log('Scenario 3: Gate staff makes typing error');
|
||||
const errorCode = '5544000'; // Missing digit
|
||||
console.log(`Staff enters: "${errorCode}"`);
|
||||
const result3 = validator.validateBackupCode(errorCode);
|
||||
if (result3.valid) {
|
||||
console.log(`✅ Code accepted`);
|
||||
} else {
|
||||
console.log(`❌ Code rejected - ask customer to show physical ticket`);
|
||||
console.log(`💡 Suggestion: Check last 8 characters on ticket bottom`);
|
||||
}
|
||||
console.log();
|
||||
|
||||
console.log('6. Security Features');
|
||||
console.log('===================');
|
||||
console.log('✅ Signed tokens prevent counterfeiting');
|
||||
console.log('✅ UUID ticket IDs prevent enumeration attacks');
|
||||
console.log('✅ Backup codes are last 8 characters (not sequential)');
|
||||
console.log('✅ HMAC signatures detect tampering');
|
||||
console.log('✅ Time-based expiration prevents replay attacks');
|
||||
console.log('✅ Offline validation available for signed tokens');
|
||||
console.log();
|
||||
|
||||
console.log('7. Production Recommendations');
|
||||
console.log('=============================');
|
||||
console.log('✅ Use signed tokens (BCT.v2.{payload}.{signature}) for security');
|
||||
console.log('✅ Implement proper HMAC-SHA256 signatures in production');
|
||||
console.log('✅ Rotate signing keys quarterly');
|
||||
console.log('✅ Use Error Correction Level M (15%) for most use cases');
|
||||
console.log('✅ Use Error Correction Level H (30%) for thermal printers');
|
||||
console.log('✅ Include backup codes on all printed tickets');
|
||||
console.log('✅ Train gate staff on manual entry procedures');
|
||||
console.log('✅ Test QR codes across different devices and lighting conditions');
|
||||
console.log('✅ Monitor QR scan success rates and manual entry frequency');
|
||||
console.log();
|
||||
|
||||
console.log('🚀 QR System Demo Complete!');
|
||||
console.log('Visit /scanner?eventId=test-event-123 to test the scanner interface');
|
||||
console.log(`Development server: http://localhost:5174`);
|
||||
console.log();
|
||||
|
||||
console.log('📱 Manual Entry Instructions for Gate Staff:');
|
||||
console.log('============================================');
|
||||
console.log('1. If QR code won\'t scan, click the # button');
|
||||
console.log('2. Enter the last 8 characters from bottom of ticket');
|
||||
console.log('3. Characters can be numbers 0-9 or letters A-F');
|
||||
console.log('4. System will show XXXX-XXXX format as you type');
|
||||
console.log('5. Press Submit when all 8 characters entered');
|
||||
console.log('6. Green checkmark = valid ticket, allow entry');
|
||||
console.log('7. Red X = invalid code, ask to see physical ticket');
|
||||
console.log();
|
||||
175
reactrebuild0825/scripts/qr-system-demo.ts
Normal file
175
reactrebuild0825/scripts/qr-system-demo.ts
Normal file
@@ -0,0 +1,175 @@
|
||||
/**
|
||||
* QR System Demonstration Script
|
||||
* Shows QR validation, backup code generation, and manual entry flow
|
||||
*/
|
||||
|
||||
import { QRValidator, createQRValidator, getQRErrorMessage, formatBackupCode } from '../src/lib/qr-validator';
|
||||
import { QRGenerator, createQRGenerator, generateTicketQR, generateQRForFormat, validateQRData } from '../src/lib/qr-generator';
|
||||
|
||||
console.log('🎫 Black Canyon Tickets - QR System Demonstration\n');
|
||||
|
||||
// Sample ticket data
|
||||
const sampleTicketData = {
|
||||
ticketId: '550e8400-e29b-41d4-a716-446655440000',
|
||||
eventId: 'evt_789012345',
|
||||
zone: 'GA',
|
||||
seat: 'A12',
|
||||
expiresInDays: 30
|
||||
};
|
||||
|
||||
console.log('1. Ticket Data Validation');
|
||||
console.log('=========================');
|
||||
const validation = validateQRData(sampleTicketData);
|
||||
console.log(`✅ Valid: ${validation.valid}`);
|
||||
if (!validation.valid) {
|
||||
console.log(`❌ Errors: ${validation.errors.join(', ')}`);
|
||||
}
|
||||
console.log();
|
||||
|
||||
console.log('2. QR Code Generation');
|
||||
console.log('====================');
|
||||
const generator = createQRGenerator();
|
||||
|
||||
// Generate QR for different formats
|
||||
const formats = ['email', 'print', 'thermal', 'wallet'] as const;
|
||||
for (const format of formats) {
|
||||
const { qr, svg } = generateQRForFormat(sampleTicketData, format);
|
||||
console.log(`📱 ${format.toUpperCase()} Format:`);
|
||||
console.log(` QR Data: ${qr.qrData.substring(0, 50)}...`);
|
||||
console.log(` Backup Code: ${qr.backupCode}`);
|
||||
console.log(` Format: ${qr.metadata.format}`);
|
||||
console.log(` Generated: ${new Date(qr.metadata.generatedAt).toLocaleString()}`);
|
||||
if (qr.metadata.expiresAt) {
|
||||
console.log(` Expires: ${new Date(qr.metadata.expiresAt).toLocaleString()}`);
|
||||
}
|
||||
console.log();
|
||||
}
|
||||
|
||||
console.log('3. QR Code Validation');
|
||||
console.log('====================');
|
||||
const validator = createQRValidator();
|
||||
|
||||
// Test different QR formats
|
||||
const testQRs = [
|
||||
// Simple format
|
||||
`TICKET_${sampleTicketData.ticketId}`,
|
||||
|
||||
// Signed token format (generated)
|
||||
generateTicketQR(sampleTicketData).qrData,
|
||||
|
||||
// Invalid formats
|
||||
'INVALID_QR_CODE',
|
||||
'BCT.v2.invalid.signature',
|
||||
'TICKET_not-a-uuid'
|
||||
];
|
||||
|
||||
testQRs.forEach((qr, index) => {
|
||||
console.log(`Test ${index + 1}: ${qr.substring(0, 40)}${qr.length > 40 ? '...' : ''}`);
|
||||
const result = validator.validateQR(qr);
|
||||
console.log(` ✅ Valid: ${result.valid}`);
|
||||
console.log(` 📱 Format: ${result.format}`);
|
||||
if (result.ticketId) {
|
||||
console.log(` 🎫 Ticket ID: ${result.ticketId}`);
|
||||
}
|
||||
if (result.eventId) {
|
||||
console.log(` 🎪 Event ID: ${result.eventId}`);
|
||||
}
|
||||
if (!result.valid) {
|
||||
console.log(` ❌ Error: ${getQRErrorMessage(result)}`);
|
||||
}
|
||||
if (result.metadata) {
|
||||
if (result.metadata.expiresAt) {
|
||||
const expires = new Date(result.metadata.expiresAt * 1000);
|
||||
console.log(` ⏰ Expires: ${expires.toLocaleString()}`);
|
||||
}
|
||||
if (result.metadata.zone) {
|
||||
console.log(` 🏟️ Zone: ${result.metadata.zone}`);
|
||||
}
|
||||
if (result.metadata.seat) {
|
||||
console.log(` 💺 Seat: ${result.metadata.seat}`);
|
||||
}
|
||||
}
|
||||
console.log();
|
||||
});
|
||||
|
||||
console.log('4. Backup Code Validation');
|
||||
console.log('=========================');
|
||||
const testBackupCodes = [
|
||||
'55440000', // Valid (last 8 chars of ticket ID)
|
||||
'1234ABCD', // Valid hex format
|
||||
'12345', // Too short
|
||||
'123456789', // Too long
|
||||
'1234GHIJ', // Invalid characters
|
||||
'' // Empty
|
||||
];
|
||||
|
||||
testBackupCodes.forEach((code, index) => {
|
||||
console.log(`Test ${index + 1}: "${code}"`);
|
||||
const result = validator.validateBackupCode(code);
|
||||
console.log(` ✅ Valid: ${result.valid}`);
|
||||
if (result.valid && result.normalizedCode) {
|
||||
console.log(` 🔢 Normalized: ${formatBackupCode(result.normalizedCode)}`);
|
||||
}
|
||||
console.log();
|
||||
});
|
||||
|
||||
console.log('5. Backup Code Generation');
|
||||
console.log('========================');
|
||||
const testTicketIds = [
|
||||
sampleTicketData.ticketId,
|
||||
'123e4567-e89b-12d3-a456-426614174000',
|
||||
'invalid-uuid'
|
||||
];
|
||||
|
||||
testTicketIds.forEach((ticketId, index) => {
|
||||
console.log(`Test ${index + 1}: ${ticketId}`);
|
||||
try {
|
||||
const backupCode = generator.generateBackupCode(ticketId);
|
||||
console.log(` 🔢 Backup Code: ${formatBackupCode(backupCode)}`);
|
||||
} catch (error) {
|
||||
console.log(` ❌ Error: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
||||
}
|
||||
console.log();
|
||||
});
|
||||
|
||||
console.log('6. QR Format Detection');
|
||||
console.log('======================');
|
||||
const mixedQRs = [
|
||||
'TICKET_550e8400-e29b-41d4-a716-446655440000',
|
||||
'BCT.v2.eyJ0aWQiOiIxMjMiLCJlaWQiOiI0NTYifQ.signature',
|
||||
'550e8400-e29b-41d4-a716-446655440000', // Legacy UUID only
|
||||
'MANUAL_55440000'
|
||||
];
|
||||
|
||||
mixedQRs.forEach((qr, index) => {
|
||||
console.log(`QR ${index + 1}: ${qr}`);
|
||||
const ticketId = validator.extractTicketId(qr);
|
||||
if (ticketId) {
|
||||
console.log(` 🎫 Extracted Ticket ID: ${ticketId}`);
|
||||
const backupCode = generator.generateBackupCode(ticketId);
|
||||
console.log(` 🔢 Backup Code: ${formatBackupCode(backupCode)}`);
|
||||
} else {
|
||||
console.log(` ❌ Could not extract ticket ID`);
|
||||
}
|
||||
console.log();
|
||||
});
|
||||
|
||||
console.log('7. Production Recommendations');
|
||||
console.log('=============================');
|
||||
console.log('✅ Use signed tokens (BCT.v2.{payload}.{signature}) for security');
|
||||
console.log('✅ Implement proper HMAC-SHA256 signatures in production');
|
||||
console.log('✅ Rotate signing keys quarterly');
|
||||
console.log('✅ Use Error Correction Level M (15%) for most use cases');
|
||||
console.log('✅ Use Error Correction Level H (30%) for thermal printers');
|
||||
console.log('✅ Include backup codes on all printed tickets');
|
||||
console.log('✅ Train gate staff on manual entry procedures');
|
||||
console.log('✅ Test QR codes across different devices and lighting conditions');
|
||||
console.log('✅ Monitor QR scan success rates and manual entry frequency');
|
||||
console.log();
|
||||
|
||||
console.log('🚀 QR System Demo Complete!');
|
||||
console.log('Visit /scanner?eventId=test-event-123 to test the scanner interface');
|
||||
console.log(`Server running at: http://localhost:5174`);
|
||||
console.log();
|
||||
|
||||
export {};
|
||||
82
reactrebuild0825/scripts/setup-stripe-connect.sh
Normal file
82
reactrebuild0825/scripts/setup-stripe-connect.sh
Normal file
@@ -0,0 +1,82 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Stripe Connect Setup Script for Black Canyon Tickets
|
||||
# Run this script to configure Firebase Functions for Stripe Connect
|
||||
|
||||
set -e
|
||||
|
||||
echo "🚀 Setting up Stripe Connect for Black Canyon Tickets"
|
||||
echo "=================================================="
|
||||
|
||||
# Check if Firebase CLI is installed
|
||||
if ! command -v firebase &> /dev/null; then
|
||||
echo "❌ Firebase CLI not found. Install with: npm install -g firebase-tools"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check if logged in to Firebase
|
||||
if ! firebase projects:list &> /dev/null; then
|
||||
echo "⚠️ Not logged in to Firebase. Logging in..."
|
||||
firebase login
|
||||
fi
|
||||
|
||||
# Set project
|
||||
echo "📁 Setting Firebase project to black-canyon-tickets..."
|
||||
firebase use black-canyon-tickets
|
||||
|
||||
echo ""
|
||||
echo "🔑 You need to configure these environment variables:"
|
||||
echo ""
|
||||
echo "Required Stripe keys:"
|
||||
echo " - STRIPE_SECRET_KEY (from Stripe Dashboard)"
|
||||
echo " - STRIPE_WEBHOOK_SECRET (from webhook endpoint setup)"
|
||||
echo " - APP_URL (your application URL)"
|
||||
echo ""
|
||||
|
||||
read -p "Enter your Stripe Secret Key (sk_test_...): " STRIPE_SECRET_KEY
|
||||
read -p "Enter your Stripe Webhook Secret (whsec_...): " STRIPE_WEBHOOK_SECRET
|
||||
read -p "Enter your App URL (e.g., https://portal.blackcanyontickets.com): " APP_URL
|
||||
|
||||
if [ -z "$STRIPE_SECRET_KEY" ] || [ -z "$STRIPE_WEBHOOK_SECRET" ] || [ -z "$APP_URL" ]; then
|
||||
echo "❌ All fields are required. Please run the script again."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "🔧 Setting Firebase Functions configuration..."
|
||||
|
||||
# Set environment variables
|
||||
firebase functions:config:set \
|
||||
stripe.secret_key="$STRIPE_SECRET_KEY" \
|
||||
stripe.webhook_secret="$STRIPE_WEBHOOK_SECRET" \
|
||||
app.url="$APP_URL"
|
||||
|
||||
echo ""
|
||||
echo "📦 Installing Functions dependencies..."
|
||||
cd functions
|
||||
npm install
|
||||
|
||||
echo ""
|
||||
echo "🔨 Building Functions..."
|
||||
npm run build
|
||||
|
||||
echo ""
|
||||
echo "✅ Setup complete!"
|
||||
echo ""
|
||||
echo "📋 Next steps:"
|
||||
echo "1. Deploy functions: firebase deploy --only functions"
|
||||
echo ""
|
||||
echo "2. Configure Stripe webhooks:"
|
||||
echo " Platform webhook URL: https://us-central1-black-canyon-tickets.cloudfunctions.net/stripeWebhook"
|
||||
echo " Events: account.updated"
|
||||
echo ""
|
||||
echo " Connect webhook URL: https://us-central1-black-canyon-tickets.cloudfunctions.net/stripeConnectWebhook"
|
||||
echo " Events: checkout.session.completed, payment_intent.succeeded"
|
||||
echo ""
|
||||
echo "3. Test the integration:"
|
||||
echo " - Start your React app: npm run dev"
|
||||
echo " - Navigate to /org/{orgId}/payments"
|
||||
echo " - Click 'Connect Stripe' to test onboarding"
|
||||
echo ""
|
||||
|
||||
cd ..
|
||||
196
reactrebuild0825/scripts/validate-theme.js
Executable file
196
reactrebuild0825/scripts/validate-theme.js
Executable file
@@ -0,0 +1,196 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
/**
|
||||
* Theme Validation Script
|
||||
* Ensures no hardcoded colors are used in the codebase
|
||||
*/
|
||||
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import { execSync } from 'child_process';
|
||||
import { fileURLToPath } from 'url';
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
|
||||
// Color patterns to detect
|
||||
const FORBIDDEN_PATTERNS = [
|
||||
// Hardcoded hex colors
|
||||
/#[0-9a-fA-F]{3,6}/g,
|
||||
|
||||
// Tailwind color classes we don't want
|
||||
/\b(slate|gray|zinc|neutral|stone)-\d+/g,
|
||||
/\b(red|green|blue|yellow|purple|pink|indigo|cyan|teal|orange|amber|lime|emerald|sky|violet|fuchsia|rose)-\d+/g,
|
||||
|
||||
// Generic color names in class names
|
||||
/\b(text|bg|border)-(white|black)\b/g,
|
||||
|
||||
// RGB/RGBA functions
|
||||
/rgba?\s*\([^)]+\)/g,
|
||||
|
||||
// HSL functions
|
||||
/hsla?\s*\([^)]+\)/g,
|
||||
|
||||
// Gradient utilities with hardcoded colors
|
||||
/\bfrom-(slate|gray|white|black|red|green|blue|yellow|purple|pink|indigo|cyan|teal|orange|amber|lime|emerald|sky|violet|fuchsia|rose)-\d+/g,
|
||||
/\bto-(slate|gray|white|black|red|green|blue|yellow|purple|pink|indigo|cyan|teal|orange|amber|lime|emerald|sky|violet|fuchsia|rose)-\d+/g,
|
||||
];
|
||||
|
||||
// Files to check
|
||||
const EXTENSIONS = ['.tsx', '.ts', '.jsx', '.js', '.css'];
|
||||
const EXCLUDED_DIRS = ['node_modules', '.git', 'dist', 'build'];
|
||||
const EXCLUDED_FILES = [
|
||||
'tailwind.config.js', // Allow CSS variables in config
|
||||
'tokens.css', // Allow generated CSS variables
|
||||
'validate-theme.js', // Allow this script itself
|
||||
];
|
||||
|
||||
// Allowed exceptions (for documentation, comments, etc.)
|
||||
const ALLOWED_EXCEPTIONS = [
|
||||
// Comments about colors
|
||||
/\/\*.*?(#[0-9a-fA-F]{3,6}|slate-\d+).*?\*\//g,
|
||||
/\/\/.*?(#[0-9a-fA-F]{3,6}|slate-\d+)/g,
|
||||
|
||||
// String literals (not class names)
|
||||
/"[^"]*?(#[0-9a-fA-F]{3,6}|slate-\d+)[^"]*?"/g,
|
||||
/'[^']*?(#[0-9a-fA-F]{3,6}|slate-\d+)[^']*?'/g,
|
||||
|
||||
// Console.log and similar
|
||||
/console\.(log|warn|error).*?(#[0-9a-fA-F]{3,6}|slate-\d+)/g,
|
||||
];
|
||||
|
||||
function getAllFiles(dir, extensions = EXTENSIONS) {
|
||||
let results = [];
|
||||
const list = fs.readdirSync(dir);
|
||||
|
||||
list.forEach(file => {
|
||||
const filePath = path.join(dir, file);
|
||||
const stat = fs.statSync(filePath);
|
||||
|
||||
if (stat && stat.isDirectory()) {
|
||||
if (!EXCLUDED_DIRS.includes(file)) {
|
||||
results = results.concat(getAllFiles(filePath, extensions));
|
||||
}
|
||||
} else {
|
||||
const ext = path.extname(file);
|
||||
const basename = path.basename(file);
|
||||
|
||||
if (extensions.includes(ext) && !EXCLUDED_FILES.includes(basename)) {
|
||||
results.push(filePath);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
function removeAllowedExceptions(content) {
|
||||
let cleaned = content;
|
||||
|
||||
ALLOWED_EXCEPTIONS.forEach(pattern => {
|
||||
cleaned = cleaned.replace(pattern, '');
|
||||
});
|
||||
|
||||
return cleaned;
|
||||
}
|
||||
|
||||
function validateFile(filePath) {
|
||||
const content = fs.readFileSync(filePath, 'utf8');
|
||||
const cleanedContent = removeAllowedExceptions(content);
|
||||
const violations = [];
|
||||
|
||||
FORBIDDEN_PATTERNS.forEach(pattern => {
|
||||
const matches = [...cleanedContent.matchAll(pattern)];
|
||||
|
||||
matches.forEach(match => {
|
||||
const lineNumber = content.substring(0, match.index).split('\n').length;
|
||||
violations.push({
|
||||
file: filePath,
|
||||
line: lineNumber,
|
||||
match: match[0],
|
||||
pattern: pattern.toString(),
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
return violations;
|
||||
}
|
||||
|
||||
function main() {
|
||||
console.log('🎨 Validating theme system...\n');
|
||||
|
||||
const projectRoot = path.join(__dirname, '..');
|
||||
const srcDir = path.join(projectRoot, 'src');
|
||||
const files = getAllFiles(srcDir);
|
||||
|
||||
console.log(`Checking ${files.length} files for hardcoded colors...\n`);
|
||||
|
||||
let totalViolations = 0;
|
||||
const violationsByFile = {};
|
||||
|
||||
files.forEach(file => {
|
||||
const violations = validateFile(file);
|
||||
|
||||
if (violations.length > 0) {
|
||||
violationsByFile[file] = violations;
|
||||
totalViolations += violations.length;
|
||||
}
|
||||
});
|
||||
|
||||
if (totalViolations === 0) {
|
||||
console.log('✅ No hardcoded colors found! Theme system is clean.\n');
|
||||
|
||||
// Additional check: ensure semantic classes are being used
|
||||
console.log('🔍 Checking for proper semantic token usage...\n');
|
||||
|
||||
const semanticPatterns = [
|
||||
/\btext-text-(primary|secondary|muted|inverse|disabled)\b/g,
|
||||
/\bbg-bg-(primary|secondary|tertiary|elevated|overlay)\b/g,
|
||||
/\bborder-border\b/g,
|
||||
/\baccent-(primary|secondary|gold)-\d+/g,
|
||||
];
|
||||
|
||||
let semanticUsageCount = 0;
|
||||
|
||||
files.forEach(file => {
|
||||
const content = fs.readFileSync(file, 'utf8');
|
||||
semanticPatterns.forEach(pattern => {
|
||||
const matches = content.match(pattern);
|
||||
if (matches) {
|
||||
semanticUsageCount += matches.length;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
console.log(`✅ Found ${semanticUsageCount} instances of semantic token usage.\n`);
|
||||
|
||||
return process.exit(0);
|
||||
}
|
||||
|
||||
console.log(`❌ Found ${totalViolations} hardcoded color violations:\n`);
|
||||
|
||||
Object.entries(violationsByFile).forEach(([file, violations]) => {
|
||||
console.log(`📄 ${file}:`);
|
||||
|
||||
violations.forEach(violation => {
|
||||
console.log(` Line ${violation.line}: "${violation.match}"`);
|
||||
});
|
||||
|
||||
console.log('');
|
||||
});
|
||||
|
||||
console.log('🚨 VALIDATION FAILED!\n');
|
||||
console.log('Please replace hardcoded colors with semantic tokens from the design system.');
|
||||
console.log('See THEMING.md for guidance on proper token usage.\n');
|
||||
|
||||
console.log('Common replacements:');
|
||||
console.log(' text-slate-900 → text-text-primary');
|
||||
console.log(' text-slate-600 → text-text-secondary');
|
||||
console.log(' bg-white → bg-bg-primary');
|
||||
console.log(' border-slate-200 → border-border');
|
||||
console.log(' #3b82f6 → Use accent-primary-500 or similar\n');
|
||||
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
main();
|
||||
Reference in New Issue
Block a user