feat: adsense
This commit is contained in:
parent
e172969996
commit
bcf9f659b4
@ -68,7 +68,7 @@ namespace QRRapidoApp.Middleware
|
||||
"favicon.ico", "robots.txt", "sitemap.xml",
|
||||
"signin-microsoft", "signin-google", "signout-callback-oidc",
|
||||
"Account/ExternalLoginCallback", "Account/Logout", "Pagamento/CreateCheckout",
|
||||
"Pagamento/StripeWebhook"
|
||||
"Pagamento/StripeWebhook", "api/QR"
|
||||
};
|
||||
|
||||
return specialRoutes.Any(route => path.StartsWith(route, StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
@ -681,6 +681,15 @@
|
||||
<data name="TypeGuideText" xml:space="preserve">
|
||||
<value>📝 Para texto libre, ingresa cualquier contenido que desees</value>
|
||||
</data>
|
||||
<data name="ContentAddedToastTitle" xml:space="preserve">
|
||||
<value>✅ ¡Contenido ñemboguapy! (Contenido agregado!)</value>
|
||||
</data>
|
||||
<data name="ContentAddedToastMessage" xml:space="preserve">
|
||||
<value>Opciones avanzadas ojeporukuaa guýpe. Eity "Generar QR Code" emoĩmbyre jave.</value>
|
||||
</data>
|
||||
<data name="GenerateButtonReady" xml:space="preserve">
|
||||
<value>✨ ¡Emoĩmbyma! (¡Listo para generar!)</value>
|
||||
</data>
|
||||
<data name="TipsFasterQR" xml:space="preserve">
|
||||
<value>Ñe'ẽ pya'épe ha porãve (Consejos para QR Más Rápidos)</value>
|
||||
</data>
|
||||
|
||||
@ -681,6 +681,15 @@
|
||||
<data name="TypeGuideText" xml:space="preserve">
|
||||
<value>📝 Para texto livre, digite qualquer conteúdo que desejar</value>
|
||||
</data>
|
||||
<data name="ContentAddedToastTitle" xml:space="preserve">
|
||||
<value>✅ Conteúdo adicionado!</value>
|
||||
</data>
|
||||
<data name="ContentAddedToastMessage" xml:space="preserve">
|
||||
<value>Opções avançadas disponíveis abaixo. Clique em "Gerar QR Code" quando estiver pronto.</value>
|
||||
</data>
|
||||
<data name="GenerateButtonReady" xml:space="preserve">
|
||||
<value>✨ Pronto para gerar!</value>
|
||||
</data>
|
||||
<data name="TipsFasterQR" xml:space="preserve">
|
||||
<value>Dicas para QR Mais Rápidos</value>
|
||||
</data>
|
||||
|
||||
@ -25,7 +25,9 @@ namespace QRRapidoApp.Services
|
||||
{
|
||||
if (_context.Users == null) return null; // Development mode without MongoDB
|
||||
|
||||
return await _context.Users.Find(u => u.Id == userId).FirstOrDefaultAsync();
|
||||
var userData = await _context.Users.Find(u => u.Id == userId).FirstOrDefaultAsync();
|
||||
|
||||
return userData ?? new User();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
||||
@ -151,7 +151,10 @@
|
||||
validationContentMinLength: '@Localizer["ValidationContentMinLength"]',
|
||||
errorSavingHistory: '@Localizer["ErrorSavingHistory"]',
|
||||
rateLimitReached: '@Localizer["RateLimitReached"]',
|
||||
premiumLogoRequired: '@Localizer["PremiumLogoRequired"]'
|
||||
premiumLogoRequired: '@Localizer["PremiumLogoRequired"]',
|
||||
contentAddedToastTitle: '@Localizer["ContentAddedToastTitle"]',
|
||||
contentAddedToastMessage: '@Localizer["ContentAddedToastMessage"]',
|
||||
generateButtonReady: '@Localizer["GenerateButtonReady"]'
|
||||
};
|
||||
</script>
|
||||
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
"ApplicationName": "QRRapido-Prod",
|
||||
"Environment": "Prod",
|
||||
"ConnectionStrings": {
|
||||
"MongoDB": "mongodb://admin:c4rn31r0@129.146.116.218:27017,141.148.162.114:27017/seu_banco?replicaSet=rs0&authSource=admin"
|
||||
"MongoDB": "mongodb://admin:c4rn31r0@129.146.116.218:27017,141.148.162.114:27017/QrRapido?replicaSet=rs0&authSource=admin"
|
||||
},
|
||||
"Serilog": {
|
||||
"SeqUrl": "http://localhost:5341",
|
||||
|
||||
@ -27,7 +27,7 @@
|
||||
"PriceId": "prod_SnfQTxwE3i8r5L"
|
||||
},
|
||||
"AdSense": {
|
||||
"ClientId": "ca-pub-XXXXXXXXXX",
|
||||
"ClientId": "ca-pub-3475956393038764",
|
||||
"Enabled": true
|
||||
},
|
||||
"Performance": {
|
||||
|
||||
@ -1123,3 +1123,75 @@ html[data-theme="dark"] .form-control:valid {
|
||||
border-color: #22c55e !important;
|
||||
box-shadow: 0 0 0 0.25rem rgba(34, 197, 94, 0.25) !important;
|
||||
}
|
||||
|
||||
/* =================================
|
||||
UX IMPROVEMENTS - BUTTON READY STATES
|
||||
================================= */
|
||||
|
||||
.btn-pulse {
|
||||
animation: buttonPulse 1.5s ease-in-out infinite;
|
||||
box-shadow: 0 0 10px rgba(0, 123, 255, 0.4) !important;
|
||||
}
|
||||
|
||||
.btn-ready {
|
||||
background: linear-gradient(45deg, #007bff, #00d4ff) !important;
|
||||
border-color: #007bff !important;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
transform: scale(1.02);
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.btn-ready::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: -100%;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent);
|
||||
transition: left 0.6s ease;
|
||||
}
|
||||
|
||||
.btn-ready:hover::before {
|
||||
left: 100%;
|
||||
}
|
||||
|
||||
@keyframes buttonPulse {
|
||||
0% {
|
||||
transform: scale(1.02);
|
||||
box-shadow: 0 0 10px rgba(0, 123, 255, 0.4);
|
||||
}
|
||||
50% {
|
||||
transform: scale(1.05);
|
||||
box-shadow: 0 0 20px rgba(0, 123, 255, 0.6);
|
||||
}
|
||||
100% {
|
||||
transform: scale(1.02);
|
||||
box-shadow: 0 0 10px rgba(0, 123, 255, 0.4);
|
||||
}
|
||||
}
|
||||
|
||||
/* Dark mode adjustments for button ready states */
|
||||
html[data-theme="dark"] .btn-pulse {
|
||||
box-shadow: 0 0 10px rgba(96, 165, 250, 0.4) !important;
|
||||
}
|
||||
|
||||
html[data-theme="dark"] .btn-ready {
|
||||
background: linear-gradient(45deg, #3b82f6, #60a5fa) !important;
|
||||
}
|
||||
|
||||
html[data-theme="dark"] @keyframes buttonPulse {
|
||||
0% {
|
||||
transform: scale(1.02);
|
||||
box-shadow: 0 0 10px rgba(96, 165, 250, 0.4);
|
||||
}
|
||||
50% {
|
||||
transform: scale(1.05);
|
||||
box-shadow: 0 0 20px rgba(96, 165, 250, 0.6);
|
||||
}
|
||||
100% {
|
||||
transform: scale(1.02);
|
||||
box-shadow: 0 0 10px rgba(96, 165, 250, 0.4);
|
||||
}
|
||||
}
|
||||
@ -45,6 +45,11 @@ class QRRapidoGenerator {
|
||||
this.selectedStyle = 'classic'; // Estilo padrão
|
||||
this.contentValid = false;
|
||||
|
||||
// UX Improvements - Intelligent delay system
|
||||
this.contentDelayTimer = null;
|
||||
this.hasShownContentToast = false;
|
||||
this.buttonReadyState = false;
|
||||
|
||||
this.initializeEvents();
|
||||
this.checkAdFreeStatus();
|
||||
this.updateLanguage();
|
||||
@ -111,9 +116,25 @@ class QRRapidoGenerator {
|
||||
let timer;
|
||||
qrContent.addEventListener('input', (e) => {
|
||||
clearTimeout(timer);
|
||||
// Clear URL validation timeout if exists
|
||||
if (qrContent.validationTimeout) {
|
||||
clearTimeout(qrContent.validationTimeout);
|
||||
}
|
||||
|
||||
timer = setTimeout(() => {
|
||||
this.handleContentChange(e.target.value);
|
||||
this.updateGenerateButton();
|
||||
|
||||
// Handle URL-specific validation
|
||||
const type = document.getElementById('qr-type')?.value;
|
||||
if (type === 'url') {
|
||||
qrContent.validationTimeout = setTimeout(() => {
|
||||
this.updateGenerateButton();
|
||||
}, 200); // Additional URL validation delay
|
||||
}
|
||||
|
||||
// Trigger UX improvements with delay
|
||||
this.handleContentInputWithDelay(e.target.value);
|
||||
}, 300);
|
||||
});
|
||||
}
|
||||
@ -391,7 +412,6 @@ class QRRapidoGenerator {
|
||||
}
|
||||
|
||||
// Sending request to backend
|
||||
|
||||
const response = await fetch(requestData.endpoint, fetchOptions);
|
||||
|
||||
if (!response.ok) {
|
||||
@ -1057,8 +1077,10 @@ class QRRapidoGenerator {
|
||||
console.log('Calling showUnlimitedCounter directly for logged user');
|
||||
this.showUnlimitedCounter();
|
||||
} else {
|
||||
if (response.status !== 401) {
|
||||
console.log('GetUserStats response not ok:', response.status);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
// If not authenticated or error, keep the default "Carregando..." text
|
||||
console.debug('User not authenticated or error loading stats:', error);
|
||||
@ -1200,9 +1222,9 @@ class QRRapidoGenerator {
|
||||
|
||||
// Create SVG with embedded base64 image
|
||||
const svgData = `<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="${img.width}" height="${img.height}" xmlns="http://www.w3.org/2000/svg">
|
||||
<svg width="${img.width}" height="${img.height}" xmlns="http://www.w3.org/2000/svg">
|
||||
<image width="${img.width}" height="${img.height}" href="data:image/png;base64,${base64Data}"/>
|
||||
</svg>`;
|
||||
</svg>`;
|
||||
resolve(svgData);
|
||||
};
|
||||
img.src = `data:image/png;base64,${base64Data}`;
|
||||
@ -1582,41 +1604,40 @@ class QRRapidoGenerator {
|
||||
const contentField = document.getElementById('qr-content');
|
||||
if (!contentField) return;
|
||||
|
||||
// Auto-fix URL on blur (when user leaves the field)
|
||||
contentField.addEventListener('blur', () => {
|
||||
const type = document.getElementById('qr-type')?.value;
|
||||
//const contentField = document.getElementById('qr-content');
|
||||
//if (!contentField) return;
|
||||
|
||||
if (type === 'url' && contentField.value.trim()) {
|
||||
const originalValue = contentField.value.trim();
|
||||
const fixedValue = this.autoFixURL(originalValue);
|
||||
//// Auto-fix URL on blur (when user leaves the field)
|
||||
//contentField.addEventListener('blur', () => {
|
||||
// const type = document.getElementById('qr-type')?.value;
|
||||
|
||||
if (originalValue !== fixedValue) {
|
||||
contentField.value = fixedValue;
|
||||
console.log('🔧 URL auto-corrigida:', originalValue, '→', fixedValue);
|
||||
// if (type === 'url' && contentField.value.trim()) {
|
||||
// const originalValue = contentField.value.trim();
|
||||
// const fixedValue = this.autoFixURL(originalValue);
|
||||
|
||||
// Revalidar após correção
|
||||
this.updateGenerateButton();
|
||||
}
|
||||
}
|
||||
});
|
||||
// if (originalValue !== fixedValue) {
|
||||
// contentField.value = fixedValue;
|
||||
// console.log('🔧 URL auto-corrigida:', originalValue, '→', fixedValue);
|
||||
|
||||
// Real-time validation with debounce
|
||||
contentField.addEventListener('input', () => {
|
||||
const type = document.getElementById('qr-type')?.value;
|
||||
// // Revalidar após correção
|
||||
// this.updateGenerateButton();
|
||||
// }
|
||||
// }
|
||||
//});
|
||||
|
||||
if (type === 'url') {
|
||||
// Debounce para não validar a cada caractere
|
||||
clearTimeout(contentField.validationTimeout);
|
||||
contentField.validationTimeout = setTimeout(() => {
|
||||
this.updateGenerateButton();
|
||||
}, 500);
|
||||
}
|
||||
});
|
||||
// Note: Real-time validation is now handled in initializeEvents() to avoid duplicate listeners
|
||||
}
|
||||
|
||||
handleTypeSelection(type) {
|
||||
this.selectedType = type;
|
||||
|
||||
// Reset UX improvements flags for new type selection
|
||||
this.hasShownContentToast = false;
|
||||
if (this.contentDelayTimer) {
|
||||
clearTimeout(this.contentDelayTimer);
|
||||
}
|
||||
this.removeButtonReadyState();
|
||||
|
||||
if (type) {
|
||||
this.removeInitialHighlight();
|
||||
// Sempre habilitar campos de conteúdo após selecionar tipo
|
||||
@ -1647,6 +1668,139 @@ class QRRapidoGenerator {
|
||||
this.updateGenerateButton();
|
||||
}
|
||||
|
||||
// UX Improvements - Intelligent delay system
|
||||
handleContentInputWithDelay(content) {
|
||||
// Clear existing timer
|
||||
if (this.contentDelayTimer) {
|
||||
clearTimeout(this.contentDelayTimer);
|
||||
}
|
||||
|
||||
// Only proceed with delay logic if we have valid content and selected type
|
||||
if (this.selectedType && this.validateContent(content) && !this.hasShownContentToast) {
|
||||
this.contentDelayTimer = setTimeout(() => {
|
||||
this.triggerContentReadyUX();
|
||||
}, 2000); // 2 seconds delay after the initial 300ms debounce
|
||||
}
|
||||
}
|
||||
|
||||
triggerContentReadyUX() {
|
||||
if (this.selectedType && this.contentValid && !this.hasShownContentToast) {
|
||||
// Mark as shown to prevent multiple toasts
|
||||
this.hasShownContentToast = true;
|
||||
|
||||
const contentField = document.getElementById('qr-content');
|
||||
const originalValue = contentField.value.trim();
|
||||
const fixedValue = this.autoFixURL(originalValue);
|
||||
if (originalValue !== fixedValue) {
|
||||
contentField.value = fixedValue;
|
||||
this.updateGenerateButton();
|
||||
}
|
||||
|
||||
// Show educational toast
|
||||
this.showContentAddedToast();
|
||||
|
||||
// Update button state with ready indicator
|
||||
this.updateGenerateButtonToReady();
|
||||
|
||||
// Auto scroll to QR generation area
|
||||
this.smoothScrollToQRArea();
|
||||
}
|
||||
}
|
||||
|
||||
showContentAddedToast() {
|
||||
const toastTitle = window.QRRapidoTranslations?.contentAddedToastTitle || '✅ Conteúdo adicionado!';
|
||||
const toastMessage = window.QRRapidoTranslations?.contentAddedToastMessage ||
|
||||
'Use as avançadas abaixo para personalizar o QR. \n Clique em "Gerar QR Code" quando estiver pronto.';
|
||||
|
||||
const fullMessage = `<strong>${toastTitle}</strong><br/>${toastMessage}`;
|
||||
|
||||
// Create educational toast similar to existing system but with longer duration
|
||||
const toast = this.createEducationalToast(fullMessage);
|
||||
this.showGuidanceToast(toast, 10000); // 10 seconds
|
||||
}
|
||||
|
||||
createEducationalToast(message) {
|
||||
const toast = document.createElement('div');
|
||||
toast.className = 'toast align-items-center text-bg-success border-0';
|
||||
toast.setAttribute('role', 'alert');
|
||||
toast.style.minWidth = '400px';
|
||||
toast.id = 'content-added-toast';
|
||||
toast.innerHTML = `
|
||||
<div class="d-flex">
|
||||
<div class="toast-body fw-medium">
|
||||
${message}
|
||||
</div>
|
||||
<button type="button" class="btn-close btn-close-white me-2 m-auto" data-bs-dismiss="toast"></button>
|
||||
</div>
|
||||
`;
|
||||
return toast;
|
||||
}
|
||||
|
||||
updateGenerateButtonToReady() {
|
||||
const generateBtn = document.getElementById('generate-btn');
|
||||
if (generateBtn && this.contentValid) {
|
||||
this.buttonReadyState = true;
|
||||
|
||||
// Add visual ready state
|
||||
generateBtn.classList.add('btn-pulse', 'btn-ready');
|
||||
|
||||
// Update button text with ready indicator
|
||||
const readyText = window.QRRapidoTranslations?.generateButtonReady || '✨ Pronto para gerar!';
|
||||
const originalHtml = generateBtn.innerHTML;
|
||||
|
||||
// Store original for restoration
|
||||
if (!generateBtn.dataset.originalHtml) {
|
||||
generateBtn.dataset.originalHtml = originalHtml;
|
||||
}
|
||||
|
||||
generateBtn.innerHTML = `<i class="fas fa-bolt"></i> ${readyText}`;
|
||||
|
||||
// Remove ready state after 5 seconds
|
||||
setTimeout(() => {
|
||||
this.removeButtonReadyState();
|
||||
}, 5000);
|
||||
}
|
||||
}
|
||||
|
||||
removeButtonReadyState() {
|
||||
const generateBtn = document.getElementById('generate-btn');
|
||||
if (generateBtn && generateBtn.dataset.originalHtml) {
|
||||
generateBtn.classList.remove('btn-pulse', 'btn-ready');
|
||||
generateBtn.innerHTML = generateBtn.dataset.originalHtml;
|
||||
this.buttonReadyState = false;
|
||||
}
|
||||
}
|
||||
|
||||
smoothScrollToQRArea() {
|
||||
// Wait a bit for the toast to appear, then scroll
|
||||
setTimeout(() => {
|
||||
let targetElement;
|
||||
|
||||
// Find the QR preview area or advanced options
|
||||
const qrPreview = document.getElementById('qr-result');
|
||||
const advancedSection = document.querySelector('.advanced-options');
|
||||
const generateBtn = document.getElementById('generate-btn');
|
||||
const toast = document.getElementById('content-added-toast');
|
||||
|
||||
// Priority: QR result > Advanced options > Generate button
|
||||
targetElement = qrPreview || advancedSection || toast || generateBtn;
|
||||
|
||||
if (targetElement) {
|
||||
// Calculate offset - less aggressive on mobile
|
||||
const isMobile = window.innerWidth <= 768;
|
||||
const offset = isMobile ? 20 : 100;
|
||||
|
||||
const elementPosition = targetElement.getBoundingClientRect().top + window.pageYOffset;
|
||||
const offsetPosition = elementPosition - offset;
|
||||
|
||||
window.scrollTo({
|
||||
top: offsetPosition,
|
||||
behavior: 'smooth'
|
||||
});
|
||||
}
|
||||
}, 1000); // 1 second delay to let toast show first
|
||||
}
|
||||
|
||||
enableContentFields(type) {
|
||||
const contentGroup = document.getElementById('content-group');
|
||||
const vcardInterface = document.getElementById('vcard-interface');
|
||||
@ -1966,7 +2120,7 @@ class QRRapidoGenerator {
|
||||
// In a real implementation, these would come from server-side localization
|
||||
// For now, we'll use the JavaScript language strings or fallback to Portuguese
|
||||
return {
|
||||
'url': document.querySelector('[data-type-guide-url]')?.textContent || '🌐 Para gerar QR de URL, digite o endereço completo (ex: https://google.com)',
|
||||
//'url': document.querySelector('[data-type-guide-url]')?.textContent || '🌐 Para gerar QR de URL, digite o endereço completo (ex: https://google.com)',
|
||||
'vcard': document.querySelector('[data-type-guide-vcard]')?.textContent || '👤 Para cartão de visita, preencha nome, telefone e email nos campos abaixo',
|
||||
'wifi': document.querySelector('[data-type-guide-wifi]')?.textContent || '📶 Para WiFi, informe nome da rede, senha e tipo de segurança',
|
||||
'sms': document.querySelector('[data-type-guide-sms]')?.textContent || '💬 Para SMS, digite o número do destinatário e a mensagem',
|
||||
@ -2007,7 +2161,9 @@ class QRRapidoGenerator {
|
||||
toastContainer = existingContainer;
|
||||
} else {
|
||||
// Find the main content area and insert after hero section
|
||||
const mainElement = document.querySelector('main[role="main"]');
|
||||
// const mainElement = document.querySelector('main[role="main"]');
|
||||
const mainElement = document.getElementById('customization-accordion');
|
||||
|
||||
if (mainElement) {
|
||||
// Insert at the beginning of main content
|
||||
mainElement.insertBefore(toastContainer, mainElement.firstChild);
|
||||
|
||||
Loading…
Reference in New Issue
Block a user