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 = `
+
+ `;
+ 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);