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:
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();
|
||||
Reference in New Issue
Block a user