// QR Generation and Trust Network Component class QRGenerator { static init() { console.log('QRGenerator component initialized'); this.setupQRValidation(); this.loadTrustNetwork(); } static generateTrustQR(productId, recommenderId, reason, expirationDays = 30) { const qrId = this.generateQRId(); const issuedAt = new Date().toISOString(); const expiresAt = new Date(Date.now() + expirationDays * 24 * 60 * 60 * 1000).toISOString(); const payload = { v: 1, p: recommenderId, m: this.getMerchantId(productId), pr: productId, q: qrId, t: issuedAt, exp: expiresAt, r: reason }; const payloadString = `TSQR:v1|p:${payload.p}|m:${payload.m}|pr:${payload.pr}|q:${payload.q}|t:${payload.t}|exp:${payload.exp}|r:${encodeURIComponent(payload.r)}`; // In a real implementation, this would be signed with the user's private key const signature = this.signPayload(payloadString, recommenderId); const signedPayload = `${payloadString}|sig:${signature}`; return { qrId, payload: signedPayload, expiresAt, status: 'active' }; } static signPayload(payload, userId) { // In a real implementation, this would use WebCrypto API or similar // to sign the payload with the user's private key const message = `${payload}|user:${userId}|nonce:${Math.random().toString(36).substring(7)}`; // Simulate signature generation return btoa(message).substring(0, 44); // Base64 encoded signature } static validateQRScan(scannedPayload, scannerContext = {}) { try { // Parse the QR payload const parsed = this.parseQRPpayload(scannedPayload); if (!parsed) { return { valid: false, error: 'Invalid QR format' }; } // Check expiration if (new Date(parsed.exp) < new Date()) { return { valid: false, error: 'QR code has expired' }; } // Validate signature if (!this.validateSignature(scannedPayload, parsed)) { return { valid: false, error: 'Invalid signature' }; } // Check usage limits const usageCount = this.getUsageCount(parsed.q); const maxUses = this.getMaxUses(parsed); if (usageCount >= maxUses) { return { valid: false, error: 'QR code usage limit reached' }; } // Record the scan this.recordScan(parsed, scannerContext); return { valid: true, data: parsed, recommendation: { productId: parsed.pr, recommenderId: parsed.p, reason: parsed.r, issuedAt: parsed.t } }; } catch (error) { console.error('QR validation error:', error); return { valid: false, error: 'Validation failed' }; } } static parseQRPpayload(payload) { try { // Parse TSQR format const parts = payload.split('|'); const data = {}; parts.forEach(part => { const [key, value] = part.split(':'); if (key && value) { data[key] = value; } }); // Validate required fields const required = ['v', 'p', 'm', 'pr', 'q', 't', 'sig']; const missing = required.filter(field => !data[field]); if (missing.length > 0) { return null; } return data; } catch (error) { return null; } } static validateSignature(payload, parsed) { // In a real implementation, this would verify the signature // against the user's public key try { const payloadWithoutSig = payload.split('|sig:')[0]; const providedSig = parsed.sig; // Simulate signature validation const expectedSig = btoa(payloadWithoutSig).substring(0, 44); return providedSig === expectedSig; } catch (error) { return false; } } static recordScan(parsed, context) { // Record the QR scan for attribution tracking const scanRecord = { qrId: parsed.q, productId: parsed.pr, recommenderId: parsed.p, scannerUserId: context.userId || null, timestamp: new Date().toISOString(), geolocation: context.geolocation || null, userAgent: navigator.userAgent, ip: context.ip || null }; // In a real implementation, this would save to the database console.log('QR scan recorded:', scanRecord); // Store in local storage for demo purposes const scans = JSON.parse(localStorage.getItem('qrScans') || '[]'); scans.push(scanRecord); localStorage.setItem('qrScans', JSON.stringify(scans)); } static getUsageCount(qrId) { // Get how many times this QR has been used const scans = JSON.parse(localStorage.getItem('qrScans') || '[]'); return scans.filter(scan => scan.qrId === qrId).length; } static getMaxUses(parsed) { // Different products may have different usage limits // For now, return a default of 10 uses return 10; } static getMerchantId(productId) { // In a real implementation, this would lookup the merchant ID // For demo purposes, return a mock merchant ID return `merchant_${productId.substring(0, 8)}`; } static generateQRId() { return 'QR' + Date.now().toString(36) + Math.random().toString(36).substring(2, 8); } static setupQRValidation() { // Setup QR scanner validation handlers const scannerInputs = document.querySelectorAll('input[placeholder*="QR code"]'); scannerInputs.forEach(input => { input.addEventListener('input', (e) => { if (e.target.value.trim()) { this.validateManualQR(e.target.value.trim()); } }); }); } static async validateManualQR(payload) { const result = this.validateQRScan(payload, { manualEntry: true }); if (result.valid) { this.showValidationSuccess(result.recommendation); } else { this.showValidationError(result.error); } } static showValidationSuccess(recommendation) { // Show success message with recommendation details const message = ` Valid QR from trusted recommender Product: ${recommendation.productId} Reason: ${recommendation.reason} `; if (window.OneSearchApp) { window.OneSearchApp.showNotification('Trust QR validated successfully', 'success'); } } static showValidationError(error) { // Show error message if (window.OneSearchApp) { window.OneSearchApp.showNotification(`QR validation failed: ${error}`, 'error'); } } static loadTrustNetwork() { // Load recent recommendations from the trust network const networkData = [ { id: 'rec_1', productId: '1', recommenderId: 'user_123', recommenderName: 'Sarah M.', productTitle: 'Kitchen Storage Solutions', timestamp: new Date(Date.now() - 2 * 24 * 60 * 60 * 1000).toISOString(), trustLevel: 'verified', reason: 'Perfect for small kitchens with excellent build quality' }, { id: 'rec_2', productId: '2', recommenderId: 'user_456', recommenderName: 'Mike R.', productTitle: 'Smart Home Automation', timestamp: new Date(Date.now() - 7 * 24 * 60 * 60 * 1000).toISOString(), trustLevel: 'high', reason: 'Seamless integration and reliable performance' }, { id: 'rec_3', productId: '3', recommenderId: 'user_789', recommenderName: 'Lisa K.', productTitle: 'Space-Saving Organizer', timestamp: new Date(Date.now() - 1 * 24 * 60 * 60 * 1000).toISOString(), trustLevel: 'verified', reason: 'Transforms cluttered spaces into organized areas' } ]; this.updateTrustNetworkDisplay(networkData); } static updateTrustNetworkDisplay(networkData) { const container = document.querySelector('#recommendations .space-y-3'); if (!container) return; // Update the recent recommendations display networkData.forEach(rec => { const existingElement = Array.from(container.children).find(el => el.textContent.includes(rec.productTitle) ); if (!existingElement) { const newElement = this.createRecommendationElement(rec); container.insertBefore(newElement, container.firstChild); } }); } static createRecommendationElement(recommendation) { const element = document.createElement('div'); element.className = 'flex items-center space-x-3 p-3 border rounded-lg fade-in'; const timeAgo = this.getTimeAgo(recommendation.timestamp); const trustColor = recommendation.trustLevel === 'verified' ? 'green' : 'blue'; element.innerHTML = `
${recommendation.productTitle}
by ${recommendation.recommenderName} • ${timeAgo}
${recommendation.trustLevel}
`; // Replace icons setTimeout(() => { if (window.feather) { window.feather.replace(); } }, 0); return element; } static getTimeAgo(timestamp) { const now = new Date(); const time = new Date(timestamp); const diffInMs = now - time; const diffInDays = Math.floor(diffInMs / (1000 * 60 * 60 * 24)); if (diffInDays === 0) { return 'today'; } else if (diffInDays === 1) { return 'yesterday'; } else { return `${diffInDays} days ago`; } } static createQRTemplate(productId, productTitle) { return { productId, productTitle, recommendationReasons: [ 'Excellent build quality', 'Great value for money', 'Solved my specific problem', 'Highly recommended by community', 'Verified by multiple users' ], defaultExpirationDays: 30, maxUsages: 10 }; } static exportQRData(qrId) { // Export QR generation data for auditing const scans = JSON.parse(localStorage.getItem('qrScans') || '[]'); const qrScans = scans.filter(scan => scan.qrId === qrId); return { qrId, generatedAt: new Date().toISOString(), totalScans: qrScans.length, scans: qrScans, conversions: qrScans.filter(scan => scan.conversion === true).length }; } } // Export for use in main app if (typeof window !== 'undefined') { window.QRGenerator = QRGenerator; } if (typeof module !== 'undefined' && module.exports) { module.exports = QRGenerator; }