2157 lines
80 KiB
JavaScript
2157 lines
80 KiB
JavaScript
// QR Rapido Speed Generator
|
||
class QRRapidoGenerator {
|
||
constructor() {
|
||
this.startTime = 0;
|
||
this.currentQR = null;
|
||
this.timerInterval = null;
|
||
this.languageStrings = {
|
||
'pt-BR': {
|
||
tagline: 'Gere QR codes em segundos!',
|
||
generating: 'Gerando...',
|
||
generated: 'Gerado em',
|
||
seconds: 's',
|
||
ultraFast: 'Geração ultra rápida!',
|
||
fast: 'Geração rápida!',
|
||
normal: 'Geração normal',
|
||
error: 'Erro na geração. Tente novamente.',
|
||
success: 'QR Code salvo no histórico!'
|
||
},
|
||
'es': {
|
||
tagline: '¡Genera códigos QR en segundos!',
|
||
generating: 'Generando...',
|
||
generated: 'Generado en',
|
||
seconds: 's',
|
||
ultraFast: '¡Generación ultra rápida!',
|
||
fast: '¡Generación rápida!',
|
||
normal: 'Generación normal',
|
||
error: 'Error en la generación. Inténtalo de nuevo.',
|
||
success: '¡Código QR guardado en el historial!'
|
||
},
|
||
'en': {
|
||
tagline: 'Generate QR codes in seconds!',
|
||
generating: 'Generating...',
|
||
generated: 'Generated in',
|
||
seconds: 's',
|
||
ultraFast: 'Ultra fast generation!',
|
||
fast: 'Fast generation!',
|
||
normal: 'Normal generation',
|
||
error: 'Generation error. Please try again.',
|
||
success: 'QR Code saved to history!'
|
||
}
|
||
};
|
||
this.currentLang = localStorage.getItem('qrrapido-lang') || 'pt-BR';
|
||
|
||
this.selectedType = null;
|
||
this.selectedStyle = 'classic'; // Estilo padrão
|
||
this.contentValid = false;
|
||
|
||
this.initializeEvents();
|
||
this.checkAdFreeStatus();
|
||
this.updateLanguage();
|
||
this.updateStatsCounters();
|
||
this.initializeProgressiveFlow();
|
||
|
||
// Validar segurança dos dados após carregamento
|
||
setTimeout(() => {
|
||
this.validateDataSecurity();
|
||
this.testUTF8Encoding();
|
||
}, 1000);
|
||
}
|
||
|
||
initializeEvents() {
|
||
// Form submission with timer
|
||
const form = document.getElementById('qr-speed-form');
|
||
if (form) {
|
||
form.addEventListener('submit', this.generateQRWithTimer.bind(this));
|
||
}
|
||
|
||
// Quick style selection
|
||
document.querySelectorAll('input[name="quick-style"]').forEach(radio => {
|
||
radio.addEventListener('change', this.applyQuickStyle.bind(this));
|
||
});
|
||
|
||
// Logo upload feedback
|
||
const logoUpload = document.getElementById('logo-upload');
|
||
if (logoUpload) {
|
||
logoUpload.addEventListener('change', this.handleLogoSelection.bind(this));
|
||
}
|
||
|
||
// Corner style validation for non-premium users
|
||
const cornerStyle = document.getElementById('corner-style');
|
||
if (cornerStyle) {
|
||
cornerStyle.addEventListener('change', this.handleCornerStyleChange.bind(this));
|
||
}
|
||
|
||
// QR type change with hints and flow
|
||
const qrType = document.getElementById('qr-type');
|
||
if (qrType) {
|
||
qrType.addEventListener('change', (e) => {
|
||
this.handleTypeSelection(e.target.value);
|
||
this.updateContentHints();
|
||
});
|
||
}
|
||
|
||
// Content validation
|
||
const qrContent = document.getElementById('qr-content');
|
||
if (qrContent) {
|
||
qrContent.addEventListener('input', (e) => {
|
||
this.handleContentChange(e.target.value);
|
||
});
|
||
}
|
||
|
||
// Style selection
|
||
document.querySelectorAll('input[name="quick-style"]').forEach(radio => {
|
||
radio.addEventListener('change', (e) => {
|
||
this.handleStyleSelection(e.target.value);
|
||
});
|
||
});
|
||
|
||
// Add listeners to all relevant fields to update button state
|
||
const fieldsToWatch = [
|
||
'qr-content', 'vcard-name', 'vcard-mobile', 'vcard-email',
|
||
'wifi-ssid', 'wifi-password', 'sms-number', 'sms-message',
|
||
'email-to', 'email-subject', 'email-body'
|
||
];
|
||
fieldsToWatch.forEach(id => {
|
||
const el = document.getElementById(id);
|
||
if (el) {
|
||
el.addEventListener('input', () => this.updateGenerateButton());
|
||
}
|
||
});
|
||
document.querySelectorAll('input[name="wifi-security"]').forEach(radio => {
|
||
radio.addEventListener('change', () => this.updateGenerateButton());
|
||
});
|
||
|
||
// Language selector
|
||
document.querySelectorAll('[data-lang]').forEach(link => {
|
||
link.addEventListener('click', this.changeLanguage.bind(this));
|
||
});
|
||
|
||
// Real-time preview for premium users
|
||
if (this.isPremiumUser()) {
|
||
this.setupRealTimePreview();
|
||
}
|
||
|
||
// Download buttons
|
||
this.setupDownloadButtons();
|
||
|
||
// Share functionality
|
||
this.setupShareButtons();
|
||
|
||
// Save to history
|
||
const saveBtn = document.getElementById('save-to-history');
|
||
if (saveBtn) {
|
||
saveBtn.addEventListener('click', this.saveToHistory.bind(this));
|
||
}
|
||
}
|
||
|
||
setupDownloadButtons() {
|
||
const pngBtn = document.getElementById('download-png');
|
||
const svgBtn = document.getElementById('download-svg');
|
||
const pdfBtn = document.getElementById('download-pdf');
|
||
|
||
if (pngBtn) pngBtn.addEventListener('click', () => this.downloadQR('png'));
|
||
if (svgBtn) svgBtn.addEventListener('click', () => this.downloadQR('svg'));
|
||
if (pdfBtn) pdfBtn.addEventListener('click', () => this.downloadQR('pdf'));
|
||
}
|
||
|
||
setupShareButtons() {
|
||
// Check if Web Share API is supported and show/hide native share option
|
||
if (navigator.share && this.isMobileDevice()) {
|
||
const nativeShareOption = document.getElementById('native-share-option');
|
||
if (nativeShareOption) {
|
||
nativeShareOption.classList.remove('d-none');
|
||
}
|
||
}
|
||
|
||
// Show save to gallery option on mobile
|
||
if (this.isMobileDevice()) {
|
||
const saveGalleryOption = document.getElementById('save-gallery-option');
|
||
if (saveGalleryOption) {
|
||
saveGalleryOption.classList.remove('d-none');
|
||
}
|
||
}
|
||
|
||
// Add event listeners to share buttons
|
||
const shareButtons = {
|
||
'native-share': () => this.shareNative(),
|
||
'share-whatsapp': () => this.shareWhatsApp(),
|
||
'share-telegram': () => this.shareTelegram(),
|
||
'share-email': () => this.shareEmail(),
|
||
'copy-qr-link': () => this.copyToClipboard(),
|
||
'save-to-gallery': () => this.saveToGallery()
|
||
};
|
||
|
||
Object.entries(shareButtons).forEach(([id, handler]) => {
|
||
const button = document.getElementById(id);
|
||
if (button) {
|
||
button.addEventListener('click', (e) => {
|
||
e.preventDefault();
|
||
handler();
|
||
});
|
||
}
|
||
});
|
||
}
|
||
|
||
isMobileDevice() {
|
||
return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent) ||
|
||
(navigator.maxTouchPoints && navigator.maxTouchPoints > 2 && /MacIntel/.test(navigator.platform));
|
||
}
|
||
|
||
async shareNative() {
|
||
if (!this.currentQR || !navigator.share) return;
|
||
|
||
try {
|
||
// Create a blob from the base64 image
|
||
const base64Response = await fetch(`data:image/png;base64,${this.currentQR.base64}`);
|
||
const blob = await base64Response.blob();
|
||
const file = new File([blob], 'qrcode.png', { type: 'image/png' });
|
||
|
||
const shareData = {
|
||
title: 'QR Code - QR Rapido',
|
||
text: 'QR Code gerado com QR Rapido - o gerador mais rápido do Brasil!',
|
||
url: window.location.origin,
|
||
files: [file]
|
||
};
|
||
|
||
// Check if files can be shared
|
||
if (navigator.canShare && navigator.canShare(shareData)) {
|
||
await navigator.share(shareData);
|
||
} else {
|
||
// Fallback without files
|
||
await navigator.share({
|
||
title: shareData.title,
|
||
text: shareData.text,
|
||
url: shareData.url
|
||
});
|
||
}
|
||
|
||
this.trackShareEvent('native');
|
||
} catch (error) {
|
||
console.error('Error sharing:', error);
|
||
if (error.name !== 'AbortError') {
|
||
this.showError('Erro ao compartilhar. Tente outro método.');
|
||
}
|
||
}
|
||
}
|
||
|
||
shareWhatsApp() {
|
||
if (!this.currentQR) return;
|
||
|
||
const text = encodeURIComponent('QR Code gerado com QR Rapido - o gerador mais rápido do Brasil! ' + window.location.origin);
|
||
const url = `https://wa.me/?text=${text}`;
|
||
|
||
if (this.isMobileDevice()) {
|
||
window.open(url, '_blank');
|
||
} else {
|
||
window.open(`https://web.whatsapp.com/send?text=${text}`, '_blank');
|
||
}
|
||
|
||
this.trackShareEvent('whatsapp');
|
||
}
|
||
|
||
shareTelegram() {
|
||
if (!this.currentQR) return;
|
||
|
||
const text = encodeURIComponent('QR Code gerado com QR Rapido - o gerador mais rápido do Brasil!');
|
||
const url = encodeURIComponent(window.location.origin);
|
||
const telegramUrl = `https://t.me/share/url?url=${url}&text=${text}`;
|
||
|
||
window.open(telegramUrl, '_blank');
|
||
this.trackShareEvent('telegram');
|
||
}
|
||
|
||
shareEmail() {
|
||
if (!this.currentQR) return;
|
||
|
||
const subject = encodeURIComponent('QR Code - QR Rapido');
|
||
const body = encodeURIComponent(`Olá!\n\nCompartilho com você este QR Code gerado no QR Rapido, o gerador mais rápido do Brasil!\n\nAcesse: ${window.location.origin}\n\nAbraços!`);
|
||
const mailtoUrl = `mailto:?subject=${subject}&body=${body}`;
|
||
|
||
window.location.href = mailtoUrl;
|
||
this.trackShareEvent('email');
|
||
}
|
||
|
||
async copyToClipboard() {
|
||
if (!this.currentQR) return;
|
||
|
||
try {
|
||
const shareText = `QR Code gerado com QR Rapido - ${window.location.origin}`;
|
||
|
||
if (navigator.clipboard && window.isSecureContext) {
|
||
await navigator.clipboard.writeText(shareText);
|
||
} else {
|
||
// Fallback for older browsers
|
||
const textArea = document.createElement('textarea');
|
||
textArea.value = shareText;
|
||
textArea.style.position = 'fixed';
|
||
textArea.style.left = '-999999px';
|
||
textArea.style.top = '-999999px';
|
||
document.body.appendChild(textArea);
|
||
textArea.focus();
|
||
textArea.select();
|
||
document.execCommand('copy');
|
||
textArea.remove();
|
||
}
|
||
|
||
this.showSuccess('Link copiado para a área de transferência!');
|
||
this.trackShareEvent('copy');
|
||
} catch (error) {
|
||
console.error('Error copying to clipboard:', error);
|
||
this.showError('Erro ao copiar link. Tente novamente.');
|
||
}
|
||
}
|
||
|
||
async saveToGallery() {
|
||
if (!this.currentQR) return;
|
||
|
||
try {
|
||
// Create a blob from the base64 image
|
||
const base64Response = await fetch(`data:image/png;base64,${this.currentQR.base64}`);
|
||
const blob = await base64Response.blob();
|
||
|
||
// Create download link
|
||
const url = window.URL.createObjectURL(blob);
|
||
const a = document.createElement('a');
|
||
a.href = url;
|
||
a.download = `qrrapido-${new Date().toISOString().slice(0,10)}-${Date.now()}.png`;
|
||
document.body.appendChild(a);
|
||
a.click();
|
||
document.body.removeChild(a);
|
||
window.URL.revokeObjectURL(url);
|
||
|
||
this.showSuccess('QR Code baixado! Verifique sua galeria/downloads.');
|
||
this.trackShareEvent('gallery');
|
||
} catch (error) {
|
||
console.error('Error saving to gallery:', error);
|
||
this.showError('Erro ao salvar na galeria. Tente novamente.');
|
||
}
|
||
}
|
||
|
||
trackShareEvent(method) {
|
||
// Google Analytics
|
||
if (typeof gtag !== 'undefined') {
|
||
gtag('event', 'qr_shared', {
|
||
'share_method': method,
|
||
'user_type': this.isPremiumUser() ? 'premium' : 'free',
|
||
'language': this.currentLang
|
||
});
|
||
}
|
||
|
||
// Internal tracking
|
||
console.log(`QR Code shared via ${method}`);
|
||
}
|
||
|
||
async generateQRWithTimer(e) {
|
||
e.preventDefault();
|
||
|
||
// Validation
|
||
if (!this.validateForm()) return;
|
||
|
||
// Start timer
|
||
this.startTime = performance.now();
|
||
this.showGenerationStarted();
|
||
|
||
const requestData = this.collectFormData();
|
||
|
||
try {
|
||
// Build fetch options based on request type
|
||
const fetchOptions = {
|
||
method: 'POST',
|
||
body: requestData.isMultipart ? requestData.data : JSON.stringify(requestData.data)
|
||
};
|
||
|
||
// Add Content-Type header only for JSON requests (FormData sets its own)
|
||
if (!requestData.isMultipart) {
|
||
fetchOptions.headers = {
|
||
'Content-Type': 'application/json'
|
||
};
|
||
}
|
||
|
||
const response = await fetch(requestData.endpoint, fetchOptions);
|
||
|
||
if (!response.ok) {
|
||
const errorData = await response.json().catch(() => ({}));
|
||
|
||
if (response.status === 429) {
|
||
this.showUpgradeModal('Limite de QR codes atingido! Upgrade para QR Rapido Premium e gere códigos ilimitados.');
|
||
return;
|
||
}
|
||
|
||
if (response.status === 400 && errorData.requiresPremium) {
|
||
this.showUpgradeModal(errorData.error || 'Logo personalizado é exclusivo do plano Premium.');
|
||
return;
|
||
}
|
||
|
||
throw new Error(errorData.error || 'Erro na geração');
|
||
}
|
||
|
||
const result = await response.json();
|
||
|
||
if (!result.success) {
|
||
if (result.requiresPremium) {
|
||
this.showUpgradeModal(result.error || 'Logo personalizado é exclusivo do plano Premium.');
|
||
return;
|
||
}
|
||
throw new Error(result.error || 'Erro desconhecido');
|
||
}
|
||
|
||
const generationTime = ((performance.now() - this.startTime) / 1000).toFixed(1);
|
||
|
||
this.displayQRResult(result, generationTime);
|
||
this.updateSpeedStats(generationTime);
|
||
this.trackGenerationEvent(requestData.data.type || requestData.data.get('type'), generationTime);
|
||
|
||
} catch (error) {
|
||
console.error('Erro ao gerar QR:', error);
|
||
this.showError(this.languageStrings[this.currentLang].error);
|
||
} finally {
|
||
this.hideGenerationLoading();
|
||
}
|
||
}
|
||
|
||
validateForm() {
|
||
const qrType = document.getElementById('qr-type').value;
|
||
|
||
if (!qrType) {
|
||
this.showError('Selecione o tipo de QR code');
|
||
return false;
|
||
}
|
||
|
||
// Special validation for VCard
|
||
if (qrType === 'vcard') {
|
||
try {
|
||
if (window.vcardGenerator) {
|
||
const errors = window.vcardGenerator.validateVCardData();
|
||
if (errors.length > 0) {
|
||
this.showError(errors.join('<br>'));
|
||
return false;
|
||
}
|
||
return true;
|
||
} else {
|
||
this.showError('VCard generator não está disponível');
|
||
return false;
|
||
}
|
||
} catch (error) {
|
||
this.showError('Erro na validação do VCard: ' + error.message);
|
||
return false;
|
||
}
|
||
} else if (qrType === 'wifi') {
|
||
const errors = window.wifiGenerator.validateWiFiData();
|
||
if (errors.length > 0) {
|
||
this.showError(errors.join('<br>'));
|
||
return false;
|
||
}
|
||
return true;
|
||
} else if (qrType === 'sms') {
|
||
const errors = window.smsGenerator.validateSMSData();
|
||
if (errors.length > 0) {
|
||
this.showError(errors.join('<br>'));
|
||
return false;
|
||
}
|
||
return true;
|
||
} else if (qrType === 'email') {
|
||
const errors = window.emailGenerator.validateEmailData();
|
||
if (errors.length > 0) {
|
||
this.showError(errors.join('<br>'));
|
||
return false;
|
||
}
|
||
return true;
|
||
}
|
||
|
||
// Normal validation for other types
|
||
const qrContent = document.getElementById('qr-content').value.trim();
|
||
|
||
if (!qrContent) {
|
||
this.showError('Digite o conteúdo do QR code');
|
||
return false;
|
||
}
|
||
|
||
if (qrContent.length > 4000) {
|
||
this.showError('Conteúdo muito longo. Máximo 4000 caracteres.');
|
||
return false;
|
||
}
|
||
|
||
return true;
|
||
}
|
||
|
||
collectFormData() {
|
||
const type = document.getElementById('qr-type').value;
|
||
const quickStyle = document.querySelector('input[name="quick-style"]:checked')?.value || 'classic';
|
||
const styleSettings = this.getStyleSettings(quickStyle);
|
||
|
||
if (type === 'wifi') {
|
||
const wifiContent = window.wifiGenerator.generateWiFiString();
|
||
return {
|
||
data: {
|
||
type: 'text', // WiFi is treated as text in the backend
|
||
content: wifiContent,
|
||
quickStyle: quickStyle,
|
||
primaryColor: document.getElementById('primary-color').value || (styleSettings.primaryColor || '#000000'),
|
||
backgroundColor: document.getElementById('bg-color').value || (styleSettings.backgroundColor || '#FFFFFF'),
|
||
size: parseInt(document.getElementById('qr-size').value),
|
||
margin: parseInt(document.getElementById('qr-margin').value),
|
||
cornerStyle: document.getElementById('corner-style')?.value || 'square',
|
||
optimizeForSpeed: true,
|
||
language: this.currentLang
|
||
},
|
||
isMultipart: false,
|
||
endpoint: '/api/QR/GenerateRapid'
|
||
};
|
||
} else if (type === 'sms') {
|
||
const smsContent = window.smsGenerator.generateSMSString();
|
||
return {
|
||
data: {
|
||
type: 'text',
|
||
content: smsContent,
|
||
quickStyle: quickStyle,
|
||
primaryColor: document.getElementById('primary-color').value || (styleSettings.primaryColor || '#000000'),
|
||
backgroundColor: document.getElementById('bg-color').value || (styleSettings.backgroundColor || '#FFFFFF'),
|
||
size: parseInt(document.getElementById('qr-size').value),
|
||
margin: parseInt(document.getElementById('qr-margin').value),
|
||
cornerStyle: document.getElementById('corner-style')?.value || 'square',
|
||
optimizeForSpeed: true,
|
||
language: this.currentLang
|
||
},
|
||
isMultipart: false,
|
||
endpoint: '/api/QR/GenerateRapid'
|
||
};
|
||
} else if (type === 'email') {
|
||
const emailContent = window.emailGenerator.generateEmailString();
|
||
return {
|
||
data: {
|
||
type: 'text',
|
||
content: emailContent,
|
||
quickStyle: quickStyle,
|
||
primaryColor: document.getElementById('primary-color').value || (styleSettings.primaryColor || '#000000'),
|
||
backgroundColor: document.getElementById('bg-color').value || (styleSettings.backgroundColor || '#FFFFFF'),
|
||
size: parseInt(document.getElementById('qr-size').value),
|
||
margin: parseInt(document.getElementById('qr-margin').value),
|
||
cornerStyle: document.getElementById('corner-style')?.value || 'square',
|
||
optimizeForSpeed: true,
|
||
language: this.currentLang
|
||
},
|
||
isMultipart: false,
|
||
endpoint: '/api/QR/GenerateRapid'
|
||
};
|
||
}
|
||
|
||
// Handle VCard type
|
||
if (type === 'vcard') {
|
||
if (window.vcardGenerator) {
|
||
const vcardContent = window.vcardGenerator.getVCardContent();
|
||
const encodedContent = this.prepareContentForQR(vcardContent, 'vcard');
|
||
return {
|
||
data: {
|
||
type: 'vcard', // Keep as vcard type for tracking
|
||
content: encodedContent,
|
||
quickStyle: quickStyle,
|
||
primaryColor: document.getElementById('primary-color').value || (styleSettings.primaryColor || '#000000'),
|
||
backgroundColor: document.getElementById('bg-color').value || (styleSettings.backgroundColor || '#FFFFFF'),
|
||
size: parseInt(document.getElementById('qr-size').value),
|
||
margin: parseInt(document.getElementById('qr-margin').value),
|
||
cornerStyle: document.getElementById('corner-style')?.value || 'square',
|
||
optimizeForSpeed: true,
|
||
language: this.currentLang
|
||
},
|
||
isMultipart: false,
|
||
endpoint: '/api/QR/GenerateRapid'
|
||
};
|
||
} else {
|
||
throw new Error('VCard generator não está disponível');
|
||
}
|
||
}
|
||
|
||
// Check if logo is selected for premium users
|
||
const logoUpload = document.getElementById('logo-upload');
|
||
const hasLogo = logoUpload && logoUpload.files && logoUpload.files[0];
|
||
|
||
if (hasLogo) {
|
||
// Use FormData for premium users with logo
|
||
const formData = new FormData();
|
||
|
||
// Get user-selected colors with proper priority
|
||
const userPrimaryColor = document.getElementById('primary-color').value;
|
||
const userBackgroundColor = document.getElementById('bg-color').value;
|
||
|
||
// Priority: User selection > Style defaults > Fallback
|
||
// Always use user selection if it exists, regardless of what color it is
|
||
const finalPrimaryColor = userPrimaryColor || (styleSettings.primaryColor || '#000000');
|
||
const finalBackgroundColor = userBackgroundColor || (styleSettings.backgroundColor || '#FFFFFF');
|
||
|
||
// Debug logging for color selection
|
||
console.log('🎨 Color Selection Debug (FormData):');
|
||
console.log(' Style:', quickStyle);
|
||
console.log(' Style Default Primary:', styleSettings.primaryColor);
|
||
console.log(' User Selected Primary:', userPrimaryColor);
|
||
console.log(' Final Primary Color:', finalPrimaryColor);
|
||
console.log(' Final Background Color:', finalBackgroundColor);
|
||
|
||
// Add basic form fields with UTF-8 encoding
|
||
const rawContent = document.getElementById('qr-content').value;
|
||
const encodedContent = this.prepareContentForQR(rawContent, type);
|
||
|
||
formData.append('type', document.getElementById('qr-type').value);
|
||
formData.append('content', encodedContent);
|
||
formData.append('quickStyle', quickStyle);
|
||
formData.append('primaryColor', finalPrimaryColor);
|
||
formData.append('backgroundColor', finalBackgroundColor);
|
||
formData.append('size', parseInt(document.getElementById('qr-size').value));
|
||
formData.append('margin', parseInt(document.getElementById('qr-margin').value));
|
||
formData.append('cornerStyle', document.getElementById('corner-style')?.value || 'square');
|
||
formData.append('optimizeForSpeed', 'true');
|
||
formData.append('language', this.currentLang);
|
||
|
||
// Add logo file
|
||
formData.append('logo', logoUpload.files[0]);
|
||
console.log('Logo file added to form data:', logoUpload.files[0].name, logoUpload.files[0].size + ' bytes');
|
||
|
||
return { data: formData, isMultipart: true, endpoint: '/api/QR/GenerateRapidWithLogo' };
|
||
} else {
|
||
// Use JSON for basic QR generation (original working method)
|
||
// Get user-selected colors
|
||
const userPrimaryColor = document.getElementById('primary-color').value;
|
||
const userBackgroundColor = document.getElementById('bg-color').value;
|
||
|
||
// Priority: User selection > Style defaults > Fallback
|
||
// Always use user selection if it exists, regardless of what color it is
|
||
const finalPrimaryColor = userPrimaryColor || (styleSettings.primaryColor || '#000000');
|
||
const finalBackgroundColor = userBackgroundColor || (styleSettings.backgroundColor || '#FFFFFF');
|
||
|
||
// Debug logging for color selection
|
||
console.log('🎨 Color Selection Debug (JSON):');
|
||
console.log(' Style:', quickStyle);
|
||
console.log(' Style Default Primary:', styleSettings.primaryColor);
|
||
console.log(' User Selected Primary:', userPrimaryColor);
|
||
console.log(' Final Primary Color:', finalPrimaryColor);
|
||
console.log(' Final Background Color:', finalBackgroundColor);
|
||
|
||
const rawContent = document.getElementById('qr-content').value;
|
||
const encodedContent = this.prepareContentForQR(rawContent, type);
|
||
|
||
return {
|
||
data: {
|
||
type: document.getElementById('qr-type').value,
|
||
content: encodedContent,
|
||
quickStyle: quickStyle,
|
||
primaryColor: finalPrimaryColor,
|
||
backgroundColor: finalBackgroundColor,
|
||
size: parseInt(document.getElementById('qr-size').value),
|
||
margin: parseInt(document.getElementById('qr-margin').value),
|
||
cornerStyle: document.getElementById('corner-style')?.value || 'square',
|
||
optimizeForSpeed: true,
|
||
language: this.currentLang
|
||
},
|
||
isMultipart: false,
|
||
endpoint: '/api/QR/GenerateRapid'
|
||
};
|
||
}
|
||
}
|
||
|
||
getStyleSettings(style) {
|
||
const styles = {
|
||
classic: { primaryColor: '#000000', backgroundColor: '#FFFFFF' },
|
||
modern: { primaryColor: '#007BFF', backgroundColor: '#F8F9FA' },
|
||
colorful: { primaryColor: '#FF6B35', backgroundColor: '#FFF3E0' }
|
||
};
|
||
return styles[style] || styles.classic;
|
||
}
|
||
|
||
displayQRResult(result, generationTime) {
|
||
const previewDiv = document.getElementById('qr-preview');
|
||
if (!previewDiv) return;
|
||
|
||
previewDiv.innerHTML = `
|
||
<img src="data:image/png;base64,${result.qrCodeBase64}"
|
||
class="img-fluid border rounded shadow-sm"
|
||
alt="QR Code gerado em ${generationTime}s">
|
||
`;
|
||
|
||
// Show generation statistics
|
||
this.showGenerationStats(generationTime);
|
||
|
||
// Show download buttons
|
||
const downloadSection = document.getElementById('download-section');
|
||
if (downloadSection) {
|
||
downloadSection.style.display = 'block';
|
||
}
|
||
|
||
// Save current data
|
||
this.currentQR = {
|
||
base64: result.qrCodeBase64,
|
||
id: result.qrId,
|
||
generationTime: generationTime
|
||
};
|
||
|
||
// Update counter for free users
|
||
if (result.remainingQRs !== undefined) {
|
||
this.updateRemainingCounter(result.remainingQRs);
|
||
}
|
||
}
|
||
|
||
showGenerationStarted() {
|
||
const button = document.getElementById('generate-btn');
|
||
const spinner = button?.querySelector('.spinner-border');
|
||
const timer = document.querySelector('.generation-timer');
|
||
|
||
if (button) button.disabled = true;
|
||
if (spinner) spinner.classList.remove('d-none');
|
||
if (timer) timer.classList.remove('d-none');
|
||
|
||
// Update timer in real time
|
||
this.timerInterval = setInterval(() => {
|
||
const elapsed = ((performance.now() - this.startTime) / 1000).toFixed(1);
|
||
const timerSpan = timer?.querySelector('span');
|
||
if (timerSpan) {
|
||
timerSpan.textContent = `${elapsed}s`;
|
||
}
|
||
}, 100);
|
||
|
||
// Preview loading
|
||
const preview = document.getElementById('qr-preview');
|
||
if (preview) {
|
||
preview.innerHTML = `
|
||
<div class="text-center p-4">
|
||
<div class="spinner-border text-primary mb-3" role="status"></div>
|
||
<p class="text-muted">${this.languageStrings[this.currentLang].generating}</p>
|
||
<div class="progress">
|
||
<div class="progress-bar progress-bar-striped progress-bar-animated"
|
||
style="width: 100%"></div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
}
|
||
}
|
||
|
||
showGenerationStats(generationTime) {
|
||
const statsDiv = document.querySelector('.generation-stats');
|
||
const speedBadge = document.querySelector('.speed-badge');
|
||
|
||
if (statsDiv) {
|
||
statsDiv.classList.remove('d-none');
|
||
const timeSpan = statsDiv.querySelector('.generation-time');
|
||
if (timeSpan) {
|
||
timeSpan.textContent = `${generationTime}s`;
|
||
}
|
||
}
|
||
|
||
// Show speed badge
|
||
if (speedBadge) {
|
||
const strings = this.languageStrings[this.currentLang];
|
||
let badgeText = strings.normal;
|
||
let badgeClass = 'bg-secondary';
|
||
|
||
if (generationTime < 1.0) {
|
||
badgeText = strings.ultraFast;
|
||
badgeClass = 'bg-success';
|
||
} else if (generationTime < 2.0) {
|
||
badgeText = strings.fast;
|
||
badgeClass = 'bg-primary';
|
||
}
|
||
|
||
speedBadge.innerHTML = `
|
||
<span class="badge ${badgeClass}">
|
||
<i class="fas fa-bolt"></i> ${badgeText}
|
||
</span>
|
||
`;
|
||
speedBadge.classList.remove('d-none');
|
||
}
|
||
}
|
||
|
||
hideGenerationLoading() {
|
||
const button = document.getElementById('generate-btn');
|
||
const spinner = button?.querySelector('.spinner-border');
|
||
|
||
if (button) button.disabled = false;
|
||
if (spinner) spinner.classList.add('d-none');
|
||
|
||
if (this.timerInterval) {
|
||
clearInterval(this.timerInterval);
|
||
this.timerInterval = null;
|
||
}
|
||
}
|
||
|
||
updateContentHints() {
|
||
const type = document.getElementById('qr-type')?.value;
|
||
const hintsElement = document.getElementById('content-hints');
|
||
const vcardInterface = document.getElementById('vcard-interface');
|
||
const contentTextarea = document.getElementById('qr-content');
|
||
|
||
if (!hintsElement || !type) return;
|
||
|
||
// Show/hide VCard interface based on type
|
||
if (type === 'vcard') {
|
||
if (vcardInterface) vcardInterface.style.display = 'block';
|
||
if (contentTextarea) {
|
||
contentTextarea.style.display = 'none';
|
||
contentTextarea.removeAttribute('required');
|
||
}
|
||
hintsElement.textContent = 'Preencha os campos acima para criar seu cartão de visita digital';
|
||
return; // Skip normal hints for VCard
|
||
} else {
|
||
if (vcardInterface) vcardInterface.style.display = 'none';
|
||
if (contentTextarea) {
|
||
contentTextarea.style.display = 'block';
|
||
contentTextarea.setAttribute('required', 'required');
|
||
}
|
||
}
|
||
|
||
const hints = {
|
||
'pt-BR': {
|
||
'url': 'Ex: https://www.exemplo.com.br',
|
||
'text': 'Digite qualquer texto que desejar',
|
||
'wifi': 'Nome da rede;Senha;Tipo de segurança (WPA/WEP)',
|
||
'vcard': 'Nome;Telefone;Email;Empresa',
|
||
'sms': 'Número;Mensagem',
|
||
'email': 'email@exemplo.com;Assunto;Mensagem'
|
||
},
|
||
'es': {
|
||
'url': 'Ej: https://www.ejemplo.com',
|
||
'text': 'Escribe cualquier texto que desees',
|
||
'wifi': 'Nombre de red;Contraseña;Tipo de seguridad (WPA/WEP)',
|
||
'vcard': 'Nombre;Teléfono;Email;Empresa',
|
||
'sms': 'Número;Mensaje',
|
||
'email': 'email@ejemplo.com;Asunto;Mensagem'
|
||
}
|
||
};
|
||
|
||
const langHints = hints[this.currentLang] || hints['pt-BR'];
|
||
hintsElement.textContent = langHints[type] || 'Digite o conteúdo apropriado para o tipo selecionado';
|
||
}
|
||
|
||
changeLanguage(e) {
|
||
e.preventDefault();
|
||
this.currentLang = e.target.dataset.lang;
|
||
this.updateLanguage();
|
||
this.updateContentHints();
|
||
|
||
// Save preference
|
||
localStorage.setItem('qrrapido-lang', this.currentLang);
|
||
|
||
// Track language change
|
||
window.trackLanguageChange && window.trackLanguageChange('pt-BR', this.currentLang);
|
||
}
|
||
|
||
updateLanguage() {
|
||
const strings = this.languageStrings[this.currentLang];
|
||
|
||
// Update tagline
|
||
const tagline = document.getElementById('tagline');
|
||
if (tagline) {
|
||
tagline.textContent = strings.tagline;
|
||
}
|
||
|
||
// Update language selector
|
||
const langMap = { 'pt-BR': 'PT', 'es': 'ES', 'en': 'EN' };
|
||
const currentLang = document.getElementById('current-lang');
|
||
if (currentLang) {
|
||
currentLang.textContent = langMap[this.currentLang];
|
||
}
|
||
|
||
// Update hints if type already selected
|
||
const qrType = document.getElementById('qr-type');
|
||
if (qrType?.value) {
|
||
this.updateContentHints();
|
||
}
|
||
}
|
||
|
||
handleLogoSelection(e) {
|
||
const file = e.target.files[0];
|
||
const logoPreview = document.getElementById('logo-preview');
|
||
const logoFilename = document.getElementById('logo-filename');
|
||
|
||
if (file) {
|
||
// Validate file size (2MB max)
|
||
if (file.size > 2 * 1024 * 1024) {
|
||
this.showError('Logo muito grande. Máximo 2MB.');
|
||
e.target.value = ''; // Clear the input
|
||
logoPreview?.classList.add('d-none');
|
||
return;
|
||
}
|
||
|
||
// Validate file type
|
||
const allowedTypes = ['image/png', 'image/jpeg', 'image/jpg'];
|
||
if (!allowedTypes.includes(file.type)) {
|
||
this.showError('Formato inválido. Use PNG ou JPG.');
|
||
e.target.value = ''; // Clear the input
|
||
logoPreview?.classList.add('d-none');
|
||
return;
|
||
}
|
||
|
||
// Show success feedback
|
||
if (logoFilename) {
|
||
const fileSizeKB = Math.round(file.size / 1024);
|
||
logoFilename.textContent = `${file.name} (${fileSizeKB}KB)`;
|
||
}
|
||
logoPreview?.classList.remove('d-none');
|
||
|
||
console.log('Logo selected:', file.name, file.size + ' bytes', file.type);
|
||
} else {
|
||
// Hide preview when no file selected
|
||
logoPreview?.classList.add('d-none');
|
||
}
|
||
}
|
||
|
||
handleCornerStyleChange(e) {
|
||
const selectedStyle = e.target.value;
|
||
const premiumStyles = ['rounded', 'circle', 'leaf'];
|
||
|
||
if (premiumStyles.includes(selectedStyle)) {
|
||
// Check if user is premium (we can detect this by checking if the option is disabled)
|
||
const option = e.target.options[e.target.selectedIndex];
|
||
if (option.disabled) {
|
||
// Reset to square
|
||
e.target.value = 'square';
|
||
this.showUpgradeModal('Estilos de borda personalizados são exclusivos do plano Premium. Faça upgrade para usar esta funcionalidade.');
|
||
return;
|
||
}
|
||
}
|
||
|
||
console.log('Corner style selected:', selectedStyle);
|
||
}
|
||
|
||
applyQuickStyle(e) {
|
||
const style = e.target.value;
|
||
const settings = this.getStyleSettings(style);
|
||
|
||
const primaryColor = document.getElementById('primary-color');
|
||
const bgColor = document.getElementById('bg-color');
|
||
|
||
if (primaryColor) primaryColor.value = settings.primaryColor;
|
||
if (bgColor) bgColor.value = settings.backgroundColor;
|
||
}
|
||
|
||
updateStatsCounters() {
|
||
// Simulate real-time counters
|
||
setInterval(() => {
|
||
const totalElement = document.getElementById('total-qrs');
|
||
if (totalElement) {
|
||
const current = parseFloat(totalElement.textContent.replace('K', '')) || 10.5;
|
||
const newValue = (current + Math.random() * 0.1).toFixed(1);
|
||
totalElement.textContent = `${newValue}K`;
|
||
}
|
||
|
||
// Update average time based on real performance
|
||
const avgElement = document.getElementById('avg-generation-time');
|
||
if (avgElement && window.qrRapidoStats) {
|
||
const avg = window.qrRapidoStats.getAverageTime();
|
||
avgElement.textContent = `${avg}s`;
|
||
}
|
||
}, 30000); // Update every 30 seconds
|
||
}
|
||
|
||
trackGenerationEvent(type, time) {
|
||
// Google Analytics
|
||
if (typeof gtag !== 'undefined') {
|
||
gtag('event', 'qr_generated', {
|
||
'qr_type': type,
|
||
'generation_time': parseFloat(time),
|
||
'user_type': this.isPremiumUser() ? 'premium' : 'free',
|
||
'language': this.currentLang
|
||
});
|
||
}
|
||
|
||
// Internal statistics
|
||
if (!window.qrRapidoStats) {
|
||
window.qrRapidoStats = {
|
||
times: [],
|
||
getAverageTime: function() {
|
||
if (this.times.length === 0) return '1.2';
|
||
const avg = this.times.reduce((a, b) => a + b) / this.times.length;
|
||
return avg.toFixed(1);
|
||
}
|
||
};
|
||
}
|
||
window.qrRapidoStats.times.push(parseFloat(time));
|
||
}
|
||
|
||
updateSpeedStats(generationTime) {
|
||
// Update the live statistics display
|
||
const timeFloat = parseFloat(generationTime);
|
||
|
||
// Update average time display in the stats cards
|
||
const avgElement = document.querySelector('.card-body h5:contains("1.2s")');
|
||
if (avgElement) {
|
||
const avgTime = window.qrRapidoStats ? window.qrRapidoStats.getAverageTime() : generationTime;
|
||
avgElement.innerHTML = `<i class="fas fa-stopwatch"></i> ${avgTime}s`;
|
||
}
|
||
|
||
// Update the generation timer in the header
|
||
const timerElement = document.querySelector('.generation-timer span');
|
||
if (timerElement) {
|
||
timerElement.textContent = `${generationTime}s`;
|
||
}
|
||
|
||
// Update performance statistics
|
||
if (!window.qrRapidoPerformance) {
|
||
window.qrRapidoPerformance = {
|
||
totalGenerations: 0,
|
||
totalTime: 0,
|
||
bestTime: Infinity,
|
||
worstTime: 0
|
||
};
|
||
}
|
||
|
||
const perf = window.qrRapidoPerformance;
|
||
perf.totalGenerations++;
|
||
perf.totalTime += timeFloat;
|
||
perf.bestTime = Math.min(perf.bestTime, timeFloat);
|
||
perf.worstTime = Math.max(perf.worstTime, timeFloat);
|
||
|
||
// Log performance statistics for debugging
|
||
console.log('📊 Performance Update:', {
|
||
currentTime: `${generationTime}s`,
|
||
averageTime: `${(perf.totalTime / perf.totalGenerations).toFixed(1)}s`,
|
||
bestTime: `${perf.bestTime.toFixed(1)}s`,
|
||
totalGenerations: perf.totalGenerations
|
||
});
|
||
}
|
||
|
||
isPremiumUser() {
|
||
return document.querySelector('.text-success')?.textContent.includes('Premium Ativo') || false;
|
||
}
|
||
|
||
async downloadQR(format) {
|
||
if (!this.currentQR) return;
|
||
|
||
try {
|
||
const response = await fetch(`/api/QR/Download/${this.currentQR.id}?format=${format}`);
|
||
if (!response.ok) throw new Error('Download failed');
|
||
|
||
const blob = await response.blob();
|
||
const url = window.URL.createObjectURL(blob);
|
||
const a = document.createElement('a');
|
||
a.href = url;
|
||
a.download = `qrrapido-${new Date().toISOString().slice(0,10)}.${format}`;
|
||
document.body.appendChild(a);
|
||
a.click();
|
||
document.body.removeChild(a);
|
||
window.URL.revokeObjectURL(url);
|
||
|
||
} catch (error) {
|
||
console.error('Download error:', error);
|
||
this.showError('Erro ao fazer download. Tente novamente.');
|
||
}
|
||
}
|
||
|
||
async saveToHistory() {
|
||
if (!this.currentQR) return;
|
||
|
||
try {
|
||
const response = await fetch('/api/QR/SaveToHistory', {
|
||
method: 'POST',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
},
|
||
body: JSON.stringify({
|
||
qrId: this.currentQR.id
|
||
})
|
||
});
|
||
|
||
if (response.ok) {
|
||
this.showSuccess(this.languageStrings[this.currentLang].success);
|
||
} else {
|
||
throw new Error('Failed to save');
|
||
}
|
||
|
||
} catch (error) {
|
||
console.error('Save error:', error);
|
||
this.showError('Erro ao salvar no histórico.');
|
||
}
|
||
}
|
||
|
||
async checkAdFreeStatus() {
|
||
try {
|
||
const response = await fetch('/Account/AdFreeStatus');
|
||
const status = await response.json();
|
||
|
||
if (status.isAdFree) {
|
||
this.hideAllAds();
|
||
this.showAdFreeMessage(status.timeRemaining);
|
||
}
|
||
|
||
} catch (error) {
|
||
console.error('Error checking ad-free status:', error);
|
||
}
|
||
}
|
||
|
||
hideAllAds() {
|
||
document.querySelectorAll('.ad-container').forEach(ad => {
|
||
ad.style.display = 'none';
|
||
});
|
||
}
|
||
|
||
showAdFreeMessage(timeRemaining) {
|
||
if (timeRemaining <= 0) return;
|
||
|
||
const existing = document.querySelector('.ad-free-notice');
|
||
if (existing) return; // Already shown
|
||
|
||
const message = document.createElement('div');
|
||
message.className = 'alert alert-success text-center mb-3 ad-free-notice';
|
||
message.innerHTML = `
|
||
<i class="fas fa-crown text-warning"></i>
|
||
<strong>Sessão sem anúncios ativa!</strong>
|
||
Tempo restante: <span class="ad-free-countdown">${this.formatTime(timeRemaining)}</span>
|
||
<a href="/Premium/Upgrade" class="btn btn-sm btn-warning ms-2">Tornar Permanente</a>
|
||
`;
|
||
|
||
const container = document.querySelector('.container');
|
||
const row = container?.querySelector('.row');
|
||
if (container && row) {
|
||
container.insertBefore(message, row);
|
||
}
|
||
}
|
||
|
||
formatTime(minutes) {
|
||
if (minutes === 0) return '0m';
|
||
|
||
const days = Math.floor(minutes / 1440);
|
||
const hours = Math.floor((minutes % 1440) / 60);
|
||
const mins = minutes % 60;
|
||
|
||
if (days > 0) return `${days}d ${hours}h ${mins}m`;
|
||
if (hours > 0) return `${hours}h ${mins}m`;
|
||
return `${mins}m`;
|
||
}
|
||
|
||
showUpgradeModal(message) {
|
||
const modal = document.createElement('div');
|
||
modal.className = 'modal fade';
|
||
modal.innerHTML = `
|
||
<div class="modal-dialog">
|
||
<div class="modal-content">
|
||
<div class="modal-header bg-warning">
|
||
<h5 class="modal-title">
|
||
<i class="fas fa-crown"></i> Upgrade para Premium
|
||
</h5>
|
||
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||
</div>
|
||
<div class="modal-body">
|
||
<p>${message}</p>
|
||
<div class="row">
|
||
<div class="col-md-6">
|
||
<h6>Plano Atual (Free)</h6>
|
||
<ul class="list-unstyled">
|
||
<li>❌ Limite de 10 QR/dia</li>
|
||
<li>❌ Anúncios</li>
|
||
<li>✅ QR básicos</li>
|
||
</ul>
|
||
</div>
|
||
<div class="col-md-6">
|
||
<h6>Premium (R$ 19,90/mês)</h6>
|
||
<ul class="list-unstyled">
|
||
<li>✅ QR ilimitados</li>
|
||
<li>✅ Sem anúncios</li>
|
||
<li>✅ QR dinâmicos</li>
|
||
<li>✅ Analytics</li>
|
||
<li>✅ Suporte prioritário</li>
|
||
</ul>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="modal-footer">
|
||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancelar</button>
|
||
<a href="/Premium/Upgrade" class="btn btn-warning">
|
||
<i class="fas fa-crown"></i> Fazer Upgrade
|
||
</a>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
|
||
document.body.appendChild(modal);
|
||
const bsModal = new bootstrap.Modal(modal);
|
||
bsModal.show();
|
||
|
||
// Remove modal when closed
|
||
modal.addEventListener('hidden.bs.modal', () => {
|
||
document.body.removeChild(modal);
|
||
});
|
||
}
|
||
|
||
updateRemainingCounter(remaining) {
|
||
const counterElement = document.querySelector('.qr-counter');
|
||
if (counterElement) {
|
||
counterElement.textContent = `${remaining} QR codes restantes hoje`;
|
||
|
||
if (remaining <= 3) {
|
||
counterElement.className = 'badge bg-warning qr-counter';
|
||
}
|
||
if (remaining === 0) {
|
||
counterElement.className = 'badge bg-danger qr-counter';
|
||
}
|
||
}
|
||
}
|
||
|
||
showError(message) {
|
||
this.showAlert(message, 'danger');
|
||
}
|
||
|
||
showSuccess(message) {
|
||
this.showAlert(message, 'success');
|
||
}
|
||
|
||
showAlert(message, type) {
|
||
const alert = document.createElement('div');
|
||
alert.className = `alert alert-${type} alert-dismissible fade show`;
|
||
alert.innerHTML = `
|
||
<div>${message}</div>
|
||
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
|
||
`;
|
||
|
||
const container = document.querySelector('.container');
|
||
const row = container?.querySelector('.row');
|
||
if (container && row) {
|
||
container.insertBefore(alert, row);
|
||
}
|
||
|
||
// Auto-remove after delay
|
||
setTimeout(() => {
|
||
if (alert.parentNode) {
|
||
alert.parentNode.removeChild(alert);
|
||
}
|
||
}, type === 'success' ? 3000 : 5000);
|
||
}
|
||
|
||
setupRealTimePreview() {
|
||
const contentField = document.getElementById('qr-content');
|
||
const typeField = document.getElementById('qr-type');
|
||
|
||
if (contentField && typeField) {
|
||
let previewTimeout;
|
||
const updatePreview = () => {
|
||
clearTimeout(previewTimeout);
|
||
previewTimeout = setTimeout(() => {
|
||
if (contentField.value.trim() && typeField.value) {
|
||
// Could implement real-time preview for premium users
|
||
console.log('Real-time preview update');
|
||
}
|
||
}, 500);
|
||
};
|
||
|
||
contentField.addEventListener('input', updatePreview);
|
||
typeField.addEventListener('change', updatePreview);
|
||
}
|
||
}
|
||
|
||
// ============================================
|
||
// SISTEMA DE FLUXO ASCENDENTE PROGRESSIVO
|
||
// ============================================
|
||
|
||
initializeProgressiveFlow() {
|
||
// Estado inicial: apenas tipo habilitado, botão gerar desabilitado
|
||
const qrContent = document.getElementById('qr-content');
|
||
const vcardInterface = document.getElementById('vcard-interface');
|
||
|
||
// Inicialmente desabilitar campo de conteúdo
|
||
if (qrContent) {
|
||
qrContent.disabled = true;
|
||
}
|
||
|
||
// Ocultar interface vCard
|
||
if (vcardInterface) {
|
||
vcardInterface.style.display = 'none';
|
||
}
|
||
|
||
this.updateGenerateButton();
|
||
}
|
||
|
||
handleTypeSelection(type) {
|
||
this.selectedType = type;
|
||
|
||
if (type) {
|
||
this.removeInitialHighlight();
|
||
// Sempre habilitar campos de conteúdo após selecionar tipo
|
||
this.enableContentFields(type);
|
||
} else {
|
||
this.disableAllFields();
|
||
}
|
||
|
||
this.updateGenerateButton();
|
||
}
|
||
|
||
handleStyleSelection(style) {
|
||
this.selectedStyle = style;
|
||
this.updateGenerateButton();
|
||
}
|
||
|
||
handleContentChange(content) {
|
||
const contentField = document.getElementById('qr-content');
|
||
this.contentValid = this.validateContent(content);
|
||
|
||
// Feedback visual para campo de conteúdo
|
||
if (contentField) {
|
||
this.validateField(contentField, this.contentValid, 'Conteúdo deve ter pelo menos 3 caracteres');
|
||
}
|
||
|
||
this.updateGenerateButton();
|
||
}
|
||
|
||
enableContentFields(type) {
|
||
const contentGroup = document.getElementById('content-group');
|
||
const vcardInterface = document.getElementById('vcard-interface');
|
||
const wifiInterface = document.getElementById('wifi-interface');
|
||
const smsInterface = document.getElementById('sms-interface');
|
||
const emailInterface = document.getElementById('email-interface');
|
||
|
||
// Hide all interfaces by default
|
||
if (vcardInterface) vcardInterface.style.display = 'none';
|
||
if (wifiInterface) wifiInterface.style.display = 'none';
|
||
if (smsInterface) smsInterface.style.display = 'none';
|
||
if (emailInterface) emailInterface.style.display = 'none';
|
||
if (contentGroup) contentGroup.style.display = 'block';
|
||
|
||
if (type === 'vcard') {
|
||
// Para vCard, ocultar textarea e mostrar interface específica
|
||
if (contentGroup) contentGroup.style.display = 'none';
|
||
if (vcardInterface) {
|
||
vcardInterface.style.display = 'block';
|
||
this.enableVCardFields();
|
||
}
|
||
} else if (type === 'wifi') {
|
||
// Para WiFi, ocultar textarea e mostrar interface específica
|
||
if (contentGroup) contentGroup.style.display = 'none';
|
||
if (wifiInterface) {
|
||
wifiInterface.style.display = 'block';
|
||
}
|
||
} else if (type === 'sms') {
|
||
// Para SMS, ocultar textarea e mostrar interface específica
|
||
if (contentGroup) contentGroup.style.display = 'none';
|
||
if (smsInterface) {
|
||
smsInterface.style.display = 'block';
|
||
}
|
||
} else if (type === 'email') {
|
||
// Para Email, ocultar textarea e mostrar interface específica
|
||
if (contentGroup) contentGroup.style.display = 'none';
|
||
if (emailInterface) {
|
||
emailInterface.style.display = 'block';
|
||
}
|
||
} else {
|
||
// Para outros tipos, mostrar textarea
|
||
if (contentGroup) contentGroup.style.display = 'block';
|
||
const qrContent = document.getElementById('qr-content');
|
||
if(qrContent) {
|
||
qrContent.disabled = false;
|
||
}
|
||
}
|
||
}
|
||
|
||
enableVCardFields() {
|
||
const requiredFields = ['vcard-name', 'vcard-mobile', 'vcard-email'];
|
||
requiredFields.forEach(fieldId => {
|
||
const field = document.getElementById(fieldId);
|
||
if (field) {
|
||
field.disabled = false;
|
||
field.setAttribute('required', 'required');
|
||
}
|
||
});
|
||
}
|
||
|
||
disableAllFields() {
|
||
// Resetar estado
|
||
const qrContent = document.getElementById('qr-content');
|
||
const vcardInterface = document.getElementById('vcard-interface');
|
||
|
||
if (qrContent) {
|
||
qrContent.disabled = true;
|
||
qrContent.value = '';
|
||
}
|
||
|
||
if (vcardInterface) {
|
||
vcardInterface.style.display = 'none';
|
||
// Limpar campos vCard
|
||
const vcardFields = document.querySelectorAll('#vcard-interface input');
|
||
vcardFields.forEach(field => {
|
||
field.value = '';
|
||
field.disabled = true;
|
||
});
|
||
}
|
||
|
||
this.contentValid = false;
|
||
}
|
||
|
||
validateContent(content) {
|
||
if (!content) return false;
|
||
return content.trim().length >= 3;
|
||
}
|
||
|
||
validateVCardFields() {
|
||
const nameField = document.getElementById('vcard-name');
|
||
const mobileField = document.getElementById('vcard-mobile');
|
||
const emailField = document.getElementById('vcard-email');
|
||
|
||
const name = nameField?.value.trim() || '';
|
||
const mobile = mobileField?.value.trim() || '';
|
||
const email = emailField?.value.trim() || '';
|
||
|
||
// Validação individual dos campos com feedback visual
|
||
this.validateField(nameField, name !== '', 'Nome é obrigatório');
|
||
this.validateField(mobileField, mobile !== '' && mobile.length >= 10, 'Telefone deve ter pelo menos 10 dígitos');
|
||
this.validateField(emailField, email !== '' && email.includes('@'), 'Email válido é obrigatório');
|
||
|
||
const isValid = name !== '' && mobile !== '' && email !== '' && email.includes('@') && mobile.length >= 10;
|
||
this.contentValid = isValid;
|
||
|
||
return isValid;
|
||
}
|
||
|
||
validateField(field, isValid, errorMessage) {
|
||
if (!field) return;
|
||
|
||
const feedbackElement = field.parentNode.querySelector('.invalid-feedback');
|
||
|
||
if (isValid) {
|
||
field.classList.remove('is-invalid');
|
||
field.classList.add('is-valid');
|
||
if (feedbackElement) {
|
||
feedbackElement.style.display = 'none';
|
||
}
|
||
} else if (field.value.trim() !== '') {
|
||
// Só mostrar erro se o campo tem conteúdo
|
||
field.classList.remove('is-valid');
|
||
field.classList.add('is-invalid');
|
||
if (feedbackElement) {
|
||
feedbackElement.textContent = errorMessage;
|
||
feedbackElement.style.display = 'block';
|
||
}
|
||
} else {
|
||
// Campo vazio, remover validações visuais
|
||
field.classList.remove('is-valid', 'is-invalid');
|
||
if (feedbackElement) {
|
||
feedbackElement.style.display = 'none';
|
||
}
|
||
}
|
||
}
|
||
|
||
updateGenerateButton() {
|
||
const generateBtn = document.getElementById('generate-btn');
|
||
if (!generateBtn) return;
|
||
|
||
let isValid = false;
|
||
const type = this.selectedType;
|
||
|
||
if (type === 'url' || type === 'text') {
|
||
const content = document.getElementById('qr-content')?.value || '';
|
||
isValid = content.trim().length >= 3;
|
||
} else if (type === 'vcard') {
|
||
isValid = this.validateVCardFields();
|
||
} else if (type === 'wifi') {
|
||
const data = window.wifiGenerator.collectWiFiData();
|
||
isValid = data.ssid.trim() !== '';
|
||
if (data.security !== 'nopass' && !data.password.trim()) {
|
||
isValid = false;
|
||
}
|
||
} else if (type === 'sms') {
|
||
const data = window.smsGenerator.collectSMSData();
|
||
isValid = data.number.trim() !== '' && data.message.trim() !== '';
|
||
} else if (type === 'email') {
|
||
const data = window.emailGenerator.collectEmailData();
|
||
isValid = data.to.trim() !== '' && data.subject.trim() !== '';
|
||
}
|
||
|
||
generateBtn.disabled = !isValid;
|
||
|
||
if (isValid) {
|
||
generateBtn.classList.remove('btn-secondary', 'disabled');
|
||
generateBtn.classList.add('btn-primary');
|
||
} else {
|
||
generateBtn.classList.remove('btn-primary');
|
||
generateBtn.classList.add('btn-secondary', 'disabled');
|
||
}
|
||
}
|
||
|
||
// Remove destaque inicial quando tipo for selecionado
|
||
removeInitialHighlight() {
|
||
const typeField = document.getElementById('qr-type');
|
||
if (typeField && typeField.classList.contains('qr-field-highlight')) {
|
||
typeField.classList.remove('qr-field-highlight');
|
||
}
|
||
}
|
||
|
||
// ============================================
|
||
// FUNÇÕES DE CODIFICAÇÃO UTF-8
|
||
// ============================================
|
||
|
||
// Garantir codificação UTF-8 para todos os tipos de QR Code
|
||
prepareContentForQR(content, type) {
|
||
if (!content) return '';
|
||
|
||
// Garantir que o conteúdo seja tratado como UTF-8
|
||
try {
|
||
// Verificar se há caracteres especiais
|
||
const hasSpecialChars = /[^ |