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 \
|
--name qrrapido-staging \
|
||||||
--restart unless-stopped \
|
--restart unless-stopped \
|
||||||
-p 5000:8080 \
|
-p 5000:8080 \
|
||||||
|
--add-host=host.docker.internal:host-gateway \
|
||||||
|
-e Serilog__SeqUrl="http://host.docker.internal:5343" \
|
||||||
-e ASPNETCORE_ENVIRONMENT=Staging \
|
-e ASPNETCORE_ENVIRONMENT=Staging \
|
||||||
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:develop
|
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:develop
|
||||||
EOF
|
EOF
|
||||||
@ -124,6 +126,8 @@ jobs:
|
|||||||
--name qrrapido-staging \
|
--name qrrapido-staging \
|
||||||
--restart unless-stopped \
|
--restart unless-stopped \
|
||||||
-p 5000:8080 \
|
-p 5000:8080 \
|
||||||
|
--add-host=host.docker.internal:host-gateway \
|
||||||
|
-e Serilog__SeqUrl="http://host.docker.internal:5342" \
|
||||||
-e ASPNETCORE_ENVIRONMENT=Staging \
|
-e ASPNETCORE_ENVIRONMENT=Staging \
|
||||||
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:develop
|
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:develop
|
||||||
EOF
|
EOF
|
||||||
|
|||||||
@ -6,7 +6,7 @@ namespace QRRapidoApp.Middleware
|
|||||||
{
|
{
|
||||||
private readonly RequestDelegate _next;
|
private readonly RequestDelegate _next;
|
||||||
private readonly ILogger<LanguageRedirectionMiddleware> _logger;
|
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";
|
private const string DefaultCulture = "pt-BR";
|
||||||
|
|
||||||
public LanguageRedirectionMiddleware(RequestDelegate next, ILogger<LanguageRedirectionMiddleware> logger)
|
public LanguageRedirectionMiddleware(RequestDelegate next, ILogger<LanguageRedirectionMiddleware> logger)
|
||||||
@ -19,24 +19,27 @@ namespace QRRapidoApp.Middleware
|
|||||||
{
|
{
|
||||||
var path = context.Request.Path.Value?.TrimStart('/') ?? "";
|
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))
|
if (HasCultureInPath(path) || IsSpecialRoute(path))
|
||||||
{
|
{
|
||||||
await _next(context);
|
await _next(context);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Detect browser language
|
|
||||||
var detectedCulture = DetectBrowserLanguage(context);
|
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}";
|
var redirectUrl = $"/{detectedCulture}";
|
||||||
if (!string.IsNullOrEmpty(path))
|
if (!string.IsNullOrEmpty(path))
|
||||||
{
|
{
|
||||||
redirectUrl += $"/{path}";
|
redirectUrl += $"/{path}";
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add query string if present
|
|
||||||
if (context.Request.QueryString.HasValue)
|
if (context.Request.QueryString.HasValue)
|
||||||
{
|
{
|
||||||
redirectUrl += context.Request.QueryString.Value;
|
redirectUrl += context.Request.QueryString.Value;
|
||||||
@ -80,41 +83,30 @@ namespace QRRapidoApp.Middleware
|
|||||||
|
|
||||||
if (acceptLanguage != null && acceptLanguage.Any())
|
if (acceptLanguage != null && acceptLanguage.Any())
|
||||||
{
|
{
|
||||||
// Check for exact matches first
|
|
||||||
foreach (var lang in acceptLanguage.OrderByDescending(x => x.Quality ?? 1.0))
|
foreach (var lang in acceptLanguage.OrderByDescending(x => x.Quality ?? 1.0))
|
||||||
{
|
{
|
||||||
var langCode = lang.Value.Value;
|
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))
|
if (string.Equals(langCode, "es-PY", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
|
||||||
return DefaultCulture;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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))
|
|
||||||
{
|
{
|
||||||
return "es-PY";
|
return "es-PY";
|
||||||
}
|
}
|
||||||
|
|
||||||
var matchingCulture = _supportedCultures.FirstOrDefault(c => c.StartsWith(languagePart + "-") || c == languagePart);
|
// Generic 'es' maps to 'es-PY'
|
||||||
if (matchingCulture != null)
|
if (langCode.StartsWith("es-", StringComparison.OrdinalIgnoreCase) || string.Equals(langCode, "es", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
return matchingCulture;
|
return "es-PY";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for pt-BR
|
||||||
|
if (langCode.StartsWith("pt-", StringComparison.OrdinalIgnoreCase) || string.Equals(langCode, "pt", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
return "pt-BR";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Default fallback
|
|
||||||
return DefaultCulture;
|
return DefaultCulture;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -147,7 +147,6 @@ builder.Services.Configure<RequestLocalizationOptions>(options =>
|
|||||||
{
|
{
|
||||||
new CultureInfo("pt-BR"),
|
new CultureInfo("pt-BR"),
|
||||||
new CultureInfo("es-PY"),
|
new CultureInfo("es-PY"),
|
||||||
new CultureInfo("es")
|
|
||||||
};
|
};
|
||||||
|
|
||||||
options.DefaultRequestCulture = new RequestCulture("pt-BR", "pt-BR");
|
options.DefaultRequestCulture = new RequestCulture("pt-BR", "pt-BR");
|
||||||
@ -284,7 +283,7 @@ app.MapHealthChecks("/healthcheck");
|
|||||||
// Language routes (must be first)
|
// Language routes (must be first)
|
||||||
app.MapControllerRoute(
|
app.MapControllerRoute(
|
||||||
name: "localized",
|
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
|
// API routes
|
||||||
app.MapControllerRoute(
|
app.MapControllerRoute(
|
||||||
|
|||||||
@ -137,6 +137,23 @@
|
|||||||
</div>
|
</div>
|
||||||
</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) -->
|
<!-- Dynamic QR Section (Premium) -->
|
||||||
<div id="dynamic-qr-section" style="display: none;" class="opacity-controlled disabled-state">
|
<div id="dynamic-qr-section" style="display: none;" class="opacity-controlled disabled-state">
|
||||||
<div class="premium-feature-box">
|
<div class="premium-feature-box">
|
||||||
|
|||||||
@ -183,6 +183,21 @@ class QRRapidoGenerator {
|
|||||||
if (saveBtn) {
|
if (saveBtn) {
|
||||||
saveBtn.addEventListener('click', this.saveToHistory.bind(this));
|
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() {
|
setupDownloadButtons() {
|
||||||
@ -719,6 +734,9 @@ class QRRapidoGenerator {
|
|||||||
this.displayReadabilityAnalysis(result.readabilityInfo);
|
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
|
// CORREÇÃO: Log para debug - verificar se QR code tem logo
|
||||||
const logoUpload = document.getElementById('logo-upload');
|
const logoUpload = document.getElementById('logo-upload');
|
||||||
const hasLogo = logoUpload && logoUpload.files && logoUpload.files.length > 0;
|
const hasLogo = logoUpload && logoUpload.files && logoUpload.files.length > 0;
|
||||||
@ -1629,7 +1647,7 @@ class QRRapidoGenerator {
|
|||||||
if (this.selectedType && this.validateContent(content) && !this.hasShownContentToast) {
|
if (this.selectedType && this.validateContent(content) && !this.hasShownContentToast) {
|
||||||
this.contentDelayTimer = setTimeout(() => {
|
this.contentDelayTimer = setTimeout(() => {
|
||||||
this.triggerContentReadyUX();
|
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
|
// Update button state with ready indicator
|
||||||
this.updateGenerateButtonToReady();
|
this.updateGenerateButtonToReady();
|
||||||
|
|
||||||
// Auto scroll to QR generation area
|
|
||||||
this.smoothScrollToQRArea();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1966,7 +1981,12 @@ class QRRapidoGenerator {
|
|||||||
|
|
||||||
updateGenerateButton() {
|
updateGenerateButton() {
|
||||||
const generateBtn = document.getElementById('generate-btn');
|
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;
|
let isValid = false;
|
||||||
const type = this.selectedType;
|
const type = this.selectedType;
|
||||||
@ -2022,15 +2042,146 @@ class QRRapidoGenerator {
|
|||||||
isValid = data.to.trim() !== '' && data.subject.trim() !== '';
|
isValid = data.to.trim() !== '' && data.subject.trim() !== '';
|
||||||
}
|
}
|
||||||
|
|
||||||
generateBtn.disabled = !isValid;
|
// Controle do botão principal "Gerar QR Code"
|
||||||
|
if (generateBtn) {
|
||||||
if (isValid) {
|
generateBtn.disabled = !isValid;
|
||||||
generateBtn.classList.remove('btn-secondary', 'disabled');
|
if (isValid) {
|
||||||
generateBtn.classList.add('btn-primary');
|
generateBtn.classList.remove('btn-secondary', 'disabled');
|
||||||
} else {
|
generateBtn.classList.add('btn-primary');
|
||||||
generateBtn.classList.remove('btn-primary');
|
} else {
|
||||||
generateBtn.classList.add('btn-secondary', 'disabled');
|
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
|
// Remove destaque inicial quando tipo for selecionado
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user