Compare commits

..

2 Commits

Author SHA1 Message Date
Ricardo Carneiro
55ad73b505 fix: erros de produção
All checks were successful
BCards Deployment Pipeline / Run Tests (push) Successful in 4s
BCards Deployment Pipeline / PR Validation (push) Has been skipped
BCards Deployment Pipeline / Build and Push Image (push) Successful in 8m19s
BCards Deployment Pipeline / Deploy to Production (ARM - OCI) (push) Successful in 2m19s
BCards Deployment Pipeline / Deploy to Staging (x86 - Local) (push) Has been skipped
BCards Deployment Pipeline / Cleanup Old Resources (push) Has been skipped
BCards Deployment Pipeline / Deployment Summary (push) Successful in 1s
2025-09-12 00:28:45 -03:00
Ricardo Carneiro
ffac8a787b fix: ajuste de tamanho de imagem e toast de mensagens para ajustes 2025-09-10 18:42:50 -03:00
7 changed files with 628 additions and 36 deletions

View File

@ -25,7 +25,8 @@
"Bash(/mnt/c/vscode/vcart.me.novo/clean-build.sh:*)",
"Bash(sed:*)",
"Bash(./clean-build.sh:*)",
"Bash(git add:*)"
"Bash(git add:*)",
"Bash(scp:*)"
]
},
"enableAllProjectMcpServers": false

452
categorias.json Normal file
View File

@ -0,0 +1,452 @@
[
{
"name": "Artesanato",
"slug": "artesanato",
"icon": "🎨",
"seoKeywords": [
"artesanato",
"artesão",
"feito à mão",
"personalizado",
"criativo",
"decoração"
],
"description": "Artesãos e criadores de produtos feitos à mão, decoração e arte personalizada",
"isActive": true
},
{
"name": "Papelaria",
"slug": "papelaria",
"icon": "📝",
"seoKeywords": [
"papelaria",
"escritório",
"material escolar",
"impressão",
"convites",
"personalização"
],
"description": "Lojas de papelaria, material de escritório, impressão e produtos personalizados",
"isActive": true
},
{
"name": "Coaching",
"slug": "coaching",
"icon": "🎯",
"seoKeywords": [
"coaching",
"mentoria",
"desenvolvimento pessoal",
"life coach",
"business coach",
"liderança"
],
"description": "Coaches, mentores e profissionais de desenvolvimento pessoal e empresarial",
"isActive": true
},
{
"name": "Fitness",
"slug": "fitness",
"icon": "💪",
"seoKeywords": [
"fitness",
"academia",
"personal trainer",
"musculação",
"treinamento",
"exercícios"
],
"description": "Personal trainers, academias, estúdios de pilates e profissionais fitness",
"isActive": true
},
{
"name": "Psicologia",
"slug": "psicologia",
"icon": "🧠",
"seoKeywords": [
"psicólogo",
"terapia",
"psicologia",
"saúde mental",
"consultório",
"atendimento"
],
"description": "Psicólogos, terapeutas e profissionais de saúde mental",
"isActive": true
},
{
"name": "Nutrição",
"slug": "nutricao",
"icon": "🥗",
"seoKeywords": [
"nutricionista",
"dieta",
"nutrição",
"alimentação saudável",
"consultoria nutricional",
"emagrecimento"
],
"description": "Nutricionistas, consultores em alimentação e profissionais da nutrição",
"isActive": true
},
{
"name": "Moda e Vestuário",
"slug": "moda",
"icon": "👗",
"seoKeywords": [
"moda",
"vestuário",
"roupas",
"fashion",
"estilista",
"costureira"
],
"description": "Lojas de roupas, estilistas, costureiras e profissionais da moda",
"isActive": true
},
{
"name": "Fotografia",
"slug": "fotografia",
"icon": "📸",
"seoKeywords": [
"fotógrafo",
"fotografia",
"ensaio",
"casamento",
"eventos",
"retratos"
],
"description": "Fotógrafos profissionais, estúdios fotográficos e serviços de fotografia",
"isActive": true
},
{
"name": "Marketing Digital",
"slug": "marketing-digital",
"icon": "📱",
"seoKeywords": [
"marketing digital",
"social media",
"publicidade",
"SEO",
"gestão de redes",
"digital"
],
"description": "Agências de marketing digital, gestores de redes sociais e consultores digitais",
"isActive": true
},
{
"name": "Contabilidade",
"slug": "contabilidade",
"icon": "📊",
"seoKeywords": [
"contador",
"contabilidade",
"fiscal",
"imposto de renda",
"MEI",
"consultoria contábil"
],
"description": "Contadores, escritórios contábeis e consultoria fiscal",
"isActive": true
},
{
"name": "Design",
"slug": "design",
"icon": "🎨",
"seoKeywords": [
"designer",
"design gráfico",
"identidade visual",
"logo",
"criativo",
"branding"
],
"description": "Designers gráficos, criativos e profissionais de identidade visual",
"isActive": true
},
{
"name": "Consultoria",
"slug": "consultoria",
"icon": "🤝",
"seoKeywords": [
"consultor",
"consultoria",
"assessoria",
"especialista",
"negócios",
"estratégia"
],
"description": "Consultores especializados, assessoria empresarial e serviços de consultoria",
"isActive": true
},
{
"name": "Pets",
"slug": "pets",
"icon": "🐕",
"seoKeywords": [
"veterinário",
"pet shop",
"animais",
"cuidados",
"petshop",
"adestramento"
],
"description": "Veterinários, pet shops, adestradores e serviços para animais",
"isActive": true
},
{
"name": "Casa e Jardim",
"slug": "casa-jardim",
"icon": "🏡",
"seoKeywords": [
"paisagismo",
"jardinagem",
"decoração",
"casa",
"jardim",
"plantas"
],
"description": "Paisagistas, jardineiros, decoradores e serviços para casa e jardim",
"isActive": true
},
{
"name": "Automóveis",
"slug": "automoveis",
"icon": "🚗",
"seoKeywords": [
"mecânico",
"automóveis",
"carros",
"oficina",
"manutenção",
"peças"
],
"description": "Mecânicos, oficinas, lojas de peças e serviços automotivos",
"isActive": true
},
{
"name": "Turismo",
"slug": "turismo",
"icon": "✈️",
"seoKeywords": [
"turismo",
"viagem",
"agência",
"guia turístico",
"passeios",
"hospedagem"
],
"description": "Agências de turismo, guias, pousadas e prestadores de serviços turísticos",
"isActive": true
},
{
"name": "Música",
"slug": "musica",
"icon": "🎵",
"seoKeywords": [
"músico",
"professor de música",
"instrumentos",
"aulas",
"banda",
"eventos musicais"
],
"description": "Músicos, professores de música, bandas e profissionais do entretenimento",
"isActive": true
},
{
"name": "Idiomas",
"slug": "idiomas",
"icon": "🗣️",
"seoKeywords": [
"professor de idiomas",
"inglês",
"espanhol",
"tradutor",
"aulas particulares",
"curso de idiomas"
],
"description": "Professores de idiomas, tradutores e escolas de línguas",
"isActive": true
},
{
"name": "Limpeza",
"slug": "limpeza",
"icon": "🧽",
"seoKeywords": [
"limpeza",
"faxina",
"diarista",
"higienização",
"empresa de limpeza",
"doméstica"
],
"description": "Empresas de limpeza, diaristas e serviços de higienização",
"isActive": true
},
{
"name": "Segurança",
"slug": "seguranca",
"icon": "🛡️",
"seoKeywords": [
"segurança",
"vigilante",
"porteiro",
"alarmes",
"monitoramento",
"proteção"
],
"description": "Empresas de segurança, vigilantes e serviços de proteção",
"isActive": true
},
{
"name": "Eventos",
"slug": "eventos",
"icon": "🎉",
"seoKeywords": [
"eventos",
"festa",
"casamento",
"buffet",
"decoração de festas",
"cerimonial"
],
"description": "Organizadores de eventos, buffets, decoração e cerimonial",
"isActive": true
},
{
"name": "Transporte",
"slug": "transporte",
"icon": "🚐",
"seoKeywords": [
"transporte",
"frete",
"mudança",
"delivery",
"motorista",
"logística"
],
"description": "Empresas de transporte, fretes, mudanças e serviços de entrega",
"isActive": true
},
{
"name": "Construção",
"slug": "construcao",
"icon": "🔨",
"seoKeywords": [
"construção",
"pedreiro",
"pintor",
"eletricista",
"encanador",
"reforma"
],
"description": "Profissionais da construção civil, reformas e manutenção predial",
"isActive": true
},
{
"name": "Joias e Acessórios",
"slug": "joias",
"icon": "💎",
"seoKeywords": [
"joias",
"bijuterias",
"acessórios",
"ourives",
"relógios",
"semijoias"
],
"description": "Joalherias, bijuterias, ourives e lojas de acessórios",
"isActive": true
},
{
"name": "Odontologia",
"slug": "odontologia",
"icon": "🦷",
"seoKeywords": [
"dentista",
"odontologia",
"clínica dentária",
"ortodontia",
"implante",
"oral"
],
"description": "Dentistas, clínicas odontológicas e profissionais da área bucal",
"isActive": true
},
{
"name": "Fisioterapia",
"slug": "fisioterapia",
"icon": "🏥",
"seoKeywords": [
"fisioterapeuta",
"fisioterapia",
"reabilitação",
"RPG",
"massagem",
"terapia"
],
"description": "Fisioterapeutas, clínicas de reabilitação e terapias corporais",
"isActive": true
},
{
"name": "Livraria",
"slug": "livraria",
"icon": "📚",
"seoKeywords": [
"livraria",
"livros",
"sebo",
"literatura",
"editora",
"publicação"
],
"description": "Livrarias, sebos, editoras e comércio de livros e publicações",
"isActive": true
},
{
"name": "Floricultura",
"slug": "floricultura",
"icon": "🌸",
"seoKeywords": [
"floricultura",
"flores",
"buquê",
"plantas",
"arranjos",
"casamento"
],
"description": "Floriculturas, arranjos florais e comércio de plantas ornamentais",
"isActive": true
},
{
"name": "Farmácia",
"slug": "farmacia",
"icon": "💊",
"seoKeywords": [
"farmácia",
"farmacêutico",
"medicamentos",
"drogaria",
"manipulação",
"remédios"
],
"description": "Farmácias, drogarias e farmacêuticos especializados",
"isActive": true
},
{
"name": "Delivery",
"slug": "delivery",
"icon": "🛵",
"seoKeywords": [
"delivery",
"entrega",
"motoboy",
"comida",
"aplicativo",
"rápido"
],
"description": "Serviços de delivery, entregadores e aplicativos de entrega",
"isActive": true
}
]

View File

@ -186,6 +186,8 @@ public class AdminController : Controller
[HttpPost]
[Route("ManagePage")]
[RequestSizeLimit(5 * 1024 * 1024)] // Allow 5MB uploads
[RequestFormLimits(MultipartBodyLengthLimit = 5 * 1024 * 1024)]
public async Task<IActionResult> ManagePage(ManagePageViewModel model)
{
ViewBag.IsHomePage = false;
@ -231,10 +233,27 @@ public class AdminController : Controller
{
_logger.LogError(ex, "Error uploading profile image");
ModelState.AddModelError("ProfileImageFile", "Erro ao fazer upload da imagem. Tente novamente.");
TempData["ImageError"] = "Erro ao processar a imagem. Verifique o formato e tamanho.";
// Preservar dados do form e repopular dropdowns
var userPlanType = Enum.TryParse<PlanType>(user.CurrentPlan, true, out var planType) ? planType : PlanType.Trial;
var planLimitations = await _paymentService.GetPlanLimitationsAsync(userPlanType.ToString());
// Repopulate dropdowns
model.AvailableCategories = await _categoryService.GetAllCategoriesAsync();
model.AvailableThemes = await _themeService.GetAvailableThemesAsync();
model.MaxLinksAllowed = userPlanType.GetMaxLinksPerPage();
model.AllowProductLinks = planLimitations.AllowProductLinks;
// Preservar ProfileImageId existente se estava editando
if (!model.IsNewPage && !string.IsNullOrEmpty(model.Id))
{
var existingPage = await _userPageService.GetPageByIdAsync(model.Id);
if (existingPage != null)
{
model.ProfileImageId = existingPage.ProfileImageId;
}
}
return View(model);
}
}
@ -328,10 +347,20 @@ public class AdminController : Controller
return RedirectToAction("Dashboard");
}
// IMPORTANTE: Preservar ProfileImageId da página existente se não houver novo upload
// IMPORTANTE: Tratar remoção de imagem ou preservar existente se não houver novo upload
if (model.ProfileImageFile == null || model.ProfileImageFile.Length == 0)
{
model.ProfileImageId = existingPage.ProfileImageId;
if (model.ProfileImageId == "REMOVE_IMAGE")
{
// Usuário quer remover a imagem existente
model.ProfileImageId = null;
_logger.LogInformation("Profile image removed by user request");
}
else
{
// Preservar imagem existente
model.ProfileImageId = existingPage.ProfileImageId;
}
}
await UpdateUserPageFromModel(existingPage, model);

View File

@ -23,6 +23,14 @@ namespace BCards.Web.Middleware
{
await _next(context);
// Verificar se a resposta já começou antes de modificar headers
if (context.Response.HasStarted)
{
_logger.LogDebug("AuthCache: Response already started, skipping header modifications for {Path}",
context.Request.Path.Value);
return;
}
// Aplicar headers apenas para páginas HTML (não APIs, imagens, etc)
if (context.Response.ContentType?.StartsWith("text/html") == true)
{

View File

@ -4,12 +4,10 @@ using BCards.Web.Repositories;
using BCards.Web.HealthChecks;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Authentication.Google;
using Microsoft.AspNetCore.Authentication.MicrosoftAccount;
using Microsoft.AspNetCore.Localization;
using Microsoft.Extensions.Options;
using MongoDB.Driver;
using System.Globalization;
using Stripe;
using Microsoft.AspNetCore.Authentication.OAuth;
using SendGrid;
using BCards.Web.Middleware;
@ -22,11 +20,9 @@ using Serilog.Sinks.OpenSearch;
var builder = WebApplication.CreateBuilder(args);
// Configure Serilog with environment-specific settings
var isDevelopment = builder.Environment.IsDevelopment();
var hostname = Environment.MachineName;
// 🔥 CORREÇÃO 1: Habilitar SelfLog ANTES de configurar o logger
Serilog.Debugging.SelfLog.Enable(msg =>
{
Console.WriteLine($"[SERILOG SELF] {DateTime.Now:HH:mm:ss} {msg}");
@ -112,7 +108,7 @@ else
.MinimumLevel.Override("Microsoft.AspNetCore.StaticFiles", LogEventLevel.Warning)
.MinimumLevel.Override("Microsoft", LogEventLevel.Warning)
.MinimumLevel.Override("System", LogEventLevel.Warning)
// 🔥 GARANTIR CONSOLE EM PRODUÇÃO TAMBÉM
.WriteTo.Console(
restrictedToMinimumLevel: LogEventLevel.Information,
outputTemplate: "[{Timestamp:yyyy-MM-dd HH:mm:ss} {Level:u3}] {Message:lj}{NewLine}{Exception}")
@ -161,7 +157,6 @@ else
}
}
// 🔥 REMOVER OS Task.Delay E Log.CloseAndFlush desnecessários
var logger = loggerConfig.CreateLogger();
Log.Logger = logger;
@ -176,7 +171,6 @@ builder.Host.UseSerilog();
// Log startup information
Log.Information("Starting BCards application on {Hostname} in {Environment} mode", hostname, builder.Environment.EnvironmentName);
// 🔥 CONFIGURAR FORWARDED HEADERS NO BUILDER
builder.Services.Configure<ForwardedHeadersOptions>(options =>
{
options.ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto;
@ -186,7 +180,6 @@ builder.Services.Configure<ForwardedHeadersOptions>(options =>
options.ForwardLimit = null;
});
// 🔥 OTIMIZAÇÃO: Sistema de Compressão de Response (Brotli + Gzip)
builder.Services.AddResponseCompression(options =>
{
options.EnableForHttps = true;
@ -410,12 +403,25 @@ builder.Services.AddScoped<IEmailService, EmailService>();
builder.Services.AddScoped<IImageStorageService, GridFSImageStorage>();
// Configure upload limits for file handling (images up to 5MB)
builder.Services.Configure<FormOptions>(options =>
{
options.MultipartBodyLengthLimit = 10 * 1024 * 1024;
options.MultipartBodyLengthLimit = 5 * 1024 * 1024; // 5MB
options.ValueLengthLimit = int.MaxValue;
options.ValueCountLimit = int.MaxValue;
options.KeyLengthLimit = int.MaxValue;
options.BufferBody = true;
options.BufferBodyLengthLimit = 5 * 1024 * 1024; // 5MB
options.MultipartBodyLengthLimit = 5 * 1024 * 1024; // 5MB
options.MultipartHeadersLengthLimit = 16384;
});
// Configure Kestrel server limits for larger requests
builder.Services.Configure<Microsoft.AspNetCore.Server.Kestrel.Core.KestrelServerOptions>(options =>
{
options.Limits.MaxRequestBodySize = 5 * 1024 * 1024; // 5MB
options.Limits.RequestHeadersTimeout = TimeSpan.FromMinutes(2);
options.Limits.KeepAliveTimeout = TimeSpan.FromMinutes(2);
});
builder.Services.AddScoped<ILivePageRepository, LivePageRepository>();
@ -555,11 +561,11 @@ app.UseRequestLocalization();
app.UseAuthentication();
app.UseAuthorization();
app.UseMiddleware<BCards.Web.Middleware.SmartCacheMiddleware>();
app.UseMiddleware<BCards.Web.Middleware.AuthCacheMiddleware>();
app.UseMiddleware<BCards.Web.Middleware.PlanLimitationMiddleware>();
app.UseMiddleware<BCards.Web.Middleware.PageStatusMiddleware>();
app.UseMiddleware<BCards.Web.Middleware.PreviewTokenMiddleware>();
app.UseMiddleware<SmartCacheMiddleware>();
app.UseMiddleware<AuthCacheMiddleware>();
app.UseMiddleware<PlanLimitationMiddleware>();
app.UseMiddleware<PageStatusMiddleware>();
app.UseMiddleware<PreviewTokenMiddleware>();
app.UseMiddleware<ModerationAuthMiddleware>();
if (app.Environment.IsDevelopment())
@ -640,7 +646,6 @@ app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
// Initialize default data
using (var scope = app.Services.CreateScope())
{
var themeService = scope.ServiceProvider.GetRequiredService<IThemeService>();
@ -660,12 +665,10 @@ using (var scope = app.Services.CreateScope())
await categoryService.InitializeDefaultCategoriesAsync();
}
// 🔥 CORREÇÃO 12: Logs adicionais após inicialização
Log.Information("Default themes and categories initialized successfully");
}
catch (Exception ex)
{
//var logger = scope.ServiceProvider.GetRequiredService<ILogger<Program>>();
Log.Error(ex, "Error initializing default data");
}
}
@ -674,7 +677,6 @@ try
{
Log.Information("BCards application started successfully on {Hostname}", hostname);
// 🔥 CORREÇÃO 13: Aguardar um pouco para logs serem enviados antes da aplicação rodar
if (isDevelopment)
{
Console.WriteLine("[DEBUG] Aguardando envio de logs iniciais...");
@ -693,10 +695,8 @@ finally
{
Log.Information("BCards application shutting down on {Hostname}", hostname);
// 🔥 CORREÇÃO 14: Aguardar logs finais serem enviados
await Task.Delay(5000);
Log.CloseAndFlush();
}
// Make Program accessible for integration tests
public partial class Program { }

View File

@ -119,9 +119,12 @@
<input type="file" class="form-control" id="profileImageInput" name="ProfileImageFile" accept="image/*">
<div class="form-text">
<i class="fas fa-info-circle me-1"></i>
Formatos aceitos: JPG, PNG, GIF. Máximo: 2MB e 4000x4000px. Será redimensionada para 400x400px.
Formatos aceitos: JPG, PNG, GIF. Máximo: 2MB e 4000x4000px.
</div>
<span class="text-danger" id="imageError"></span>
<div class="invalid-feedback" id="imageErrorFeedback" style="display: none;">
Erro com a imagem selecionada
</div>
</div>
</div>
<div class="col-md-4">
@ -245,7 +248,9 @@
"twitter",
"instagram"
};
var match = myList.FirstOrDefault(stringToCheck => Model.Links[i].Icon.Contains(stringToCheck));
var match = myList.FirstOrDefault(stringToCheck =>
!string.IsNullOrEmpty(Model.Links[i].Icon) &&
Model.Links[i].Icon.Contains(stringToCheck));
if (match==null)
{
if (Model.Links[i].Type==LinkType.Normal)
@ -369,10 +374,10 @@
</div>
</div>
@{
var facebook = Model.Links.Where(x => x.Icon.Contains("facebook")).FirstOrDefault();
var twitter = Model.Links.Where(x => x.Icon.Contains("twitter")).FirstOrDefault();
var whatsapp = Model.Links.Where(x => x.Icon.Contains("whatsapp")).FirstOrDefault();
var instagram = Model.Links.Where(x => x.Icon.Contains("instagram")).FirstOrDefault();
var facebook = Model.Links.Where(x => !string.IsNullOrEmpty(x.Icon) && x.Icon.Contains("facebook")).FirstOrDefault();
var twitter = Model.Links.Where(x => !string.IsNullOrEmpty(x.Icon) && x.Icon.Contains("twitter")).FirstOrDefault();
var whatsapp = Model.Links.Where(x => !string.IsNullOrEmpty(x.Icon) && x.Icon.Contains("whatsapp")).FirstOrDefault();
var instagram = Model.Links.Where(x => !string.IsNullOrEmpty(x.Icon) && x.Icon.Contains("instagram")).FirstOrDefault();
var facebookUrl = facebook !=null ? facebook.Url : "";
var twitterUrl = twitter !=null ? twitter.Url : "";
var whatsappUrl = whatsapp !=null ? whatsapp.Url.Replace("https://wa.me/","") : "";
@ -946,6 +951,9 @@
// Check for validation errors and show toast + open accordion
checkValidationErrors();
// Check for server-side image errors
checkServerImageErrors();
// Garantir que campos não marcados sejam string vazia ao submeter
$('form').on('submit', function() {
ensureUncheckedFieldsAreEmpty();
@ -1376,6 +1384,42 @@
}, 5000);
}
// Nova função para mostrar toast de erro com múltiplas mensagens
function showErrorToast(errors) {
if (!Array.isArray(errors)) {
errors = [errors];
}
const errorList = errors.map(error => `<li>${error}</li>`).join('');
const toastHtml = `
<div class="toast align-items-center text-white bg-danger border-0" role="alert" aria-live="assertive" aria-atomic="true">
<div class="d-flex">
<div class="toast-body">
<strong><i class="fas fa-exclamation-triangle me-2"></i>Erro de Validação</strong>
<ul class="mb-0 mt-2 small">
${errorList}
</ul>
</div>
<button type="button" class="btn-close btn-close-white me-2 m-auto" data-bs-dismiss="toast"></button>
</div>
</div>
`;
if (!$('#toastContainer').length) {
$('body').append('<div id="toastContainer" class="toast-container position-fixed top-0 end-0 p-3"></div>');
}
const $toast = $(toastHtml);
$('#toastContainer').append($toast);
const toast = new bootstrap.Toast($toast[0], { delay: 6000 }); // 6 segundos para erro
toast.show();
setTimeout(() => {
$toast.remove();
}, 7000);
}
// Validation Error Handling
function checkValidationErrors() {
// Só verificar erros se estamos em um POST-back (ou seja, se ModelState foi validado)
@ -1663,19 +1707,24 @@
// Preview da imagem selecionada
fileInput.on('change', function(e) {
const file = e.target.files[0];
errorSpan.text('');
const feedbackDiv = $('#imageErrorFeedback');
// Limpar estados de erro anteriores
clearImageError();
if (!file) return;
// Validações client-side
// Validações client-side com feedback visual melhorado
if (!file.type.match(/^image\/(jpeg|jpg|png|gif)$/i)) {
errorSpan.text('Formato inválido. Use apenas JPG, PNG ou GIF.');
setImageError('Formato inválido. Use apenas JPG, PNG ou GIF.');
showErrorToast(['Formato de imagem inválido. Use apenas JPG, PNG ou GIF.']);
fileInput.val('');
return;
}
if (file.size > 2 * 1024 * 1024) { // 2MB
errorSpan.text('Arquivo muito grande. Máximo 2MB.');
setImageError('Arquivo muito grande. Máximo 2MB.');
showErrorToast(['Arquivo muito grande. O tamanho máximo é de 2MB.']);
fileInput.val('');
return;
}
@ -1694,7 +1743,7 @@
removeBtn.on('click', function() {
if (confirm('Tem certeza que deseja remover a imagem de perfil?')) {
fileInput.val('');
hiddenField.val('');
hiddenField.val('REMOVE_IMAGE'); // Valor específico para indicar remoção
preview.attr('src', '/images/default-avatar.svg');
removeBtn.hide();
errorSpan.text('');
@ -1706,6 +1755,59 @@
removeBtn.show();
}
}
// Funções auxiliares para gerenciar estados de erro da imagem
function setImageError(message) {
const fileInput = $('#profileImageInput');
const errorSpan = $('#imageError');
const feedbackDiv = $('#imageErrorFeedback');
// Adicionar classes de erro
fileInput.addClass('is-invalid');
// Mostrar mensagens de erro
errorSpan.text(message);
feedbackDiv.text(message).show();
// Adicionar borda vermelha na área de preview
$('.profile-image-preview').addClass('border-danger');
}
function clearImageError() {
const fileInput = $('#profileImageInput');
const errorSpan = $('#imageError');
const feedbackDiv = $('#imageErrorFeedback');
// Remover classes de erro
fileInput.removeClass('is-invalid is-valid');
// Limpar mensagens de erro
errorSpan.text('');
feedbackDiv.hide();
// Remover borda vermelha da área de preview
$('.profile-image-preview').removeClass('border-danger');
}
// Função para verificar erros de imagem do servidor
function checkServerImageErrors() {
@if (TempData["ImageError"] != null)
{
@:const imageError = '@Html.Raw(TempData["ImageError"])';
@:setImageError(imageError);
@:showErrorToast([imageError]);
}
// Verificar ModelState errors para ProfileImageFile
@if (ViewData.ModelState.ContainsKey("ProfileImageFile"))
{
@:const modelStateError = '@Html.Raw(ViewData.ModelState["ProfileImageFile"].Errors.FirstOrDefault()?.ErrorMessage)';
@:if (modelStateError) {
@:setImageError(modelStateError);
@:showErrorToast([modelStateError]);
@:}
}
}
</script>
}