A Pen by Ram Kishor on CodePen.
Created
May 5, 2025 10:22
-
-
Save shubham91150/6746e8906aa4b821cb6493b03d563b90 to your computer and use it in GitHub Desktop.
Smart 2
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<html> | |
<head> | |
<meta charset="utf-8"> | |
<meta name="viewport" content="width=device-width, initial-scale=1"> | |
<title>Smart QR Scanner</title> | |
<link rel="stylesheet" href="styles.css"> | |
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap" rel="stylesheet"> | |
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css"> | |
<script src="https://unpkg.com/html5-qrcode"></script> | |
</head> | |
<body> | |
<div class="container"> | |
<header> | |
<h1><i class="fas fa-qrcode"></i> Smart QR Scanner</h1> | |
<!-- div class="button-container"> | |
अन्य बटन्स... --> | |
<button id="themeToggle" class="action-button"> | |
<i class="fas fa-moon"></i> | |
<span>डार्क</span> | |
</button> | |
</div> | |
</header> | |
<div class="button-container"> | |
<button id="startButton" class="action-button"> | |
<i class="fas fa-camera"></i> | |
<span>Scan</span> | |
</button> | |
<button id="flashLightButton" class="action-button"> | |
<i class="fas fa-bolt"></i> | |
<span>Flash On</span> | |
</button> | |
<input type="file" id="fileInput" accept="image/*" style="display: none;"> | |
<button id="fileButton" class="action-button"> | |
<i class="fas fa-image"></i> | |
<span>Upload</span> | |
</button> | |
</div> | |
<div id="qr-reader" class="reader-container"></div> | |
<div id="qr-reader-results" class="results-container"> | |
<div id="result-content"></div> | |
<div id="action-buttons" class="action-buttons-container"></div> | |
</div> | |
<div class="history-section"> | |
<div class="history-header"> | |
<h2><i class="fas fa-history"></i> Recent Scans</h2> | |
<button id="clearHistory" class="clear-button"> | |
<i class="fas fa-trash"></i> | |
<span>Clear</span> | |
</button> | |
</div> | |
<div id="scan-history"></div> | |
</div> | |
</div> | |
<script src="script.js"></script> | |
</body> | |
</html> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* Add this at the top of your script.js, just after the CONFIG object | |
function debug(message) { | |
if (CONFIG.DEBUG) { | |
console.log(`[Debug] ${message}`); | |
} | |
}// Configuration object with all settings | |
// CONFIG में थीम संबंधित सेटिंग जोड़ें | |
const CONFIG = Object.freeze({ | |
// अन्य सेटिंग... | |
THEME: { | |
STORAGE_KEY: 'qrScannerTheme', | |
DEFAULT: 'light' | |
} | |
}); | |
// QRScannerState क्लास में थीम कंट्रोल जोड़ें | |
constructor() { | |
// अन्य इनिशियलाइजेशन... | |
this.currentTheme = 'light'; | |
this.loadTheme(); | |
} | |
loadTheme() { | |
try { | |
this.currentTheme = localStorage.getItem(CONFIG.THEME.STORAGE_KEY) || CONFIG.THEME.DEFAULT; | |
this.applyTheme(this.currentTheme); | |
} catch (error) { | |
console.error('Failed to load theme:', error); | |
this.currentTheme = CONFIG.THEME.DEFAULT; | |
} | |
} | |
toggleTheme() { | |
const newTheme = this.currentTheme === 'light' ? 'dark' : 'light'; | |
this.applyTheme(newTheme); | |
this.currentTheme = newTheme; | |
localStorage.setItem(CONFIG.THEME.STORAGE_KEY, newTheme); | |
} | |
applyTheme(theme) { | |
document.body.classList.remove('theme-light', 'theme-dark'); | |
document.body.classList.add(`theme-${theme}`); | |
// थीम आइकॉन अपडेट करें | |
const themeButton = document.getElementById('themeToggle'); | |
if (themeButton) { | |
themeButton.innerHTML = theme === 'light' | |
? '<i class="fas fa-moon"></i><span>डार्क</span>' | |
: '<i class="fas fa-sun"></i><span>लाइट</span>'; | |
} | |
} | |
const CONFIG = Object.freeze({ | |
SCANNER: { | |
FPS: 10, | |
QR_BOX_SIZE: 250, | |
ASPECT_RATIO: 1.0, | |
MAX_FILE_SIZE: 5 * 1024 * 1024 // 5MB | |
}, | |
HISTORY: { | |
MAX_ITEMS: 50, | |
STORAGE_KEY: 'qrScanHistory' | |
}, | |
ALLOWED_FILE_TYPES: ['image/jpeg', 'image/png', 'image/webp', 'image/svg' ], | |
DEBUG: true | |
}); | |
// QR Scanner State Management | |
class QRScannerState { | |
constructor() { | |
this.html5QrCode = null; | |
this.scanning = false; | |
this.flashOn = false; | |
this.history = []; | |
this.loadHistory(); | |
}*/ | |
// Configuration object with all settings | |
const CONFIG = Object.freeze({ | |
SCANNER: { | |
FPS: 10, | |
QR_BOX_SIZE: 250, | |
ASPECT_RATIO: 1.0, | |
MAX_FILE_SIZE: 5 * 1024 * 1024 // 5MB | |
}, | |
HISTORY: { | |
MAX_ITEMS: 50, | |
STORAGE_KEY: 'qrScanHistory' | |
}, | |
THEME: { | |
STORAGE_KEY: 'qrScannerTheme', | |
DEFAULT: 'light' | |
}, | |
ALLOWED_FILE_TYPES: ['image/jpeg', 'image/png', 'image/webp', 'image/svg+xml'], | |
DEBUG: true | |
}); | |
// Debug function | |
function debug(message) { | |
if (CONFIG.DEBUG) { | |
console.log(`[Debug] ${message}`); | |
} | |
} | |
// QR Scanner State Management | |
class QRScannerState { | |
constructor() { | |
this.html5QrCode = null; | |
this.scanning = false; | |
this.flashOn = false; | |
this.history = []; | |
this.currentTheme = CONFIG.THEME.DEFAULT; | |
this.loadHistory(); | |
this.loadTheme(); | |
} | |
loadTheme() { | |
try { | |
this.currentTheme = localStorage.getItem(CONFIG.THEME.STORAGE_KEY) || CONFIG.THEME.DEFAULT; | |
this.applyTheme(this.currentTheme); | |
} catch (error) { | |
console.error('Failed to load theme:', error); | |
this.currentTheme = CONFIG.THEME.DEFAULT; | |
} | |
} | |
toggleTheme() { | |
const newTheme = this.currentTheme === 'light' ? 'dark' : 'light'; | |
this.applyTheme(newTheme); | |
this.currentTheme = newTheme; | |
localStorage.setItem(CONFIG.THEME.STORAGE_KEY, newTheme); | |
} | |
applyTheme(theme) { | |
document.body.classList.remove('theme-light', 'theme-dark'); | |
document.body.classList.add(`theme-${theme}`); | |
// थीम आइकॉन अपडेट करें | |
const themeButton = document.getElementById('themeToggle'); | |
if (themeButton) { | |
themeButton.innerHTML = theme === 'light' | |
? '<i class="fas fa-moon"></i><span>डार्क</span>' | |
: '<i class="fas fa-sun"></i><span>लाइट</span>'; | |
} | |
} | |
// Add your other methods like loadHistory here | |
} | |
// Set up event listeners after DOM is loaded | |
document.addEventListener('DOMContentLoaded', () => { | |
const qrState = new QRScannerState(); | |
const themeButton = document.getElementById('themeToggle'); | |
if (themeButton) { | |
themeButton.addEventListener('click', () => qrState.toggleTheme()); | |
} | |
// Add other event listeners here | |
}); | |
loadHistory() { | |
try { | |
this.history = JSON.parse(localStorage.getItem(CONFIG.HISTORY.STORAGE_KEY)) || []; | |
} catch (error) { | |
console.error('Failed to load history:', error); | |
this.history = []; | |
} | |
} | |
saveHistory() { | |
try { | |
localStorage.setItem(CONFIG.HISTORY.STORAGE_KEY, JSON.stringify(this.history)); | |
} catch (error) { | |
this.showNotification('Failed to save history', 'error'); | |
} | |
} | |
addToHistory(text, type) { | |
this.history.unshift({ | |
text, | |
type, | |
timestamp: new Date().toISOString() | |
}); | |
if (this.history.length > CONFIG.HISTORY.MAX_ITEMS) { | |
this.history.pop(); | |
} | |
this.saveHistory(); | |
this.updateHistoryDisplay(); | |
} | |
clearHistory() { | |
this.history = []; | |
localStorage.removeItem(CONFIG.HISTORY.STORAGE_KEY); | |
this.updateHistoryDisplay(); | |
this.showNotification('History cleared', 'success'); | |
} | |
showNotification(message, type = 'success', duration = 3000) { | |
const notification = document.createElement('div'); | |
notification.className = `notification ${type}`; | |
notification.textContent = message; | |
notification.setAttribute('role', 'alert'); | |
document.body.appendChild(notification); | |
setTimeout(() => { | |
notification.classList.add('show'); | |
setTimeout(() => { | |
notification.classList.remove('show'); | |
setTimeout(() => notification.remove(), 300); | |
}, duration); | |
}, 100); | |
} | |
async initializeScanner() { | |
try { | |
this.html5QrCode = new Html5Qrcode("qr-reader"); | |
await this.checkPermissions(); | |
} catch (error) { | |
this.showNotification('Failed to initialize scanner: ' + error.message, 'error'); | |
throw error; | |
} | |
} | |
async checkPermissions() { | |
try { | |
const stream = await navigator.mediaDevices.getUserMedia({ video: true }); | |
stream.getTracks().forEach(track => track.stop()); | |
} catch (error) { | |
throw new Error('Camera permission denied'); | |
} | |
} | |
async startScanning() { | |
if (!this.html5QrCode) { | |
await this.initializeScanner(); | |
} | |
try { | |
const config = { | |
fps: CONFIG.SCANNER.FPS, | |
qrbox: { | |
width: CONFIG.SCANNER.QR_BOX_SIZE, | |
height: CONFIG.SCANNER.QR_BOX_SIZE | |
}, | |
aspectRatio: CONFIG.SCANNER.ASPECT_RATIO | |
}; | |
await this.html5QrCode.start( | |
{ facingMode: "environment" }, | |
config, | |
this.handleScanSuccess.bind(this), | |
this.handleScanError.bind(this) | |
); | |
this.scanning = true; | |
this.updateScanButton(true); | |
} catch (error) { | |
this.showNotification('Failed to start scanner: ' + error.message, 'error'); | |
} | |
} | |
async stopScanning() { | |
if (this.html5QrCode && this.scanning) { | |
try { | |
await this.html5QrCode.stop(); | |
this.scanning = false; | |
this.updateScanButton(false); | |
if (this.flashOn) { | |
await this.toggleFlash(false); | |
} | |
} catch (error) { | |
this.showNotification('Failed to stop scanner: ' + error.message, 'error'); | |
} | |
} | |
} | |
updateScanButton(scanning) { | |
const startButton = document.getElementById('startButton'); | |
if (startButton) { | |
startButton.innerHTML = scanning ? | |
'<i class="fas fa-stop"></i><span>Stop</span>' : | |
'<i class="fas fa-camera"></i><span>Scan</span>'; | |
startButton.classList.toggle('active', scanning); | |
} | |
} | |
async toggleFlash(force = null) { | |
if (!this.html5QrCode || !this.scanning) { | |
this.showNotification('Scanner must be active to use flash', 'error'); | |
return; | |
} | |
try { | |
// Get video element | |
const videoElement = document.querySelector('#qr-reader video'); | |
if (!videoElement || !videoElement.srcObject) { | |
throw new Error('No active video stream found'); | |
} | |
// Get video track | |
const track = videoElement.srcObject.getVideoTracks()[0]; | |
if (!track) { | |
throw new Error('No video track available'); | |
} | |
// Check if torch is supported | |
const capabilities = track.getCapabilities(); | |
if (!capabilities || !capabilities.torch) { | |
throw new Error('Flash not supported on this device'); | |
} | |
// Determine new state | |
const newState = force !== null ? force : !this.flashOn; | |
// Toggle torch | |
await track.applyConstraints({ advanced: [{ torch: newState }] }); | |
// Update state | |
this.flashOn = newState; | |
// Update UI | |
const flashButton = document.getElementById('flashLightButton'); | |
if (flashButton) { | |
flashButton.innerHTML = this.flashOn ? | |
'<i class="fas fa-bolt"></i><span>Flash Off</span>' : | |
'<i class="fas fa-bolt"></i><span>Flash On</span>'; | |
flashButton.classList.toggle('active', this.flashOn); | |
} | |
// Show success notification | |
this.showNotification(this.flashOn ? 'Flash turned on' : 'Flash turned off', 'success'); | |
} catch (error) { | |
debug(`Flash error: ${error.message}`); | |
this.showNotification('Flash control not available on this device', 'error'); | |
} | |
} | |
//*/ | |
async handleScanSuccess(decodedText, decodedResult) { | |
const contentType = this.detectContentType(decodedText); | |
this.addToHistory(decodedText, contentType); | |
this.displayResult(decodedText, contentType); | |
this.playSuccessSound(); | |
await this.stopScanning(); | |
} | |
handleScanError(error) { | |
if (!(error instanceof Html5QrcodeError)) { | |
this.showNotification('Scan error: ' + error.message, 'error'); | |
} | |
} | |
detectContentType(content) { | |
if (!content) return 'unknown'; | |
const patterns = { | |
url: /^https?:\/\//i, | |
email: /^mailto:|^[^@]+@[^.]+\.[a-z]{2,}$/i, | |
phone: /^tel:|^\+?[\d\s-]{6,}$/, | |
wifi: /^WIFI:(?:T:|S:|P:|H:)/i, | |
geo: /^geo:[-?\d.]+,[-?\d.]+/, | |
sms: /^sms:/i, | |
upiId: /^[a-zA-Z0-9.-_]+@[a-zA-Z]+$/, | |
// For handling full UPI URLs | |
upiUrl: /^upi:\/\/pay\?/i // नया पैटर्न UPI URL के लिए | |
}; | |
for (const [type, pattern] of Object.entries(patterns)) { | |
if (pattern.test(content)) return type; | |
} | |
return 'text'; | |
} | |
validateUrl(url) { | |
try { | |
const parsed = new URL(url); | |
const allowedProtocols = ['http:', 'https:']; | |
return allowedProtocols.includes(parsed.protocol); | |
} catch { | |
return false; | |
} | |
} | |
displayResult(decodedText, contentType) { | |
const resultContainer = document.getElementById('qr-reader-results'); | |
const resultContent = document.getElementById('result-content'); | |
const actionButtons = document.getElementById('action-buttons'); | |
if (!resultContainer || !resultContent || !actionButtons) return; | |
resultContent.textContent = decodedText; | |
actionButtons.innerHTML = ''; | |
const actions = { | |
url: [ | |
['Open URL', () => this.validateUrl(decodedText) && window.open(decodedText, '_blank')], | |
['Copy URL', () => this.copyToClipboard(decodedText)] | |
], | |
email: [ | |
['Send Email', () => window.open(`mailto:${decodedText}`)], | |
['Copy Email', () => this.copyToClipboard(decodedText.replace('mailto:', ''))] | |
], | |
phone: [ | |
['Call', () => window.open(`tel:${decodedText}`)], | |
['Copy Number', () => this.copyToClipboard(decodedText.replace('tel:', ''))] | |
], | |
wifi: [ | |
['Copy Network', () => this.copyToClipboard(this.parseWifiQR(decodedText).ssid)], | |
['Copy Password', () => this.copyToClipboard(this.parseWifiQR(decodedText).password)] | |
], | |
text: [ | |
['Copy Text', () => this.copyToClipboard(decodedText)] | |
] | |
}; | |
// Handle UPI URL | |
if (contentType === 'upiUrl') { | |
try { | |
const rawParams = decodeURIComponent(decodedText.replace('upi://pay?', '')); | |
const params = new URLSearchParams(rawParams); | |
const paymentDetails = { | |
vpa: params.get('pa'), | |
name: params.get('pn') || 'Recipient', | |
amount: params.get('am'), | |
fullUrl: decodedText | |
}; | |
// Add actions for UPI | |
actions['upiUrl'] = [ | |
['Pay via UPI', () => this.shareUpiDetails(paymentDetails)], | |
['Copy UPI ID', () => this.copyToClipboard(paymentDetails.vpa)], | |
['Show Details', () => this.showUpiDetails(paymentDetails)] | |
]; | |
} catch (error) { | |
console.error('Failed to parse UPI URL:', error); | |
this.showNotification('Invalid UPI QR code', 'error'); | |
} | |
} | |
// Handle UPI ID | |
if (contentType === 'upiId') { | |
const paymentDetails = { | |
vpa: decodedText, | |
name: 'UPI Recipient', | |
fullUrl: `upi://pay?pa=${encodeURIComponent(decodedText)}` | |
}; | |
actions['upiId'] = [ | |
['Pay via UPI', () => this.shareUpiDetails(paymentDetails)], | |
['Copy UPI ID', () => this.copyToClipboard(decodedText)] | |
]; | |
} | |
(actions[contentType] || actions.text).forEach(([text, onClick]) => { | |
const button = document.createElement('button'); | |
button.textContent = text; | |
button.className = 'smart-action-button'; | |
button.addEventListener('click', onClick); | |
actionButtons.appendChild(button); | |
}); | |
} | |
parseWifiQR(content) { | |
const match = content.match(/WIFI:(?:T:([^;]*);)?(?:S:([^;]*);)?(?:P:([^;]*);)?(?:H:([^;]*);)?/i); | |
return { | |
type: match?.[1] || 'WPA', | |
ssid: match?.[2] || '', | |
password: match?.[3] || '', | |
hidden: match?.[4] === 'true' | |
}; | |
} | |
// add | |
/*async shareUpiDetails(details) { | |
// Better mobile detection using maxTouchPoints | |
const isMobile = navigator.maxTouchPoints > 0; | |
if (!isMobile) { | |
this.showNotification('Please use a mobile device for UPI payments', 'warning'); | |
return; | |
} | |
// Try to detect installed payment apps | |
const paymentApps = [ | |
{ | |
name: 'Google Pay', | |
package: 'com.google.android.apps.nbu.paisa.user', | |
uri: `intent:#Intent;action=android.intent.action.VIEW;scheme=${encodeURIComponent(details.fullUrl)};package=com.google.android.apps.nbu.paisa.user;end`, | |
icon: 'https://play-lh.googleusercontent.com/HArtbyi53u0jnqhnnxkQnMx9dHOERNcprZyKnInd2nrfM7Wd9ivMNTiz7IJP6-mSpwk' | |
}, | |
{ | |
name: 'PhonePe', | |
package: 'com.phonepe.app', | |
uri: `phonepe://${details.fullUrl.replace('upi://', '')}`, | |
icon: 'https://play-lh.googleusercontent.com/6iyA2zVz5PyyMjK5SIxdUhrb7oh9cYVXJ93q6DZkmx07Er1o90PXYeo6mzL4VC2Gj9s' | |
}, | |
{ | |
name: 'Paytm', | |
package: 'net.one97.paytm', | |
uri: `paytmmp://${details.fullUrl.replace('upi://', '')}`, | |
icon: 'https://play-lh.googleusercontent.com/k7tS5pSdVE_m5KkWKHpF7CSch0GQ4NOTaRpukZeRzqcWFcfRQjGzTqfnTYXiWzDDyes' | |
}, | |
{ | |
name: 'BHIM UPI', | |
package: 'in.org.npci.upiapp', | |
uri: details.fullUrl, | |
icon: 'https://play-lh.googleusercontent.com/B5cNBA15IxjCT-8UTXEWgiPcGkJ1C07iHKwm2Hbs8xR3PnJvZ0swTag3abdD7XAJ7Q' | |
}, | |
{ | |
name: 'Amazon Pay', | |
package: 'in.amazon.mShop.android.shopping', | |
uri: `amazonpay://upi/pay?${details.fullUrl.split('?')[1]}`, | |
icon: 'https://play-lh.googleusercontent.com/5pwUy5lTMgaG2KPOYnbLUBbKoJNF1xR9zrSJceMb_Gm5LKJ6KCmTIEgKdYhwQj3c8ww' | |
}, | |
{ | |
name: 'Airtel Thanks', | |
package: 'com.myairtelapp', | |
uri: details.fullUrl, | |
icon: 'https://play-lh.googleusercontent.com/KiJy82oKG_1Z2Hl-zbqT-HSjJxYvnHCxJgBQe8fGCt2vJv7Scsjxc3hm8-02wp03w-M' | |
}, | |
{ | |
name: 'Samsung Pay', | |
package: 'com.samsung.android.samsungpay.gear', | |
uri: details.fullUrl, | |
icon: 'https://play-lh.googleusercontent.com/1-ue76xmyBgJpVc1RkWLgYfKO-iEfW4iq4MQxyFkOO1WnO-GoxBK_1W6aaXMS0MzUg' | |
}, | |
{ | |
name: 'Cred', | |
package: 'com.dreamplug.androidapp', | |
uri: details.fullUrl, | |
icon: 'https://play-lh.googleusercontent.com/Dnlvff46UTr7YFMbAzBGjdoOqCRjQumZGRI6la8BPFZ9Kz3bTaYwLHGZZPvvE-p1lw' | |
}, | |
{ | |
name: 'iMobile Pay', | |
package: 'com.csam.icici.bank.imobile', | |
uri: details.fullUrl, | |
icon: 'https://play-lh.googleusercontent.com/DRs5_PfaJbjvLvHcXkdAli1qSGEspFT3OAV0SN_Rw9IKQOGQWFtZ0WQVnKC5-QtcCw' | |
} | |
]; | |
// Create a modal with app selection | |
const modal = document.createElement('div'); | |
const modalId = 'payment-app-modal-' + Date.now(); | |
modal.id = modalId; | |
modal.style.cssText = ` | |
position: fixed; | |
top: 0; | |
left: 0; | |
right: 0; | |
bottom: 0; | |
background: rgba(0,0,0,0.5); | |
display: flex; | |
align-items: center; | |
justify-content: center; | |
z-index: 1000; | |
animation: fadeIn 0.2s ease-out; | |
`; | |
modal.innerHTML = ` | |
<style> | |
@keyframes fadeIn { | |
from { opacity: 0; } | |
to { opacity: 1; } | |
} | |
@keyframes fadeOut { | |
from { opacity: 1; } | |
to { opacity: 0; } | |
} | |
.payment-app-btn { | |
display: flex; | |
align-items: center; | |
padding: 12px; | |
margin: 5px 0; | |
border: 1px solid #eee; | |
border-radius: 10px; | |
background: white; | |
width: 100%; | |
cursor: pointer; | |
transition: all 0.2s; | |
} | |
.payment-app-btn:hover { | |
background: #f9f9f9; | |
transform: translateY(-1px); | |
box-shadow: 0 2px 5px rgba(0,0,0,0.05); | |
} | |
.payment-app-icon { | |
width: 32px; | |
height: 32px; | |
border-radius: 8px; | |
margin-right: 12px; | |
object-fit: cover; | |
} | |
.payment-app-container { | |
max-height: 60vh; | |
overflow-y: auto; | |
padding: 0 10px; | |
margin: 15px 0; | |
} | |
</style> | |
<div style="background: white; border-radius: 12px; width: 90%; max-width: 360px; padding: 20px; box-shadow: 0 4px 15px rgba(0,0,0,0.2);"> | |
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px;"> | |
<h3 style="margin: 0; color: #333;">भुगतान ऐप चुनें</h3> | |
<button onclick="document.getElementById('${modalId}').style.animation = 'fadeOut 0.2s ease-in forwards'; setTimeout(() => document.getElementById('${modalId}').remove(), 200);" | |
style="background: none; border: none; font-size: 22px; color: #6c757d; cursor: pointer; padding: 0 5px;"> | |
× | |
</button> | |
</div> | |
<p style="margin: 0 0 10px 0; color: #666; font-size: 14px;"> | |
${details.name} को भुगतान करने के लिए ऐप का चयन करें | |
${details.amount ? `<br><span style="font-weight: bold; color: #333;">Amount: ₹${details.amount}</span>` : ''} | |
</p> | |
<div class="payment-app-container"> | |
${paymentApps.map(app => ` | |
<button class="payment-app-btn" data-uri="${app.uri}" data-package="${app.package}"> | |
<img src="${app.icon}" alt="${app.name}" class="payment-app-icon"> | |
<span>${app.name}</span> | |
</button> | |
`).join('')} | |
</div> | |
</div> | |
`; | |
document.body.appendChild(modal); | |
// Handle app selection | |
const appButtons = modal.querySelectorAll('.payment-app-btn'); | |
appButtons.forEach(button => { | |
button.addEventListener('click', () => { | |
// Try to open the selected app | |
try { | |
window.location.href = button.dataset.uri; | |
// Close the modal | |
modal.style.animation = 'fadeOut 0.2s ease-in forwards'; | |
setTimeout(() => modal.remove(), 200); | |
// Check after a delay if the app was opened | |
setTimeout(() => { | |
// If we're still here, probably the app isn't installed | |
this.checkAppOpenedOrShowNotification(button.dataset.package); | |
}, 1000); | |
} catch (error) { | |
console.error('Failed to open app:', error); | |
this.showPaymentAppNotification(); | |
modal.remove(); | |
} | |
}); | |
}); | |
} | |
*/ | |
async shareUpiDetails(details) { | |
// बेहतर मोबाइल डिटेक्शन - टचस्क्रीन डिवाइस की जांच | |
const isMobile = navigator.maxTouchPoints > 0; | |
if (!isMobile) { | |
this.showNotification('कृपया UPI भुगतान के लिए मोबाइल डिवाइस का उपयोग करें', 'warning'); | |
return; | |
} | |
// मॉडल बनाना | |
const modal = document.createElement('div'); | |
const modalId = 'payment-app-modal-' + Date.now(); | |
modal.id = modalId; | |
modal.style.cssText = ` | |
position: fixed; | |
top: 0; | |
left: 0; | |
right: 0; | |
bottom: 0; | |
background: rgba(0,0,0,0.5); | |
display: flex; | |
align-items: center; | |
justify-content: center; | |
z-index: 1000; | |
animation: fadeIn 0.2s ease-out; | |
`; | |
// पेमेंट ऐप्स की सूची | |
const paymentApps = [ | |
{ | |
name: 'Google Pay', | |
package: 'com.google.android.apps.nbu.paisa.user', | |
uri: `intent:#Intent;action=android.intent.action.VIEW;scheme=${encodeURIComponent(details.fullUrl)};package=com.google.android.apps.nbu.paisa.user;end`, | |
icon: 'https://play-lh.googleusercontent.com/HArtbyi53u0jnqhnnxkQnMx9dHOERNcprZyKnInd2nrfM7Wd9ivMNTiz7IJP6-mSpwk' | |
}, | |
{ | |
name: 'PhonePe', | |
package: 'com.phonepe.app', | |
uri: `phonepe://${details.fullUrl.replace('upi://', '')}`, | |
icon: 'https://play-lh.googleusercontent.com/6iyA2zVz5PyyMjK5SIxdUhrb7oh9cYVXJ93q6DZkmx07Er1o90PXYeo6mzL4VC2Gj9s' | |
}, | |
{ | |
name: 'Paytm', | |
package: 'net.one97.paytm', | |
uri: `paytmmp://${details.fullUrl.replace('upi://', '')}`, | |
icon: 'https://play-lh.googleusercontent.com/k7tS5pSdVE_m5KkWKHpF7CSch0GQ4NOTaRpukZeRzqcWFcfRQjGzTqfnTYXiWzDDyes' | |
}, | |
{ | |
name: 'BHIM UPI', | |
package: 'in.org.npci.upiapp', | |
uri: details.fullUrl, | |
icon: 'https://play-lh.googleusercontent.com/B5cNBA15IxjCT-8UTXEWgiPcGkJ1C07iHKwm2Hbs8xR3PnJvZ0swTag3abdD7XAJ7Q' | |
}, | |
{ | |
name: 'Amazon Pay', | |
package: 'in.amazon.mShop.android.shopping', | |
uri: `amazonpay://upi/pay?${details.fullUrl.split('?')[1]}`, | |
icon: 'https://play-lh.googleusercontent.com/5pwUy5lTMgaG2KPOYnbLUBbKoJNF1xR9zrSJceMb_Gm5LKJ6KCmTIEgKdYhwQj3c8ww' | |
}, | |
{ | |
name: 'Airtel Thanks', | |
package: 'com.myairtelapp', | |
uri: details.fullUrl, | |
icon: 'https://play-lh.googleusercontent.com/KiJy82oKG_1Z2Hl-zbqT-HSjJxYvnHCxJgBQe8fGCt2vJv7Scsjxc3hm8-02wp03w-M' | |
}, | |
{ | |
name: 'Samsung Pay', | |
package: 'com.samsung.android.samsungpay.gear', | |
uri: details.fullUrl, | |
icon: 'https://play-lh.googleusercontent.com/1-ue76xmyBgJpVc1RkWLgYfKO-iEfW4iq4MQxyFkOO1WnO-GoxBK_1W6aaXMS0MzUg' | |
}, | |
{ | |
name: 'Cred', | |
package: 'com.dreamplug.androidapp', | |
uri: details.fullUrl, | |
icon: 'https://play-lh.googleusercontent.com/Dnlvff46UTr7YFMbAzBGjdoOqCRjQumZGRI6la8BPFZ9Kz3bTaYwLHGZZPvvE-p1lw' | |
}, | |
{ | |
name: 'iMobile Pay', | |
package: 'com.csam.icici.bank.imobile', | |
uri: details.fullUrl, | |
icon: 'https://play-lh.googleusercontent.com/DRs5_PfaJbjvLvHcXkdAli1qSGEspFT3OAV0SN_Rw9IKQOGQWFtZ0WQVnKC5-QtcCw' | |
} | |
]; | |
// मॉडल HTML बनाना | |
modal.innerHTML = ` | |
<style> | |
@keyframes fadeIn { | |
from { opacity: 0; } | |
to { opacity: 1; } | |
} | |
@keyframes fadeOut { | |
from { opacity: 1; } | |
to { opacity: 0; } | |
} | |
.payment-app-btn { | |
display: flex; | |
align-items: center; | |
padding: 12px; | |
margin: 5px 0; | |
border: 1px solid #eee; | |
border-radius: 10px; | |
background: white; | |
width: 100%; | |
cursor: pointer; | |
transition: all 0.2s; | |
} | |
.payment-app-btn:hover { | |
background: #f9f9f9; | |
transform: translateY(-1px); | |
box-shadow: 0 2px 5px rgba(0,0,0,0.05); | |
} | |
.payment-app-icon { | |
width: 32px; | |
height: 32px; | |
border-radius: 8px; | |
margin-right: 12px; | |
object-fit: cover; | |
} | |
.payment-app-container { | |
max-height: 60vh; | |
overflow-y: auto; | |
padding: 0 10px; | |
margin: 15px 0; | |
} | |
</style> | |
<div style="background: white; border-radius: 12px; width: 90%; max-width: 360px; padding: 20px; box-shadow: 0 4px 15px rgba(0,0,0,0.2);"> | |
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px;"> | |
<h3 style="margin: 0; color: #333;">भुगतान ऐप चुनें</h3> | |
<button id="close-modal-${modalId}" | |
style="background: none; border: none; font-size: 22px; color: #6c757d; cursor: pointer; padding: 0 5px;"> | |
× | |
</button> | |
</div> | |
<p style="margin: 0 0 10px 0; color: #666; font-size: 14px;"> | |
${details.name} को भुगतान करने के लिए ऐप का चयन करें | |
${details.amount ? `<br><span style="font-weight: bold; color: #333;">Amount: ₹${details.amount}</span>` : ''} | |
</p> | |
<div class="payment-app-container"> | |
${paymentApps.map(app => ` | |
<button class="payment-app-btn" data-uri="${app.uri}" data-package="${app.package}" data-name="${app.name}"> | |
<img src="${app.icon}" alt="${app.name}" class="payment-app-icon"> | |
<span>${app.name}</span> | |
</button> | |
`).join('')} | |
</div> | |
</div> | |
`; | |
document.body.appendChild(modal); | |
// मॉडल को बंद करने वाले बटन का इवेंट लिसनर | |
document.getElementById(`close-modal-${modalId}`).addEventListener('click', () => { | |
modal.style.animation = 'fadeOut 0.2s ease-in forwards'; | |
setTimeout(() => modal.remove(), 200); | |
}); | |
// ऐप सिलेक्शन हैंडलिंग - यहां महत्वपूर्ण बदलाव हैं | |
const appButtons = modal.querySelectorAll('.payment-app-btn'); | |
appButtons.forEach(button => { | |
button.addEventListener('click', () => { | |
const appUri = button.dataset.uri; | |
const appPackage = button.dataset.package; | |
const appName = button.dataset.name; | |
// पहले मॉडल को बंद करें, फिर ऐप खोलने का प्रयास करें | |
modal.style.animation = 'fadeOut 0.2s ease-in forwards'; | |
setTimeout(() => { | |
modal.remove(); | |
this.openPaymentApp(appUri, appPackage, appName); | |
}, 200); | |
}); | |
}); | |
} | |
// नया मेथड: ऐप को खोलने और डिटेक्ट करने के लिए | |
openPaymentApp(appUri, appPackage, appName) { | |
try { | |
// चेक करने के लिए एक फ्लैग | |
let appOpenedChecked = false; | |
// विज़िबिलिटी चेंज इवेंट हैंडलर - यह देखने के लिए कि क्या ऐप खुला है | |
const visibilityHandler = () => { | |
if (document.visibilityState === 'hidden') { | |
// पेज अब बैकग्राउंड में है, इसका मतलब है ऐप संभवतः खुला है | |
appOpenedChecked = true; | |
// हैंडलर को हटा दें, हमें फिर से चेक नहीं करना है | |
document.removeEventListener('visibilitychange', visibilityHandler); | |
debug(`${appName} सफलतापूर्वक खुला लगता है`); | |
} | |
}; | |
// इवेंट लिसनर जोड़ें | |
document.addEventListener('visibilitychange', visibilityHandler); | |
// ऐप खोलने का प्रयास करें | |
debug(`${appName} खोलने का प्रयास कर रहे हैं...`); | |
window.location.href = appUri; | |
// एक टाइमआउट सेट करें जो तभी काम करेगा जब ऐप नहीं खुला हो | |
setTimeout(() => { | |
// हैंडलर को हटा दें, अब इसकी ज़रूरत नहीं है | |
document.removeEventListener('visibilitychange', visibilityHandler); | |
// केवल तभी चेक करें जब visibilitychange इवेंट ट्रिगर नहीं हुआ हो | |
if (!appOpenedChecked && document.visibilityState === 'visible') { | |
debug(`${appName} नहीं खुला, इंस्टॉल नहीं है`); | |
// अब हम जानते हैं कि ऐप इंस्टॉल नहीं है | |
this.showSpecificAppInstallNotification(appPackage, appName); | |
} | |
}, 2500); // 2.5 सेकंड प्रतीक्षा करें - ऐप स्टार्ट होने के लिए पर्याप्त समय | |
} catch (error) { | |
console.error(`Failed to open ${appName}:`, error); | |
this.showPaymentAppNotification(); | |
} | |
} | |
// विशिष्ट ऐप के लिए इंस्टॉल अधिसूचना | |
showSpecificAppInstallNotification(packageName, appName) { | |
// ऐप की जानकारी प्राप्त करें | |
const appInfo = { | |
'com.google.android.apps.nbu.paisa.user': { | |
name: 'Google Pay', | |
icon: 'https://play-lh.googleusercontent.com/HArtbyi53u0jnqhnnxkQnMx9dHOERNcprZyKnInd2nrfM7Wd9ivMNTiz7IJP6-mSpwk', | |
url: 'https://play.google.com/store/apps/details?id=com.google.android.apps.nbu.paisa.user' | |
}, | |
'com.phonepe.app': { | |
name: 'PhonePe', | |
icon: 'https://play-lh.googleusercontent.com/6iyA2zVz5PyyMjK5SIxdUhrb7oh9cYVXJ93q6DZkmx07Er1o90PXYeo6mzL4VC2Gj9s', | |
url: 'https://play.google.com/store/apps/details?id=com.phonepe.app' | |
}, | |
'net.one97.paytm': { | |
name: 'Paytm', | |
icon: 'https://play-lh.googleusercontent.com/k7tS5pSdVE_m5KkWKHpF7CSch0GQ4NOTaRpukZeRzqcWFcfRQjGzTqfnTYXiWzDDyes', | |
url: 'https://play.google.com/store/apps/details?id=net.one97.paytm' | |
}, | |
'in.org.npci.upiapp': { | |
name: 'BHIM UPI', | |
icon: 'https://play-lh.googleusercontent.com/B5cNBA15IxjCT-8UTXEWgiPcGkJ1C07iHKwm2Hbs8xR3PnJvZ0swTag3abdD7XAJ7Q', | |
url: 'https://play.google.com/store/apps/details?id=in.org.npci.upiapp' | |
}, | |
'in.amazon.mShop.android.shopping': { | |
name: 'Amazon Pay', | |
icon: 'https://play-lh.googleusercontent.com/5pwUy5lTMgaG2KPOYnbLUBbKoJNF1xR9zrSJceMb_Gm5LKJ6KCmTIEgKdYhwQj3c8ww', | |
url: 'https://play.google.com/store/apps/details?id=in.amazon.mShop.android.shopping' | |
}, | |
'com.myairtelapp': { | |
name: 'Airtel Thanks', | |
icon: 'https://play-lh.googleusercontent.com/KiJy82oKG_1Z2Hl-zbqT-HSjJxYvnHCxJgBQe8fGCt2vJv7Scsjxc3hm8-02wp03w-M', | |
url: 'https://play.google.com/store/apps/details?id=com.myairtelapp' | |
}, | |
'com.samsung.android.samsungpay.gear': { | |
name: 'Samsung Pay', | |
icon: 'https://play-lh.googleusercontent.com/1-ue76xmyBgJpVc1RkWLgYfKO-iEfW4iq4MQxyFkOO1WnO-GoxBK_1W6aaXMS0MzUg', | |
url: 'https://play.google.com/store/apps/details?id=com.samsung.android.samsungpay.gear' | |
}, | |
'com.dreamplug.androidapp': { | |
name: 'Cred', | |
icon: 'https://play-lh.googleusercontent.com/Dnlvff46UTr7YFMbAzBGjdoOqCRjQumZGRI6la8BPFz9Kz3bTaYwLHGZZPvvE-p1lw', | |
url: 'https://play.google.com/store/apps/details?id=com.dreamplug.androidapp' | |
}, | |
'com.csam.icici.bank.imobile': { | |
name: 'iMobile Pay', | |
icon: 'https://play-lh.googleusercontent.com/DRs5_PfaJbjvLvHcXkdAli1qSGEspFT3OAV0SN_Rw9IKQOGQWFtZ0WQVnKC5-QtcCw', | |
url: 'https://play.google.com/store/apps/details?id=com.csam.icici.bank.imobile' | |
} | |
}; | |
// इस विशिष्ट ऐप के लिए नोटिफिकेशन दिखाएं | |
const app = appInfo[packageName] || { | |
name: appName || 'UPI Payment App', | |
icon: 'https://play-lh.googleusercontent.com/HArtbyi53u0jnqhnnxkQnMx9dHOERNcprZyKnInd2nrfM7Wd9ivMNTiz7IJP6-mSpwk', | |
url: `https://play.google.com/store/search?q=${encodeURIComponent(appName || 'upi payment app')}&c=apps` | |
}; | |
this.showAppInstallNotification(app); | |
} | |
checkAppOpenedOrShowNotification(packageName) { | |
// This is a best-effort attempt to determine if the app was opened | |
// We'll check if our page is still visible | |
if (document.visibilityState === 'visible') { | |
// The app probably didn't open, show notification with this specific app | |
const appInfo = { | |
'com.google.android.apps.nbu.paisa.user': { | |
name: 'Google Pay', | |
icon: 'https://play-lh.googleusercontent.com/HArtbyi53u0jnqhnnxkQnMx9dHOERNcprZyKnInd2nrfM7Wd9ivMNTiz7IJP6-mSpwk', | |
url: 'https://play.google.com/store/apps/details?id=com.google.android.apps.nbu.paisa.user' | |
}, | |
'com.phonepe.app': { | |
name: 'PhonePe', | |
icon: 'https://play-lh.googleusercontent.com/6iyA2zVz5PyyMjK5SIxdUhrb7oh9cYVXJ93q6DZkmx07Er1o90PXYeo6mzL4VC2Gj9s', | |
url: 'https://play.google.com/store/apps/details?id=com.phonepe.app' | |
}, | |
'net.one97.paytm': { | |
name: 'Paytm', | |
icon: 'https://play-lh.googleusercontent.com/k7tS5pSdVE_m5KkWKHpF7CSch0GQ4NOTaRpukZeRzqcWFcfRQjGzTqfnTYXiWzDDyes', | |
url: 'https://play.google.com/store/apps/details?id=net.one97.paytm' | |
}, | |
'in.org.npci.upiapp': { | |
name: 'BHIM UPI', | |
icon: 'https://play-lh.googleusercontent.com/B5cNBA15IxjCT-8UTXEWgiPcGkJ1C07iHKwm2Hbs8xR3PnJvZ0swTag3abdD7XAJ7Q', | |
url: 'https://play.google.com/store/apps/details?id=in.org.npci.upiapp' | |
}, | |
'in.amazon.mShop.android.shopping': { | |
name: 'Amazon Pay', | |
icon: 'https://play-lh.googleusercontent.com/5pwUy5lTMgaG2KPOYnbLUBbKoJNF1xR9zrSJceMb_Gm5LKJ6KCmTIEgKdYhwQj3c8ww', | |
url: 'https://play.google.com/store/apps/details?id=in.amazon.mShop.android.shopping' | |
}, | |
'com.myairtelapp': { | |
name: 'Airtel Thanks', | |
icon: 'https://play-lh.googleusercontent.com/KiJy82oKG_1Z2Hl-zbqT-HSjJxYvnHCxJgBQe8fGCt2vJv7Scsjxc3hm8-02wp03w-M', | |
url: 'https://play.google.com/store/apps/details?id=com.myairtelapp' | |
}, | |
'com.samsung.android.samsungpay.gear': { | |
name: 'Samsung Pay', | |
icon: 'https://play-lh.googleusercontent.com/1-ue76xmyBgJpVc1RkWLgYfKO-iEfW4iq4MQxyFkOO1WnO-GoxBK_1W6aaXMS0MzUg', | |
url: 'https://play.google.com/store/apps/details?id=com.samsung.android.samsungpay.gear' | |
}, | |
'com.dreamplug.androidapp': { | |
name: 'Cred', | |
icon: 'https://play-lh.googleusercontent.com/Dnlvff46UTr7YFMbAzBGjdoOqCRjQumZGRI6la8BPFz9Kz3bTaYwLHGZZPvvE-p1lw', | |
url: 'https://play.google.com/store/apps/details?id=com.dreamplug.androidapp' | |
}, | |
'com.csam.icici.bank.imobile': { | |
name: 'iMobile Pay', | |
icon: 'https://play-lh.googleusercontent.com/DRs5_PfaJbjvLvHcXkdAli1qSGEspFT3OAV0SN_Rw9IKQOGQWFtZ0WQVnKC5-QtcCw', | |
url: 'https://play.google.com/store/apps/details?id=com.csam.icici.bank.imobile' | |
} | |
}; | |
// Show the notification for this specific app | |
const app = appInfo[packageName] || { | |
name: 'UPI Payment App', | |
icon: 'https://play-lh.googleusercontent.com/HArtbyi53u0jnqhnnxkQnMx9dHOERNcprZyKnInd2nrfM7Wd9ivMNTiz7IJP6-mSpwk', | |
url: 'https://play.google.com/store/search?q=upi%20payment%20app&c=apps' | |
}; | |
this.showAppInstallNotification(app); | |
} | |
} | |
showAppInstallNotification(app) { | |
const notification = document.createElement('div'); | |
notification.className = 'payment-app-notification'; | |
// Add a unique ID to track this notification | |
const notificationId = 'payment-notification-' + Date.now(); | |
notification.id = notificationId; | |
notification.style.cssText = ` | |
position: fixed; | |
bottom: 20px; | |
left: 50%; | |
transform: translateX(-50%); | |
background: white; | |
padding: 20px; | |
border-radius: 12px; | |
box-shadow: 0 4px 12px rgba(0,0,0,0.15); | |
width: 90%; | |
max-width: 360px; | |
z-index: 1000; | |
animation: slideUpNotification 0.3s ease-out; | |
`; | |
// Add notification content with improved styling | |
notification.innerHTML = ` | |
<style> | |
@keyframes slideUpNotification { | |
from { transform: translate(-50%, 100%); opacity: 0; } | |
to { transform: translate(-50%, 0); opacity: 1; } | |
} | |
@keyframes slideDownNotification { | |
from { transform: translate(-50%, 0); opacity: 1; } | |
to { transform: translate(-50%, 100%); opacity: 0; } | |
} | |
.app-store-link { | |
display: flex; | |
align-items: center; | |
padding: 12px 15px; | |
margin-top: 15px; | |
background: #f0f7ff; | |
border: 1px solid #cce5ff; | |
border-radius: 8px; | |
text-decoration: none; | |
color: #0056b3; | |
font-size: 14px; | |
transition: all 0.2s ease; | |
justify-content: center; | |
} | |
.app-store-link:hover { | |
background: #e2f0ff; | |
transform: translateY(-1px); | |
} | |
.app-icon-large { | |
width: 60px; | |
height: 60px; | |
border-radius: 12px; | |
margin-bottom: 10px; | |
box-shadow: 0 2px 6px rgba(0,0,0,0.1); | |
} | |
</style> | |
<div style="display: flex; justify-content: space-between; align-items: start; margin-bottom: 10px;"> | |
<h4 style="margin: 0; color: #dc3545; font-size: 16px;"> | |
${app.name} ऐप नहीं मिला! | |
</h4> | |
<button onclick="document.getElementById('${notificationId}').remove()" | |
style="background: none; border: none; font-size: 20px; color: #6c757d; cursor: pointer; padding: 0 5px;"> | |
× | |
</button> | |
</div> | |
<div style="display: flex; flex-direction: column; align-items: center; text-align: center; margin: 15px 0;"> | |
<img src="${app.icon}" class="app-icon-large" alt="${app.name}"> | |
<p style="margin: 10px 0; color: #6c757d; font-size: 14px;"> | |
UPI भुगतान करने के लिए आपके फ़ोन पर ${app.name} ऐप इंस्टॉल नहीं है। | |
</p> | |
</div> | |
<a href="${app.url}" | |
target="_blank" | |
class="app-store-link"> | |
<span>${app.name} इंस्टॉल करें</span> | |
</a> | |
<p style="margin: 15px 0 0 0; font-size: 12px; color: #6c757d; text-align: center;"> | |
ऐप इंस्टॉल करने के बाद वापस आकर पेमेंट करें। | |
</p> | |
`; | |
document.body.appendChild(notification); | |
// Improved auto-remove with existence check | |
const removeTimeout = setTimeout(() => { | |
const notificationElement = document.getElementById(notificationId); | |
if (notificationElement && document.body.contains(notificationElement)) { | |
notificationElement.style.animation = 'slideDownNotification 0.3s ease-in forwards'; | |
setTimeout(() => { | |
if (document.body.contains(notificationElement)) { | |
notificationElement.remove(); | |
} | |
}, 300); | |
} | |
}, 10000); | |
// Clean up timeout if notification is manually closed | |
notification.addEventListener('remove', () => { | |
clearTimeout(removeTimeout); | |
}); | |
} | |
showPaymentAppNotification() { | |
const notification = document.createElement('div'); | |
notification.className = 'payment-app-notification'; | |
// Add a unique ID to track this notification | |
const notificationId = 'payment-notification-' + Date.now(); | |
notification.id = notificationId; | |
notification.style.cssText = ` | |
position: fixed; | |
bottom: 20px; | |
left: 50%; | |
transform: translateX(-50%); | |
background: white; | |
padding: 20px; | |
border-radius: 12px; | |
box-shadow: 0 4px 12px rgba(0,0,0,0.15); | |
width: 90%; | |
max-width: 360px; | |
z-index: 1000; | |
animation: slideUpNotification 0.3s ease-out; | |
`; | |
// Add notification content with improved styling | |
notification.innerHTML = ` | |
<style> | |
@keyframes slideUpNotification { | |
from { transform: translate(-50%, 100%); opacity: 0; } | |
to { transform: translate(-50%, 0); opacity: 1; } | |
} | |
@keyframes slideDownNotification { | |
from { transform: translate(-50%, 0); opacity: 1; } | |
to { transform: translate(-50%, 100%); opacity: 0; } | |
} | |
.app-store-link { | |
display: inline-flex; | |
align-items: center; | |
padding: 10px 15px; | |
margin: 5px 0; | |
background: #f8f9fa; | |
border: 1px solid #dee2e6; | |
border-radius: 8px; | |
text-decoration: none; | |
color: #212529; | |
font-size: 14px; | |
transition: all 0.2s ease; | |
width: 100%; | |
} | |
.app-store-link:hover { | |
background: #e9ecef; | |
transform: translateY(-1px); | |
} | |
.app-icon { | |
width: 24px; | |
height: 24px; | |
margin-right: 8px; | |
border-radius: 6px; | |
} | |
.payment-apps-container { | |
display: flex; | |
flex-direction: column; | |
gap: 8px; | |
margin-top: 10px; | |
} | |
</style> | |
<div style="display: flex; justify-content: space-between; align-items: start; margin-bottom: 15px;"> | |
<h4 style="margin: 0; color: #dc3545; font-size: 16px;"> | |
कोई UPI पेमेंट ऐप नहीं मिला! | |
</h4> | |
<button onclick="document.getElementById('${notificationId}').remove()" | |
style="background: none; border: none; font-size: 20px; color: #6c757d; cursor: pointer; padding: 0 5px;"> | |
× | |
</button> | |
</div> | |
<p style="margin: 0 0 15px 0; color: #6c757d; font-size: 14px;"> | |
पेमेंट करने के लिए इनमें से कोई एक UPI ऐप इंस्टॉल करें: | |
</p> | |
<div class="payment-apps-container"> | |
<a href="https://play.google.com/store/apps/details?id=com.google.android.apps.nbu.paisa.user" | |
target="_blank" | |
class="app-store-link"> | |
<img src="https://play-lh.googleusercontent.com/HArtbyi53u0jnqhnnxkQnMx9dHOERNcprZyKnInd2nrfM7Wd9ivMNTiz7IJP6-mSpwk" | |
class="app-icon" | |
alt="Google Pay"> | |
Google Pay | |
</a> | |
<a href="https://play.google.com/store/apps/details?id=com.phonepe.app" | |
target="_blank" | |
class="app-store-link"> | |
<img src="https://play-lh.googleusercontent.com/6iyA2zVz5PyyMjK5SIxdUhrb7oh9cYVXJ93q6DZkmx07Er1o90PXYeo6mzL4VC2Gj9s" | |
class="app-icon" | |
alt="PhonePe"> | |
PhonePe | |
</a> | |
<a href="https://play.google.com/store/apps/details?id=net.one97.paytm" | |
target="_blank" | |
class="app-store-link"> | |
<img src="https://play-lh.googleusercontent.com/k7tS5pSdVE_m5KkWKHpF7CSch0GQ4NOTaRpukZeRzqcWFcfRQjGzTqfnTYXiWzDDyes" | |
class="app-icon" | |
alt="Paytm"> | |
Paytm | |
</a> | |
<a href="https://play.google.com/store/apps/details?id=in.org.npci.upiapp" | |
target="_blank" | |
class="app-store-link"> | |
<img src="https://play-lh.googleusercontent.com/B5cNBA15IxjCT-8UTXEWgiPcGkJ1C07iHKwm2Hbs8xR3PnJvZ0swTag3abdD7XAJ7Q" | |
class="app-icon" | |
alt="BHIM UPI"> | |
BHIM UPI | |
</a> | |
<a href="https://play.google.com/store/apps/details?id=com.myairtelapp" | |
target="_blank" | |
class="app-store-link"> | |
<img src="https://play-lh.googleusercontent.com/KiJy82oKG_1Z2Hl-zbqT-HSjJxYvnHCxJgBQe8fGCt2vJv7Scsjxc3hm8-02wp03w-M" | |
class="app-icon" | |
alt="Airtel Thanks"> | |
Airtel Thanks | |
</a> | |
</div> | |
<p style="margin: 15px 0 0 0; font-size: 12px; color: #6c757d; text-align: center;"> | |
ऐप इंस्टॉल करने के बाद वापस आकर पेमेंट करें। | |
</p> | |
`; | |
document.body.appendChild(notification); | |
// Improved auto-remove with existence check | |
const removeTimeout = setTimeout(() => { | |
const notificationElement = document.getElementById(notificationId); | |
if (notificationElement && document.body.contains(notificationElement)) { | |
notificationElement.style.animation = 'slideDownNotification 0.3s ease-in forwards'; | |
setTimeout(() => { | |
if (document.body.contains(notificationElement)) { | |
notificationElement.remove(); | |
} | |
}, 300); | |
} | |
}, 10000); | |
// Clean up timeout if notification is manually closed | |
notification.addEventListener('remove', () => { | |
clearTimeout(removeTimeout); | |
}); | |
} | |
// | |
showUpiDetails(details) { | |
const detailsModal = document.createElement('div'); | |
const modalId = 'upi-details-' + Date.now(); | |
detailsModal.id = modalId; | |
detailsModal.style.cssText = ` | |
position: fixed; | |
top: 0; | |
left: 0; | |
right: 0; | |
bottom: 0; | |
background: rgba(0,0,0,0.5); | |
display: flex; | |
align-items: center; | |
justify-content: center; | |
z-index: 1000; | |
animation: fadeIn 0.2s ease-out; | |
`; | |
detailsModal.innerHTML = ` | |
<style> | |
@keyframes fadeIn { | |
from { opacity: 0; } | |
to { opacity: 1; } | |
} | |
@keyframes fadeOut { | |
from { opacity: 1; } | |
to { opacity: 0; } | |
} | |
.detail-row { | |
display: flex; | |
justify-content: space-between; | |
padding: 10px 0; | |
border-bottom: 1px solid #eee; | |
} | |
.detail-row:last-child { | |
border-bottom: none; | |
} | |
.detail-label { | |
font-weight: bold; | |
color: #666; | |
} | |
.detail-value { | |
color: #333; | |
word-break: break-all; | |
} | |
</style> | |
<div style="background: white; border-radius: 12px; width: 90%; max-width: 360px; padding: 20px; box-shadow: 0 4px 15px rgba(0,0,0,0.2);"> | |
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 15px;"> | |
<h3 style="margin: 0; color: #333;">UPI Payment Details</h3> | |
<button onclick="document.getElementById('${modalId}').style.animation = 'fadeOut 0.2s ease-in forwards'; setTimeout(() => document.getElementById('${modalId}').remove(), 200);" | |
style="background: none; border: none; font-size: 22px; color: #6c757d; cursor: pointer; padding: 0 5px;"> | |
× | |
</button> | |
</div> | |
<div class="detail-row"> | |
<div class="detail-label">Name:</div> | |
<div class="detail-value">${details.name || 'Not specified'}</div> | |
</div> | |
<div class="detail-row"> | |
<div class="detail-label">UPI ID:</div> | |
<div class="detail-value">${details.vpa || 'Not available'}</div> | |
</div> | |
${details.amount ? ` | |
<div class="detail-row"> | |
<div class="detail-label">Amount:</div> | |
<div class="detail-value">₹${details.amount}</div> | |
</div> | |
` : ''} | |
<div style="margin-top: 20px; display: flex; justify-content: flex-end; gap: 10px;"> | |
<button onclick="document.getElementById('${modalId}').style.animation = 'fadeOut 0.2s ease-in forwards'; setTimeout(() => document.getElementById('${modalId}').remove(), 200);" | |
style="background: #f8f9fa; border: 1px solid #dee2e6; padding: 8px 15px; border-radius: 6px; cursor: pointer;"> | |
Close | |
</button> | |
<button onclick="this.closest('#${modalId}').remove(); document.dispatchEvent(new CustomEvent('pay-upi', { detail: ${JSON.stringify(details)} }));" | |
style="background: #007bff; color: white; border: none; padding: 8px 15px; border-radius: 6px; cursor: pointer;"> | |
Pay Now | |
</button> | |
</div> | |
</div> | |
`; | |
document.body.appendChild(detailsModal); | |
// Handle the pay event | |
document.addEventListener('pay-upi', (event) => { | |
if (event.detail && event.detail.fullUrl) { | |
this.shareUpiDetails(event.detail); | |
} | |
}, { once: true }); | |
} | |
async copyToClipboard(text) { | |
try { | |
await navigator.clipboard.writeText(text); | |
this.showNotification('Copied to clipboard!', 'success'); | |
} catch (error) { | |
console.error('Failed to copy:', error); | |
this.showNotification('Failed to copy to clipboard', 'error'); | |
// Fallback method for older browsers | |
try { | |
const textArea = document.createElement('textarea'); | |
textArea.value = text; | |
textArea.style.position = 'fixed'; | |
textArea.style.left = '-9999px'; | |
document.body.appendChild(textArea); | |
textArea.select(); | |
document.execCommand('copy'); | |
document.body.removeChild(textArea); | |
this.showNotification('Copied to clipboard!', 'success'); | |
} catch (fallbackError) { | |
this.showNotification('Failed to copy to clipboard', 'error'); | |
} | |
} | |
} | |
playSuccessSound() { | |
try { | |
const audio = new Audio('data:audio/mp3;base64,SUQzBAAAAAAAI1RTU0UAAAAPAAADTGF2ZjU4Ljc2LjEwMAAAAAAAAAAAAAAA//tQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWGluZwAAAA8AAAACAAADQABERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERE//////////////////////////////////////////////////////////////////8AAAAATGF2YzU4LjEzAAAAAAAAAAAAAAAAJAYAAAAAAAAAA0CM3qxWAAAAAAD/+1DEAAAAA0gAAAAATKwQAAAAAnRFVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVQQEBAQAAAAAAAAAA'); | |
audio.play(); | |
} catch (error) { | |
console.warn('Could not play sound:', error); | |
} | |
} | |
// | |
updateHistoryDisplay() { | |
const historyContainer = document.getElementById('scan-history'); | |
if (!historyContainer) return; | |
const fragment = document.createDocumentFragment(); | |
if (this.history.length === 0) { | |
const emptyMessage = document.createElement('p'); | |
emptyMessage.className = 'empty-history'; | |
emptyMessage.textContent = 'No scan history yet'; | |
fragment.appendChild(emptyMessage); | |
} else { | |
this.history.forEach(item => { | |
const historyItem = document.createElement('div'); | |
historyItem.className = 'history-item'; | |
historyItem.innerHTML = ` | |
<div class="type-badge">${item.type}</div> | |
<div class="content">${item.text}</div> | |
<div class="timestamp"> | |
<i class="fas fa-clock"></i> | |
${new Date(item.timestamp).toLocaleString()} | |
</div> | |
`; | |
fragment.appendChild(historyItem); | |
}); | |
} | |
historyContainer.innerHTML = ''; | |
historyContainer.appendChild(fragment); | |
} | |
async handleFileUpload(file) { | |
if (!file) return; | |
if (!CONFIG.ALLOWED_FILE_TYPES.includes(file.type)) { | |
this.showNotification('Invalid file type. Please upload an image.', 'error'); | |
return; | |
} | |
if (file.size > CONFIG.SCANNER.MAX_FILE_SIZE) { | |
this.showNotification('File too large. Maximum size is 5MB.', 'error'); | |
return; | |
} | |
try { | |
if (!this.html5QrCode) { | |
await this.initializeScanner(); | |
} | |
const decodedText = await this.html5QrCode.scanFile(file, true); | |
const contentType = this.detectContentType(decodedText); | |
this.addToHistory(decodedText, contentType); | |
this.displayResult(decodedText, contentType); | |
this.playSuccessSound(); | |
} catch (error) { | |
this.showNotification('Failed to scan QR code from image', 'error'); | |
} | |
} | |
} | |
// Initialize the scanner when the page loads | |
document.addEventListener('DOMContentLoaded', () => { | |
const scanner = new QRScannerState(); | |
// Set up event listeners | |
const elements = { | |
startButton: document.getElementById('startButton'), | |
fileButton: document.getElementById('fileButton'), | |
fileInput: document.getElementById('fileInput'), | |
flashLightButton: document.getElementById('flashLightButton'), | |
clearHistoryButton: document.getElementById('clearHistory') | |
}; | |
if (elements.startButton) { | |
elements.startButton.addEventListener('click', async () => { | |
if (scanner.scanning) { | |
await scanner.stopScanning(); | |
} else { | |
await scanner.startScanning(); | |
} | |
}); | |
} | |
if (elements.fileButton && elements.fileInput) { | |
elements.fileButton.addEventListener('click', () => elements.fileInput.click()); | |
elements.fileInput.addEventListener('change', (e) => scanner.handleFileUpload(e.target.files[0])); | |
} | |
if (elements.flashLightButton) { | |
elements.flashLightButton.addEventListener('click', () => scanner.toggleFlash()); | |
} | |
if (elements.clearHistoryButton) { | |
elements.clearHistoryButton.addEventListener('click', () => scanner.clearHistory()); | |
} | |
// Initialize history display | |
scanner.updateHistoryDisplay(); | |
// Clean up on page unload | |
window.addEventListener('beforeunload', () => scanner.stopScanning()); | |
}); | |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
:root { | |
--primary-color: #000000; | |
--secondary-color: #2563eb; | |
--background-color: #f5f6fa; | |
--surface-color: #ffffff; | |
--text-color: #000000; | |
--border-radius: 12px; | |
--error-color: #dc2626; | |
--success-color: #16a34a; | |
} | |
body { | |
font-family: 'Inter', sans-serif; | |
background-color: var(--background-color); | |
margin: 0; | |
padding: 20px; | |
color: var(--text-color); | |
line-height: 1.6; | |
} | |
.container { | |
max-width: 1000px; | |
margin: 0 auto; | |
background: var(--surface-color); | |
padding: 25px; | |
border-radius: var(--border-radius); | |
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); | |
} | |
header { | |
text-align: center; | |
margin-bottom: 30px; | |
} | |
h1 { | |
font-size: 28px; | |
font-weight: 600; | |
color: var(--text-color); | |
margin: 0; | |
} | |
h1 i { | |
margin-right: 10px; | |
color: var(--secondary-color); | |
} | |
.button-container { | |
display: flex; | |
justify-content: center; | |
gap: 15px; | |
margin-bottom: 20px; | |
} | |
.action-button { | |
display: flex; | |
align-items: center; | |
gap: 8px; | |
background-color: var(--primary-color); | |
color: white; | |
border: none; | |
padding: 12px 20px; | |
border-radius: 8px; | |
cursor: pointer; | |
font-size: 15px; | |
font-weight: 500; | |
transition: all 0.2s ease; | |
} | |
.action-button:hover { | |
background-color: var(--secondary-color); | |
transform: translateY(-2px); | |
} | |
.action-buttons-container { | |
display: flex; | |
flex-wrap: wrap; | |
gap: 10px; | |
justify-content: center; | |
margin-top: 15px; | |
} | |
.action-button.active { | |
background-color: var(--secondary-color); | |
} | |
#flashLightButton i { | |
margin-right: 5px; | |
} | |
.smart-action-button { | |
display: inline-flex; | |
align-items: center; | |
gap: 6px; | |
padding: 8px 16px; | |
border-radius: 6px; | |
border: none; | |
cursor: pointer; | |
font-size: 14px; | |
font-weight: 500; | |
color: white; | |
background-color: var(--secondary-color); | |
transition: all 0.2s ease; | |
} | |
.smart-action-button:hover { | |
opacity: 0.9; | |
transform: translateY(-1px); | |
} | |
.smart-action-button i { | |
font-size: 14px; | |
} | |
.reader-container { | |
max-width: 100%; | |
margin: 20px auto; | |
border-radius: var(--border-radius); | |
overflow: hidden; | |
border: 2px solid #eee; | |
} | |
.results-container { | |
margin: 20px auto; | |
padding: 15px; | |
background-color: #f8f9fa; | |
border-radius: var(--border-radius); | |
text-align: center; | |
} | |
#result-content { | |
margin-bottom: 15px; | |
word-break: break-all; | |
} | |
.history-section { | |
margin-top: 30px; | |
} | |
.history-header { | |
display: flex; | |
justify-content: space-between; | |
align-items: center; | |
margin-bottom: 15px; | |
} | |
.history-header h2 { | |
font-size: 20px; | |
margin: 0; | |
display: flex; | |
align-items: center; | |
gap: 8px; | |
color: var(--text-color); | |
} | |
.clear-button { | |
background: none; | |
border: none; | |
color: var(--error-color); | |
cursor: pointer; | |
padding: 8px; | |
border-radius: 50%; | |
transition: background-color 0.2s; | |
} | |
.clear-button:hover { | |
background-color: #fee2e2; | |
} | |
.history-item { | |
padding: 15px; | |
margin: 10px 0; | |
background-color: #f8f9fa; | |
border-radius: 8px; | |
border-left: 3px solid var(--secondary-color); | |
animation: fadeIn 0.3s ease-out; | |
} | |
.history-item .content { | |
margin-bottom: 8px; | |
} | |
.history-item .type-badge { | |
display: inline-block; | |
padding: 2px 8px; | |
border-radius: 4px; | |
font-size: 12px; | |
margin-right: 8px; | |
background-color: var(--secondary-color); | |
color: white; | |
} | |
.history-item .timestamp { | |
font-size: 12px; | |
color: #666; | |
display: flex; | |
align-items: center; | |
gap: 4px; | |
} | |
.notification { | |
position: fixed; | |
bottom: 70px; | |
right: 20px; | |
padding: 12px 20px; | |
border-radius: 8px; | |
background-color: var(--success-color); | |
font-size: 15px; | |
font-family: sans-serif; | |
color: white; | |
opacity: 0; | |
transform: translateY(10px); | |
transition: all 0.3s ease; | |
z-index: 1000; | |
} | |
.notification.show { | |
opacity: 1; | |
transform: translateY(0); | |
} | |
@keyframes fadeIn { | |
from { opacity: 0; transform: translateY(-10px); } | |
to { opacity: 1; transform: translateY(0); } | |
} | |
:root { | |
/* लाइट थीम वेरिएबल्स */ | |
--primary-color: #000000; | |
--secondary-color: #2563eb; | |
--background-color: #f5f6fa; | |
--surface-color: #ffffff; | |
--text-color: #000000; | |
/* अन्य वेरिएबल्स... */ | |
} | |
body.theme-dark { | |
/* डार्क थीम वेरिएबल्स */ | |
--primary-color: #3b82f6; | |
--secondary-color: #60a5fa; | |
--background-color: #121212; | |
--surface-color: #1e1e1e; | |
--text-color: #f5f5f5; | |
--border-radius: 12px; | |
--error-color: #ef4444; | |
--success-color: #22c55e; | |
} | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment