fix: ajustes de navegação
This commit is contained in:
parent
71c575f879
commit
49da9f874a
4
.github/workflows/deploy.yml
vendored
4
.github/workflows/deploy.yml
vendored
@ -103,6 +103,8 @@ jobs:
|
||||
--name qrrapido-staging \
|
||||
--restart unless-stopped \
|
||||
-p 5000:8080 \
|
||||
--add-host=host.docker.internal:host-gateway \
|
||||
-e Serilog__SeqUrl="http://host.docker.internal:5343" \
|
||||
-e ASPNETCORE_ENVIRONMENT=Staging \
|
||||
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:develop
|
||||
EOF
|
||||
@ -124,6 +126,8 @@ jobs:
|
||||
--name qrrapido-staging \
|
||||
--restart unless-stopped \
|
||||
-p 5000:8080 \
|
||||
--add-host=host.docker.internal:host-gateway \
|
||||
-e Serilog__SeqUrl="http://host.docker.internal:5342" \
|
||||
-e ASPNETCORE_ENVIRONMENT=Staging \
|
||||
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:develop
|
||||
EOF
|
||||
|
||||
@ -6,7 +6,7 @@ namespace QRRapidoApp.Middleware
|
||||
{
|
||||
private readonly RequestDelegate _next;
|
||||
private readonly ILogger<LanguageRedirectionMiddleware> _logger;
|
||||
private readonly string[] _supportedCultures = { "pt-BR", "es-PY", "es" };
|
||||
private readonly string[] _supportedCultures = { "pt-BR", "es-PY" };
|
||||
private const string DefaultCulture = "pt-BR";
|
||||
|
||||
public LanguageRedirectionMiddleware(RequestDelegate next, ILogger<LanguageRedirectionMiddleware> logger)
|
||||
@ -18,31 +18,34 @@ namespace QRRapidoApp.Middleware
|
||||
public async Task InvokeAsync(HttpContext context)
|
||||
{
|
||||
var path = context.Request.Path.Value?.TrimStart('/') ?? "";
|
||||
|
||||
// Skip if already has culture in path, or if it's an API, static file, or special route
|
||||
|
||||
if (HasCultureInPath(path) || IsSpecialRoute(path))
|
||||
{
|
||||
await _next(context);
|
||||
return;
|
||||
}
|
||||
|
||||
// Detect browser language
|
||||
var detectedCulture = DetectBrowserLanguage(context);
|
||||
|
||||
// Build redirect URL with culture
|
||||
|
||||
// If the detected culture is the default, do not redirect.
|
||||
if (detectedCulture == DefaultCulture)
|
||||
{
|
||||
await _next(context);
|
||||
return;
|
||||
}
|
||||
|
||||
var redirectUrl = $"/{detectedCulture}";
|
||||
if (!string.IsNullOrEmpty(path))
|
||||
{
|
||||
redirectUrl += $"/{path}";
|
||||
}
|
||||
|
||||
// Add query string if present
|
||||
|
||||
if (context.Request.QueryString.HasValue)
|
||||
{
|
||||
redirectUrl += context.Request.QueryString.Value;
|
||||
}
|
||||
|
||||
_logger.LogInformation("Redirecting to localized URL: {RedirectUrl} (detected culture: {Culture})",
|
||||
_logger.LogInformation("Redirecting to localized URL: {RedirectUrl} (detected culture: {Culture})",
|
||||
redirectUrl, detectedCulture);
|
||||
|
||||
context.Response.Redirect(redirectUrl, permanent: false);
|
||||
@ -64,7 +67,7 @@ namespace QRRapidoApp.Middleware
|
||||
{
|
||||
var specialRoutes = new[]
|
||||
{
|
||||
"api/", "health", "_framework/", "lib/", "css/", "js/", "images/",
|
||||
"api/", "health", "_framework/", "lib/", "css/", "js/", "images/",
|
||||
"favicon.ico", "robots.txt", "sitemap.xml",
|
||||
"signin-microsoft", "signin-google", "signout-callback-oidc",
|
||||
"Account/ExternalLoginCallback", "Account/Logout", "Pagamento/CreateCheckout",
|
||||
@ -80,41 +83,30 @@ namespace QRRapidoApp.Middleware
|
||||
|
||||
if (acceptLanguage != null && acceptLanguage.Any())
|
||||
{
|
||||
// Check for exact matches first
|
||||
foreach (var lang in acceptLanguage.OrderByDescending(x => x.Quality ?? 1.0))
|
||||
{
|
||||
var langCode = lang.Value.Value;
|
||||
|
||||
// Special case: es-PY should redirect to pt-BR
|
||||
// Exact match for es-PY
|
||||
if (string.Equals(langCode, "es-PY", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return DefaultCulture;
|
||||
return "es-PY";
|
||||
}
|
||||
|
||||
// Check exact match
|
||||
if (_supportedCultures.Contains(langCode))
|
||||
{
|
||||
return langCode;
|
||||
}
|
||||
|
||||
// Check language part only (e.g., 'es' from 'es-AR')
|
||||
var languagePart = langCode.Split('-')[0];
|
||||
|
||||
// Map 'es' to 'es-PY' specifically
|
||||
if (string.Equals(languagePart, "es", StringComparison.OrdinalIgnoreCase))
|
||||
// Generic 'es' maps to 'es-PY'
|
||||
if (langCode.StartsWith("es-", StringComparison.OrdinalIgnoreCase) || string.Equals(langCode, "es", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return "es-PY";
|
||||
}
|
||||
|
||||
var matchingCulture = _supportedCultures.FirstOrDefault(c => c.StartsWith(languagePart + "-") || c == languagePart);
|
||||
if (matchingCulture != null)
|
||||
// Check for pt-BR
|
||||
if (langCode.StartsWith("pt-", StringComparison.OrdinalIgnoreCase) || string.Equals(langCode, "pt", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return matchingCulture;
|
||||
return "pt-BR";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Default fallback
|
||||
return DefaultCulture;
|
||||
}
|
||||
}
|
||||
|
||||
@ -147,7 +147,6 @@ builder.Services.Configure<RequestLocalizationOptions>(options =>
|
||||
{
|
||||
new CultureInfo("pt-BR"),
|
||||
new CultureInfo("es-PY"),
|
||||
new CultureInfo("es")
|
||||
};
|
||||
|
||||
options.DefaultRequestCulture = new RequestCulture("pt-BR", "pt-BR");
|
||||
@ -284,7 +283,7 @@ app.MapHealthChecks("/healthcheck");
|
||||
// Language routes (must be first)
|
||||
app.MapControllerRoute(
|
||||
name: "localized",
|
||||
pattern: "{culture:regex(^(pt-BR|es-PY|es)$)}/{controller=Home}/{action=Index}/{id?}");
|
||||
pattern: "{culture:regex(^(es-PY)$)}/{controller=Home}/{action=Index}/{id?}");
|
||||
|
||||
// API routes
|
||||
app.MapControllerRoute(
|
||||
|
||||
@ -137,6 +137,23 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6 mb-3">
|
||||
<div class="d-grid opacity-controlled disabled-state" id="next-button-group">
|
||||
<button type="button" class="btn btn-success" id="next-btn">
|
||||
<i class="fas fa-arrow-right"></i> Personalizar
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6 mb-3">
|
||||
<div class="d-grid opacity-controlled disabled-state" id="button-gerar-quick-div">
|
||||
<button type="submit" class="btn btn-primary" id="generate-quick-btn">
|
||||
<i class="fas fa-qrcode"></i> Gerar Rápido
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Dynamic QR Section (Premium) -->
|
||||
<div id="dynamic-qr-section" style="display: none;" class="opacity-controlled disabled-state">
|
||||
<div class="premium-feature-box">
|
||||
|
||||
@ -183,6 +183,21 @@ class QRRapidoGenerator {
|
||||
if (saveBtn) {
|
||||
saveBtn.addEventListener('click', this.saveToHistory.bind(this));
|
||||
}
|
||||
|
||||
// Next button navigation
|
||||
const nextBtn = document.getElementById('next-btn');
|
||||
if (nextBtn) {
|
||||
nextBtn.addEventListener('click', this.handleNextButtonClick.bind(this));
|
||||
}
|
||||
|
||||
// Generate quick button (same functionality as main generate button)
|
||||
const generateQuickBtn = document.getElementById('generate-quick-btn');
|
||||
if (generateQuickBtn) {
|
||||
generateQuickBtn.addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
this.generateQRWithTimer(e);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
setupDownloadButtons() {
|
||||
@ -718,6 +733,9 @@ class QRRapidoGenerator {
|
||||
if (result.readabilityInfo && typeof this.displayReadabilityAnalysis === 'function') {
|
||||
this.displayReadabilityAnalysis(result.readabilityInfo);
|
||||
}
|
||||
|
||||
// Scroll suave para o preview após a geração
|
||||
this.scrollToPreview();
|
||||
|
||||
// CORREÇÃO: Log para debug - verificar se QR code tem logo
|
||||
const logoUpload = document.getElementById('logo-upload');
|
||||
@ -1629,7 +1647,7 @@ class QRRapidoGenerator {
|
||||
if (this.selectedType && this.validateContent(content) && !this.hasShownContentToast) {
|
||||
this.contentDelayTimer = setTimeout(() => {
|
||||
this.triggerContentReadyUX();
|
||||
}, 2000); // 2 seconds delay after the initial 300ms debounce
|
||||
}, 7000); // 7 seconds delay after the initial 300ms debounce
|
||||
}
|
||||
}
|
||||
|
||||
@ -1651,9 +1669,6 @@ class QRRapidoGenerator {
|
||||
|
||||
// Update button state with ready indicator
|
||||
this.updateGenerateButtonToReady();
|
||||
|
||||
// Auto scroll to QR generation area
|
||||
this.smoothScrollToQRArea();
|
||||
}
|
||||
}
|
||||
|
||||
@ -1966,7 +1981,12 @@ class QRRapidoGenerator {
|
||||
|
||||
updateGenerateButton() {
|
||||
const generateBtn = document.getElementById('generate-btn');
|
||||
if (!generateBtn) return;
|
||||
const generateQuickBtn = document.getElementById('generate-quick-btn');
|
||||
const nextGroup = document.getElementById('next-button-group');
|
||||
const nextBtn = document.getElementById('next-btn');
|
||||
const quickGroup = document.getElementById('button-gerar-quick-div');
|
||||
|
||||
if (!generateBtn && !generateQuickBtn) return;
|
||||
|
||||
let isValid = false;
|
||||
const type = this.selectedType;
|
||||
@ -2022,15 +2042,146 @@ class QRRapidoGenerator {
|
||||
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');
|
||||
// Controle do botão principal "Gerar QR Code"
|
||||
if (generateBtn) {
|
||||
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');
|
||||
}
|
||||
}
|
||||
|
||||
// Controle do botão "Gerar Rápido" com a mesma validação
|
||||
if (generateQuickBtn) {
|
||||
generateQuickBtn.disabled = !isValid;
|
||||
if (quickGroup) {
|
||||
if (!isValid) {
|
||||
quickGroup.classList.add('disabled-state');
|
||||
} else {
|
||||
quickGroup.classList.remove('disabled-state');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Controle do botão "Próximo/Personalizar" com validação mais permissiva
|
||||
const isNextValid = this.isValidForNext(type);
|
||||
if (nextBtn) {
|
||||
nextBtn.disabled = !isNextValid;
|
||||
if (nextGroup) {
|
||||
if (!isNextValid) {
|
||||
nextGroup.classList.add('disabled-state');
|
||||
} else {
|
||||
nextGroup.classList.remove('disabled-state');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Validação mais permissiva para o botão "Próximo"
|
||||
isValidForNext(type) {
|
||||
if (!type) return false;
|
||||
|
||||
if (type === 'url') {
|
||||
const contentField = document.getElementById('qr-content');
|
||||
const content = contentField?.value || '';
|
||||
const trimmedContent = content.trim();
|
||||
|
||||
// Validação mais simples: pelo menos 1 letra, um ponto, e mais 1 letra
|
||||
const simpleUrlPattern = /\w+\.\w+/;
|
||||
return trimmedContent.length >= 4 && simpleUrlPattern.test(trimmedContent);
|
||||
|
||||
} else if (type === 'text') {
|
||||
const contentField = document.getElementById('qr-content');
|
||||
const content = contentField?.value || '';
|
||||
return content.trim().length >= 1; // Mais permissivo para texto
|
||||
|
||||
} else if (type === 'vcard') {
|
||||
// Verificar apenas campos obrigatórios básicos
|
||||
const name = document.getElementById('vcard-name')?.value || '';
|
||||
const mobile = document.getElementById('vcard-mobile')?.value || '';
|
||||
const email = document.getElementById('vcard-email')?.value || '';
|
||||
|
||||
return name.trim().length >= 2 &&
|
||||
(mobile.trim().length >= 8 || email.includes('@'));
|
||||
|
||||
} else if (type === 'wifi') {
|
||||
const ssid = document.getElementById('wifi-ssid')?.value || '';
|
||||
return ssid.trim().length >= 2;
|
||||
|
||||
} else if (type === 'sms') {
|
||||
const number = document.getElementById('sms-number')?.value || '';
|
||||
const message = document.getElementById('sms-message')?.value || '';
|
||||
return number.trim().length >= 8 && message.trim().length >= 1;
|
||||
|
||||
} else if (type === 'email') {
|
||||
const to = document.getElementById('email-to')?.value || '';
|
||||
const subject = document.getElementById('email-subject')?.value || '';
|
||||
return to.includes('@') && subject.trim().length >= 1;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Método para lidar com o clique do botão "Próximo"
|
||||
handleNextButtonClick() {
|
||||
// Abrir o accordion de personalização avançada
|
||||
const customizationPanel = document.getElementById('customization-panel');
|
||||
const customizationAccordion = document.getElementById('customization-accordion');
|
||||
|
||||
if (customizationPanel && !customizationPanel.classList.contains('show')) {
|
||||
const bsCollapse = new bootstrap.Collapse(customizationPanel, {
|
||||
show: true
|
||||
});
|
||||
}
|
||||
|
||||
// Scroll suave até a personalização avançada
|
||||
if (customizationAccordion) {
|
||||
customizationAccordion.scrollIntoView({
|
||||
behavior: 'smooth',
|
||||
block: 'start'
|
||||
});
|
||||
}
|
||||
|
||||
// Opcionalmente mostrar um toast de orientação
|
||||
this.showAdvancedCustomizationToast();
|
||||
}
|
||||
|
||||
// Toast informativo sobre personalização avançada
|
||||
showAdvancedCustomizationToast() {
|
||||
const message = '✨ <strong>Personalização Avançada!</strong><br/>Customize cores, tamanho, bordas e adicione seu logo. Quando estiver pronto, clique em "Gerar QR Code".';
|
||||
const toast = this.createEducationalToast(message);
|
||||
this.showGuidanceToast(toast, 8000); // 8 segundos
|
||||
}
|
||||
|
||||
// Scroll suave para o preview após geração do QR Code
|
||||
scrollToPreview() {
|
||||
setTimeout(() => {
|
||||
const isMobile = window.innerWidth <= 768;
|
||||
|
||||
if (isMobile) {
|
||||
// No mobile, fazer scroll para o preview específico dentro da sidebar
|
||||
const previewDiv = document.getElementById('qr-preview');
|
||||
if (previewDiv) {
|
||||
const offsetTop = previewDiv.offsetTop - 60; // 60px de margem do topo
|
||||
window.scrollTo({
|
||||
top: offsetTop,
|
||||
behavior: 'smooth'
|
||||
});
|
||||
}
|
||||
} else {
|
||||
// No desktop, manter o comportamento atual
|
||||
const previewCard = document.querySelector('.col-lg-4');
|
||||
if (previewCard) {
|
||||
previewCard.scrollIntoView({
|
||||
behavior: 'smooth',
|
||||
block: 'center'
|
||||
});
|
||||
}
|
||||
}
|
||||
}, 500); // Pequeno delay para garantir que o QR foi renderizado
|
||||
}
|
||||
|
||||
// Remove destaque inicial quando tipo for selecionado
|
||||
|
||||
Loading…
Reference in New Issue
Block a user