From bcf9f659b43a29201c8695d9243e93fadbcfb072 Mon Sep 17 00:00:00 2001 From: Ricardo Carneiro Date: Mon, 11 Aug 2025 12:23:01 -0300 Subject: [PATCH] feat: adsense --- Middleware/LanguageRedirectionMiddleware.cs | 2 +- Resources/SharedResource.es-PY.resx | 9 + Resources/SharedResource.pt-BR.resx | 9 + Services/UserService.cs | 6 +- Views/Shared/_Layout.cshtml | 5 +- appsettings.Production.json | 2 +- appsettings.json | 2 +- wwwroot/css/qrrapido-theme.css | 72 +++++++ wwwroot/js/qr-speed-generator.js | 222 +++++++++++++++++--- 9 files changed, 290 insertions(+), 39 deletions(-) diff --git a/Middleware/LanguageRedirectionMiddleware.cs b/Middleware/LanguageRedirectionMiddleware.cs index 45b9cd2..a8ada2e 100644 --- a/Middleware/LanguageRedirectionMiddleware.cs +++ b/Middleware/LanguageRedirectionMiddleware.cs @@ -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)); diff --git a/Resources/SharedResource.es-PY.resx b/Resources/SharedResource.es-PY.resx index a16e70c..c9bbaab 100644 --- a/Resources/SharedResource.es-PY.resx +++ b/Resources/SharedResource.es-PY.resx @@ -681,6 +681,15 @@ 📝 Para texto libre, ingresa cualquier contenido que desees + + ✅ ¡Contenido ñemboguapy! (Contenido agregado!) + + + Opciones avanzadas ojeporukuaa guýpe. Eity "Generar QR Code" emoĩmbyre jave. + + + ✨ ¡Emoĩmbyma! (¡Listo para generar!) + Ñe'ẽ pya'épe ha porãve (Consejos para QR Más Rápidos) diff --git a/Resources/SharedResource.pt-BR.resx b/Resources/SharedResource.pt-BR.resx index fa7584d..aa134b0 100644 --- a/Resources/SharedResource.pt-BR.resx +++ b/Resources/SharedResource.pt-BR.resx @@ -681,6 +681,15 @@ 📝 Para texto livre, digite qualquer conteúdo que desejar + + ✅ Conteúdo adicionado! + + + Opções avançadas disponíveis abaixo. Clique em "Gerar QR Code" quando estiver pronto. + + + ✨ Pronto para gerar! + Dicas para QR Mais Rápidos diff --git a/Services/UserService.cs b/Services/UserService.cs index 37f1109..adee90d 100644 --- a/Services/UserService.cs +++ b/Services/UserService.cs @@ -24,8 +24,10 @@ namespace QRRapidoApp.Services try { 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) { diff --git a/Views/Shared/_Layout.cshtml b/Views/Shared/_Layout.cshtml index b29df9a..e928813 100644 --- a/Views/Shared/_Layout.cshtml +++ b/Views/Shared/_Layout.cshtml @@ -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"]' }; diff --git a/appsettings.Production.json b/appsettings.Production.json index bc0ad84..738dc5d 100644 --- a/appsettings.Production.json +++ b/appsettings.Production.json @@ -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", diff --git a/appsettings.json b/appsettings.json index bfd97d6..38f0744 100644 --- a/appsettings.json +++ b/appsettings.json @@ -27,7 +27,7 @@ "PriceId": "prod_SnfQTxwE3i8r5L" }, "AdSense": { - "ClientId": "ca-pub-XXXXXXXXXX", + "ClientId": "ca-pub-3475956393038764", "Enabled": true }, "Performance": { diff --git a/wwwroot/css/qrrapido-theme.css b/wwwroot/css/qrrapido-theme.css index 91cdff6..ee2c37b 100644 --- a/wwwroot/css/qrrapido-theme.css +++ b/wwwroot/css/qrrapido-theme.css @@ -1122,4 +1122,76 @@ html[data-theme="dark"] .required-field:valid, 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); + } } \ No newline at end of file diff --git a/wwwroot/js/qr-speed-generator.js b/wwwroot/js/qr-speed-generator.js index 094c12c..53dcf07 100644 --- a/wwwroot/js/qr-speed-generator.js +++ b/wwwroot/js/qr-speed-generator.js @@ -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,7 +1077,9 @@ class QRRapidoGenerator { console.log('Calling showUnlimitedCounter directly for logged user'); this.showUnlimitedCounter(); } else { - console.log('GetUserStats response not ok:', response.status); + if (response.status !== 401) { + console.log('GetUserStats response not ok:', response.status); + } } } catch (error) { // If not authenticated or error, keep the default "Carregando..." text @@ -1200,9 +1222,9 @@ class QRRapidoGenerator { // Create SVG with embedded base64 image const svgData = ` - - -`; + + + `; resolve(svgData); }; img.src = `data:image/png;base64,${base64Data}`; @@ -1581,42 +1603,41 @@ class QRRapidoGenerator { setupURLValidationListeners() { const contentField = document.getElementById('qr-content'); if (!contentField) return; + + //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; + //// Auto-fix URL on blur (when user leaves the field) + //contentField.addEventListener('blur', () => { + // const type = document.getElementById('qr-type')?.value; - if (type === 'url' && contentField.value.trim()) { - const originalValue = contentField.value.trim(); - const fixedValue = this.autoFixURL(originalValue); + // if (type === 'url' && contentField.value.trim()) { + // const originalValue = contentField.value.trim(); + // const fixedValue = this.autoFixURL(originalValue); - if (originalValue !== fixedValue) { - contentField.value = fixedValue; - console.log('🔧 URL auto-corrigida:', originalValue, '→', fixedValue); + // if (originalValue !== fixedValue) { + // contentField.value = fixedValue; + // console.log('🔧 URL auto-corrigida:', originalValue, '→', fixedValue); - // Revalidar após correção - this.updateGenerateButton(); - } - } - }); + // // Revalidar após correção + // this.updateGenerateButton(); + // } + // } + //}); - // Real-time validation with debounce - contentField.addEventListener('input', () => { - const type = document.getElementById('qr-type')?.value; - - 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 = `${toastTitle}
${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 = ` +
+
+ ${message} +
+ +
+ `; + 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 = ` ${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);