Skip to content

Instantly share code, notes, and snippets.

@shubham91150
Created May 5, 2025 10:22
Show Gist options
  • Save shubham91150/6746e8906aa4b821cb6493b03d563b90 to your computer and use it in GitHub Desktop.
Save shubham91150/6746e8906aa4b821cb6493b03d563b90 to your computer and use it in GitHub Desktop.
Smart 2
<!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>
/* 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());
});
: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